diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..32566aa --- /dev/null +++ b/.editorconfig @@ -0,0 +1,294 @@ +# NOTE: Requires **VS2019 16.3** or later + +# Rules for JustMyCodeToggle +# Description: Code analysis rules for JustMyCodeToggle. +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = crlf +indent_style = space +indent_size = 4 +tab_width = 4 +insert_final_newline = true + +# C# files +[*.cs] +# Organize using directives +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false +# VSEXTPREVIEW_SETTINGS: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +dotnet_diagnostic.VSEXTPREVIEW_SETTINGS.severity = none + + + +# Code style defaults +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true + +# Naming conventions +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_naming_style.camel_case_underscore.capitalization = camel_case +dotnet_naming_style.camel_case_underscore.required_prefix = _ + +dotnet_naming_rule.public_members_pascal_case.symbols = public_members +dotnet_naming_rule.public_members_pascal_case.style = pascal_case +dotnet_naming_rule.public_members_pascal_case.severity = suggestion + +dotnet_naming_rule.private_fields_camel_case_underscore.symbols = private_fields +dotnet_naming_rule.private_fields_camel_case_underscore.style = camel_case_underscore +dotnet_naming_rule.private_fields_camel_case_underscore.severity = suggestion +csharp_indent_labels = no_change +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:suggestion +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_space_around_binary_operators = before_and_after +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_prefer_static_anonymous_function = true:suggestion +csharp_prefer_static_local_function = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_var_elsewhere = false:silent +csharp_style_var_when_type_is_apparent = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_prefer_extended_property_pattern = true:suggestion +dotnet_diagnostic.CA1001.severity = warning + +dotnet_diagnostic.CA1009.severity = warning + +dotnet_diagnostic.CA1016.severity = warning + +dotnet_diagnostic.CA1033.severity = warning + +dotnet_diagnostic.CA1049.severity = warning + +dotnet_diagnostic.CA1060.severity = warning + +dotnet_diagnostic.CA1061.severity = warning + +dotnet_diagnostic.CA1063.severity = warning + +dotnet_diagnostic.CA1065.severity = warning + +dotnet_diagnostic.CA1301.severity = warning + +dotnet_diagnostic.CA1400.severity = warning + +dotnet_diagnostic.CA1401.severity = warning + +dotnet_diagnostic.CA1403.severity = warning + +dotnet_diagnostic.CA1404.severity = warning + +dotnet_diagnostic.CA1405.severity = warning + +dotnet_diagnostic.CA1410.severity = warning + +dotnet_diagnostic.CA1415.severity = warning + +dotnet_diagnostic.CA1821.severity = warning + +dotnet_diagnostic.CA1900.severity = warning + +dotnet_diagnostic.CA1901.severity = warning + +dotnet_diagnostic.CA2002.severity = warning + +dotnet_diagnostic.CA2100.severity = warning + +dotnet_diagnostic.CA2101.severity = warning + +dotnet_diagnostic.CA2108.severity = warning + +dotnet_diagnostic.CA2111.severity = warning + +dotnet_diagnostic.CA2112.severity = warning + +dotnet_diagnostic.CA2114.severity = warning + +dotnet_diagnostic.CA2116.severity = warning + +dotnet_diagnostic.CA2117.severity = warning + +dotnet_diagnostic.CA2122.severity = warning + +dotnet_diagnostic.CA2123.severity = warning + +dotnet_diagnostic.CA2124.severity = warning + +dotnet_diagnostic.CA2126.severity = warning + +dotnet_diagnostic.CA2131.severity = warning + +dotnet_diagnostic.CA2132.severity = warning + +dotnet_diagnostic.CA2133.severity = warning + +dotnet_diagnostic.CA2134.severity = warning + +dotnet_diagnostic.CA2137.severity = warning + +dotnet_diagnostic.CA2138.severity = warning + +dotnet_diagnostic.CA2140.severity = warning + +dotnet_diagnostic.CA2141.severity = warning + +dotnet_diagnostic.CA2146.severity = warning + +dotnet_diagnostic.CA2147.severity = warning + +dotnet_diagnostic.CA2149.severity = warning + +dotnet_diagnostic.CA2200.severity = warning + +dotnet_diagnostic.CA2202.severity = warning + +dotnet_diagnostic.CA2207.severity = warning + +dotnet_diagnostic.CA2212.severity = warning + +dotnet_diagnostic.CA2213.severity = warning + +dotnet_diagnostic.CA2214.severity = warning + +dotnet_diagnostic.CA2216.severity = warning + +dotnet_diagnostic.CA2220.severity = warning + +dotnet_diagnostic.CA2229.severity = warning + +dotnet_diagnostic.CA2231.severity = warning + +dotnet_diagnostic.CA2232.severity = warning + +dotnet_diagnostic.CA2235.severity = warning + +dotnet_diagnostic.CA2236.severity = warning + +dotnet_diagnostic.CA2237.severity = warning + +dotnet_diagnostic.CA2238.severity = warning + +dotnet_diagnostic.CA2240.severity = warning + +dotnet_diagnostic.CA2241.severity = warning + +dotnet_diagnostic.CA2242.severity = warning + +dotnet_diagnostic.CS1573.severity = silent + +dotnet_diagnostic.CS1591.severity = silent + +dotnet_diagnostic.CS1712.severity = silent + +# IDE0003 duplicates functionality provided by SX1101 +dotnet_diagnostic.IDE0003.severity = none + +dotnet_diagnostic.SA1101.severity = none + +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2275 +dotnet_diagnostic.SA1139.severity = none + +dotnet_diagnostic.SA1202.severity = none + +dotnet_diagnostic.SA1204.severity = none + +dotnet_diagnostic.SA1309.severity = none + +dotnet_diagnostic.SA1412.severity = warning + +dotnet_diagnostic.SA1600.severity = none + +dotnet_diagnostic.SA1601.severity = none + +dotnet_diagnostic.SA1602.severity = none + +dotnet_diagnostic.SA1611.severity = none + +dotnet_diagnostic.SA1615.severity = none + +dotnet_diagnostic.SX1101.severity = warning + +dotnet_diagnostic.SX1309.severity = warning + +dotnet_diagnostic.SX1309S.severity = warning +# VSTHRD200: Use "Async" suffix for async methods +dotnet_diagnostic.VSTHRD200.severity = none +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_readonly_field = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_field = false:silent + + +[*.vsct] +indent_style = space +indent_size = 2 + diff --git a/JustMyCodeToggle.sln b/JustMyCodeToggle.sln index 69854fb..3125f0c 100644 --- a/JustMyCodeToggle.sln +++ b/JustMyCodeToggle.sln @@ -1,19 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 -MinimumVisualStudioVersion = 15.0 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{05CB4485-3F59-4189-96D8-F046221EC9CE}" +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11123.170 d18.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tvl.VisualStudio.JustMyCodeToggle", "Tvl.VisualStudio.JustMyCodeToggle\Tvl.VisualStudio.JustMyCodeToggle.csproj", "{10CAD392-E083-41EB-92C9-B5F3142FA9F8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" ProjectSection(SolutionItems) = preProject - .gitattributes = .gitattributes - .gitignore = .gitignore - appveyor.yml = appveyor.yml - LICENSE.txt = LICENSE.txt - README.md = README.md + .editorconfig = .editorconfig EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tvl.VisualStudio.JustMyCodeToggle", "Tvl.VisualStudio.JustMyCodeToggle\Tvl.VisualStudio.JustMyCodeToggle.csproj", "{10CAD392-E083-41EB-92C9-B5F3142FA9F8}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/README.md b/README.md index 1eb4bf5..4a7150f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ -# Just My Code Toggle extension for Visual Studio 2015+ +# Just My Code Toggle extension for Visual Studio 2015->2026+ -[![Build status](https://ci.appveyor.com/api/projects/status/dnxhxa3chg1dtpnt/branch/master?svg=true)](https://ci.appveyor.com/project/sharwell/justmycodetoggle/branch/master) +This primary goal of this extension is to add the **Debug.JustMyCodeToggle** from the options menu to the Visual Studio main window. By default, the command appears in the **Debug** toolbar as well as in the context menu for the **Call Stack** window. It also adds a few other setting commands in the toolbar using a split-button to toggle: Native Code Debugging, All Symbols / Selective Symbol loading, Disable .NET and JIT code optimization for external code or release builds for better preservation of variables/stack items normally optimized away. -This extension adds the **Debug.JustMyCodeToggle** command to Visual Studio. By default, the command appears in the **Debug** toolbar as well as in the context menu for the **Call Stack** window. +JustMyCode toggle can be changed while debugging but the others will only take effect on the next debug session. ### Debug Toolbar ![Debug Toolbar](doc/toolbar.png) +The toolbar also features a few other toggles including: +- Native Code Debugging - The debug properties debug native code option (stored in the .csproj.user file) +- All Module Symbol Loading - Toggle between VS trying to only load symbols for needed files/modules or loading for all modules ( this is the Options->Debugging->Symbols "Automatic searching symbols" dropdown ) +- Disabling native / managed code optimization - Better debugging for native/3rd party code to preserve stack and variables (Only for new CPS style projects with launch profile support, as we need to set env vars). Env vars are stored in the launch profile but are deleted when you toggle this back off. This is similar to / slightly more robust than the Debugging-> use of precompiled images (which I couldn't find). + ### Call Stack Window @@ -14,4 +19,33 @@ This extension adds the **Debug.JustMyCodeToggle** command to Visual Studio. By ### Keyboard -By default, the new command is not bound to a keystroke. A key binding may be added manually by configuring a binding for the **Debug.JustMyCodeToggle** command. +By default, the new command is not bound to a keystroke. A key binding may be added manually by configuring a binding for any of the following commands: +- Debug.JustMyCodeToggle +- Debug.JMCSymbolLocalToggle +- Debug.JMCNativeCodeDebugging +- Debug.JMCDisableJitOptmizations + + +### VS 2026 Issues +VS 2026 once again has changed things so the 2022 tricks don't all work. It has largely been fixed except for "All Module Symbol Loading" for now in 2026 it will just open the options page to this, until some bugs in Extensibility are fixed. If you know of a way let me know:) + + +### Implementation +#### Commands +To simplify commands they are broken into two parts, command "setters" that handle getting/setting values using a certain manager (backend) and a base command helper. Due to the fact we partially support new Extensibility extension sdk there is a bit of oddity to be able to not duplicate code between the old OurOLEButton and our new OurExtensibilityToggleButton. + + +#### Managers +Managers are the different ways these things can be controlled in VS. We have: +- DebuggerServiceManager - Uses Debugger5 primarily was the attempt to do `debugger.SetSymbolSettings` to handle setting the module loading but it doesn't seem to actually work (no error just no change). +- DteManager - This uses dte.Properties to set options. This is kind of like a UI interaction interface. Note there is no documentation of the actual property categories/pages that exist but the manager has `DumpAllSettings` that contains as many as I have found. +- ExtensibilitySettingManager - The new unified settings backend. This is pretty sleek new system unfortunately getting the Extensibility service is broken right now. It does work if called from the extension menu. It has the ability to auto watch for external changes too. +- LaunchProfileManager - For modifying launch profiles of projects directly +- ProjectEventHandler - Generates reference events for us for things like project/solution changes so we can update current state +- SettingsStoreManager - For the VS Settings store (roaming,user,etc). While some settings appear here often changing things may not change the actual setting in VS (esp in 2026). +- StartupProjectManager - Checks if the startup project has changed and allows accessing the startup projet + +#### Entry Points +As the script is a hybrid old style and new extensibility extension it has two entry points: +- ExtensibilityExtensionEntrypoint - Extensibility entrypoint we cant call any extensibility code until here. Unfortunately this is only called once an extensibility command is clicked as it is lazy inited. +- JustMyCodeTogglePackage - The traditional entry point for VSSDK plugins, called shortly after load. \ No newline at end of file diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Commands/CommandSetters.cs b/Tvl.VisualStudio.JustMyCodeToggle/Commands/CommandSetters.cs new file mode 100644 index 0000000..6a3f449 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Commands/CommandSetters.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Extensibility.Settings; +using Microsoft.VisualStudio.Shell.Interop; +using Tvl.VisualStudio.JustMyCodeToggle.Managers; +using kvp = System.Collections.Generic.KeyValuePair; +namespace Tvl.VisualStudio.JustMyCodeToggle.Commands +{ + internal class ExtensibilityBoolToValSettingSetter : ExtensibilitySettingSetter, ISetSettingInterface{ + private SETTING_TYPE trueVal; + private SETTING_TYPE falseVal; + + public ExtensibilityBoolToValSettingSetter(string propertyName, SETTING_TYPE trueVal, SETTING_TYPE falseVal) : base(propertyName) + { + this.trueVal = trueVal; + this.falseVal = falseVal; + } + Task ISetSettingInterface.SetSetting(bool val) + { + return this.SetSetting(val ? trueVal : falseVal); + } + + async Task ISetSettingInterface.GetSetting() + { + return (await this.GetSetting()).Equals(trueVal); + } + } + internal class ExtensibilitySettingSetter : ISetSettingInterface + { + public Task GetSetting() => _settingsManager.Value.GetSetting(propertyName); + public Task SetSetting(SETTING_TYPE val) + { + return _settingsManager.Value.SetSetting(propertyName, val); + } + protected readonly string propertyName; + protected Lazy _settingsManager; + public Task WatchSetting(Action> onChange) => _settingsManager.Value.WatchSetting(propertyName, onChange); + public ExtensibilitySettingSetter(string propertyName) + { + this.propertyName = propertyName; + _settingsManager = new(() => JustMyCodeTogglePackage.instance.GetTypedService()); + } + } + /// + /// for bool values use strings true/false + /// + internal class BuildStoreSetter : ISetSettingInterface + { + public async Task GetSetting() + { + if (!await SyncToCurStartProj()) + return default; + + var buildStorage = curStartupProject as IVsBuildPropertyStorage; + + var cmdres = buildStorage.GetPropertyValue(propertyName, curConfigName, (uint)_PersistStorageType.PST_USER_FILE, out var existing); + if (cmdres != VSConstants.S_OK || String.IsNullOrWhiteSpace(existing)) + buildStorage.GetPropertyValue(propertyName, curConfigName, (uint)_PersistStorageType.PST_PROJECT_FILE, out existing);//user file will override project file but if user file is blank fallback to project + + + return strToVal(existing); + + + } + public async Task SyncToCurStartProj() + { + curStartupProject = await _settingsManager.Value.GetStartupProject(); + curConfigName = curStartupProject == null ? null : await curStartupProject.GetCurrentConfigurationName(); + return curStartupProject != null && curConfigName != null; + } + protected virtual string valToStr(SETTING_TYPE val) + { + if (val is bool bval) + return bval ? "true" : "false"; + else if (val is int ival) + return ival.ToString(); + return val.ToString(); + } + protected virtual SETTING_TYPE strToVal(string str) + { + if (typeof(SETTING_TYPE) == typeof(bool)) + { + if (Boolean.TryParse(str, out var bval)) + return (SETTING_TYPE)(object)bval; + else + return default; + + } + else if (typeof(SETTING_TYPE) == typeof(int)) + { + if (Int32.TryParse(str, out var ival)) + return (SETTING_TYPE)(object)ival; + else + return default; + } + return (SETTING_TYPE)(object)str; + } + public async Task SetSetting(SETTING_TYPE _val) + { + + if (!await SyncToCurStartProj()) + return; + var val = valToStr(_val); + + var buildStorage = curStartupProject as IVsBuildPropertyStorage; + var cmdres = buildStorage.SetPropertyValue(propertyName, curConfigName, (uint)_PersistStorageType.PST_USER_FILE, val); + + await curStartupProject.SaveProject(force: true);//must use force or else it is as if the change wasn't detected. Also cannot figure out how to just save the user file I believe we must save both + //note while this changes our .user file it doesn't update the active native debugging plan for launch + + var dteProj = await curStartupProject.GetDTEProjectFromIvsProject(); + try + { + + var itm = dteProj.ConfigurationManager?.ActiveConfiguration?.Properties?.Item(propertyName); //this updates the actual launch project in memory object + if (itm != null) + itm.Value = val; + + } + catch { } + + + + } + + + protected readonly string propertyName; + protected Lazy _settingsManager; + private IVsProject curStartupProject; + private string curConfigName; + + public BuildStoreSetter(string propertyName) + { + this.propertyName = propertyName; + _settingsManager = new(JustMyCodeTogglePackage.instance.GetTypedService); + } + } + internal class DteStoreSetter : ISetSettingInterface + { + public Task GetSetting() => Task.FromResult(_settingsManager.Value.GetDteSetting(category, page, propertyName)); + + public Task SetSetting(SETTING_TYPE val) + { + _settingsManager.Value.SetDteSetting(category, page, propertyName, val); + return Task.CompletedTask; + } + + protected readonly string category; + private readonly string page; + protected readonly string propertyName; + protected Lazy _settingsManager; + + public DteStoreSetter(string category, string page, string propertyName) + { + + this.category = category; + this.page = page; + this.propertyName = propertyName; + _settingsManager = new(JustMyCodeTogglePackage.instance.GetTypedService); + } + } + internal class StartupProfileEnvVarSetter : StartupProfileSetter + { + private string deleteOnVal; + override public async Task GetSetting() + { + return await _settingsManager.Value.GetProfileEnvVar(propertyName) ?? deleteOnVal; + } + + public virtual Task SetSetting(string val) => _settingsManager.Value.UpdateLaunchProfileENVVars(val == deleteOnVal, new kvp(propertyName, val)); + public virtual Task SetSettingAddl(string val, params kvp[] addl) => _settingsManager.Value.UpdateLaunchProfileENVVars(val == deleteOnVal, [new kvp(propertyName, val), .. addl]); + public StartupProfileEnvVarSetter(string propertyName, string deleteOnVal) : base(propertyName) + { + this.deleteOnVal = deleteOnVal; + } + } + internal class StartupProfileSetter : ISetSettingInterface + { + public virtual async Task GetSetting() + { + var launchProfile = await _settingsManager.Value.GetLaunchProfile(); + if (launchProfile == null) + { + Debug.WriteLine($"JMC: in theory project supports CPS profiles but no launch profile found??"); + return default; + } + else + { + var curVal = launchProfile.OtherSettings.FirstOrDefault(a => a.Key.Equals(propertyName, StringComparison.CurrentCultureIgnoreCase)); + return (SETTING_TYPE)curVal.Value; + } + } + + public virtual Task SetSetting(SETTING_TYPE val) => _settingsManager.Value.UpdateLaunchProfileSetting(propertyName, val); + + protected readonly string propertyName; + protected Lazy _settingsManager; + protected Lazy _startManager; + + public StartupProjectManager StartManager => _startManager.Value; + public bool SupportsLaunchProfiles => _settingsManager.Value.SupportsLaunchProfiles; + + public StartupProfileSetter(string propertyName) + { + this.propertyName = propertyName; + _settingsManager = new(JustMyCodeTogglePackage.instance.GetTypedService); + _startManager = new(JustMyCodeTogglePackage.instance.GetTypedService); + } + } + + internal class SettingsStoreSetter : ISetSettingInterface + { + public Task GetSetting() => Task.FromResult(_settingsManager.Value.GetSetting(collectionPath, propertyName)); + + public Task SetSetting(SETTING_TYPE val) + { + _settingsManager.Value.SetSetting(collectionPath, propertyName, val); + return Task.CompletedTask; + } + + protected readonly string collectionPath; + protected readonly string propertyName; + protected Lazy _settingsManager; + + public SettingsStoreSetter(string collectionPath, string propertyName) + { + + this.collectionPath = collectionPath; + this.propertyName = propertyName; + _settingsManager = new(JustMyCodeTogglePackage.instance.GetTypedService); + } + + } + internal interface ISetSettingInterface + { + Task SetSetting(SETTING_TYPE val); + Task GetSetting(); + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Commands/FullSymbolLoadToggleCmd.cs b/Tvl.VisualStudio.JustMyCodeToggle/Commands/FullSymbolLoadToggleCmd.cs new file mode 100644 index 0000000..1585691 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Commands/FullSymbolLoadToggleCmd.cs @@ -0,0 +1,135 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; +using EnvDTE; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.Commands; +using Microsoft.VisualStudio.Extensibility.Settings; +using Microsoft.VisualStudio.Shell; +using Tvl.VisualStudio.JustMyCodeToggle.Managers; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Commands +{ + internal class DebuggerSymbolLoadSetter : ISetSettingInterface + + { + private DebuggerServiceManager _settingsManager; + + public DebuggerSymbolLoadSetter() + { + _settingsManager = JustMyCodeTogglePackage.instance.GetTypedService(); + } + public Task GetSetting() => Task.FromResult(_settingsManager.GetLoadAllModules()); + + public async Task SetSetting(bool val) + { + await _settingsManager.SetLoadAllModulesAsync(val); + } + } + [Command(PackageGuids.guidJustMyCodeTogglePackageCmdSetString, PackageIds.JMCSymbolLoadBtn)] + internal class FullSymbolLoadBtn : OurOLEButton + { + + protected override void BeforeQueryStatus(EventArgs e) + { + var useCmd = !ProjectExtensions.IsVS2026; + useCmd = true; + + this.Command.Visible = useCmd; + + //this.Command.Visible = true; + + //this.Command.Enabled = useCmd; + //this.Command.Supported = useCmd; + + + //this.Command.Enabled = useCmd; + //this.Command.Supported = useCmd; + + base.BeforeQueryStatus(e); + } + protected override Task InitializeCompletedAsync() + { + + return base.InitializeCompletedAsync(); ; + } + + protected DteManager dteManager; + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + dteManager ??= JustMyCodeTogglePackage.instance.GetTypedService(); + await dteManager.ExecuteCommandAsync("Tools.Options", "Debugging.Symbols"); + + + //return base.ExecuteAsync(e); + } + } + + [VisualStudioContribution] + internal class FullSymbolLoadExtensibilityBtn : OurExtensibilityToggleButton + { + public FullSymbolLoadExtensibilityBtn(VisualStudioExtensibility extensibility) + { + JustMyCodeTogglePackage.instance.RegisterService(new ExtensibilitySettingManager(extensibility)); + } + //public override Task InitializeAsync(CancellationToken cancellationToken) + //{ + + // return base.InitializeAsync(cancellationToken); + //} + public override CommandConfiguration CommandConfiguration => new("Toggle Loading All Symbols") + { + // Use this object initializer to set optional parameters for the command. The required parameter, + // displayName, is set above. DisplayName is localized and references an entry in .vsextension\string-resources.json. + Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText), + Flags = CommandFlags.CanToggle, + + //VsctCommandMapping = new VsctId(new(PackageGuids.guidJustMyCodeTogglePackageCmdSetString),PackageIds.JMCSymbolLoadBtn), + Placements = [ + CommandPlacement.KnownPlacements.ExtensionsMenu, + //CommandPlacement.VsctParent(new(PackageGuids.guidJustMyCodeTogglePackageCmdSetString),PackageIds.JMCToolbarGroup, priority: 0x124), + + ], + }; + + + } + + internal class FullSymbolLoadCmd : ToggleSettingDynamicSetterCmd + { + private SettingsStoreSetter settingStoreSetter; + private ExtensibilityBoolToValSettingSetter unifiedSetter; + private IDisposable watched; + public FullSymbolLoadCmd() : base(true, false) + { + this.settingStoreSetter = new("Debugger", "SymbolUseExcludeList"); + this.unifiedSetter = new ExtensibilityBoolToValSettingSetter(@"debugging.symbols.load.moduleFilterMode", "loadAllButExcluded", "loadOnlyIncluded"); + if (ProjectExtensions.IsVS2026 && ExtensibilitySettingManager.Inited) + { + watched = this.unifiedSetter.WatchSetting(SettingChanged); + } + else + { + setter = settingStoreSetter; + } + } + + private async void SettingChanged(SettingValue value) + { + //ISetSettingInterface setter = this.unifiedSetter; + //await this.SetSetting(await setter.GetSetting()); + await this.SyncCheckedToCurVal(); + + } + + protected override Task[]> GetSetters() + { + ISetSettingInterface setter = ProjectExtensions.IsVS2026 && ExtensibilitySettingManager.Inited ? unifiedSetter : settingStoreSetter; + return Task.FromResult[]>([setter]); + } + } + + +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Commands/JitOptimizationsToggleCmd.cs b/Tvl.VisualStudio.JustMyCodeToggle/Commands/JitOptimizationsToggleCmd.cs new file mode 100644 index 0000000..9786e41 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Commands/JitOptimizationsToggleCmd.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Commands +{ + [Command(PackageGuids.guidJustMyCodeTogglePackageCmdSetString,PackageIds.JMCDisableJitOptmizationsBtn)] + internal class JitOptimizationsBtn : OurOLEButton + { + } + + internal class JitOptimizationsCommand : ToggleSettingCmd, ISetSettingInterface + { + protected StartupProfileEnvVarSetter profileSetter; + + private const string primaryCheckVar = "COMPlus_ZapDisable"; + public JitOptimizationsCommand() : base(null, true, false) + { + setter = this; + profileSetter = new(primaryCheckVar, "0"); + var prefixes = new string[] { "DOTNET_", "COMPlus_" }; + var envs = new List>(); + var baseVars = new Dictionary(){ {"ZapDisable","1" }, + {"ReadyToRun","0" }, + {"TieredCompilation","0" }, + {"JITMinOpts","1" }}; + foreach (var kvp in baseVars) + { + foreach (var prefix in prefixes) + { + var key = prefix + kvp.Key; + if (key != primaryCheckVar) + envs.Add(new(key, kvp.Value)); + } + } + this.ENVVars = envs.ToArray(); + profileSetter.StartManager.StartupProjectChanged += (_,_) => this.SyncCheckedToCurVal(); + profileSetter.StartManager.ActiveProjectSupportsCPSProfilesChanged += (_, _) => SyncEnabled(); + } + + + private void SyncEnabled() + { + Native.SetEnabled( profileSetter.StartManager.ActiveProjectSupportsCPSProfiles); + + } + + public KeyValuePair[] ENVVars { get; } + + public async Task GetSetting() + { + return await profileSetter.GetSetting() == "1"; + } + + public Task SetSetting(bool val) + { + + return profileSetter.SetSettingAddl(val ? "1" : "0", this.ENVVars); + } + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Commands/JustMyCodeToggleCmd.cs b/Tvl.VisualStudio.JustMyCodeToggle/Commands/JustMyCodeToggleCmd.cs new file mode 100644 index 0000000..ee68f28 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Commands/JustMyCodeToggleCmd.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; +using EnvDTE; + + +namespace Tvl.VisualStudio.JustMyCodeToggle.Commands +{ + [Command(PackageGuids.guidJustMyCodeTogglePackageCmdSetString,PackageIds.JMCMenuController)] + internal class MenuBtnCmd : BaseCommand + { + public MenuBtnCmd() => instance = this; + internal static MenuBtnCmd instance; + } + + [Command(PackageGuids.guidJustMyCodeTogglePackageCmdSetString,PackageIds.JMCJustMyCodeBtn)] + internal class JustMyCodeBtn : OurOLEButton + { + } + + internal class JustMyCodeCmd : ToggleSettingDynamicSetterCmd + { + private SettingsStoreSetter settingStoreSetter; + private DteStoreSetter dteSetter; + + protected override Task[]> GetSetters() + { + ISetSettingInterface setter = ProjectExtensions.IsVS2026 ? dteSetter : settingStoreSetter; + return Task.FromResult[]>([setter]); + } + override protected void SetCheckedToMatch(bool cur) + { + base.SetCheckedToMatch(cur); + if (MenuBtnCmd.instance != null) + MenuBtnCmd.instance.Command.Checked = Native.IsChecked(); //keep menu in sync with us + } + + public JustMyCodeCmd() : base(true, false) + { + this.settingStoreSetter = new("Debugger", "JustMyCode"); + this.dteSetter = new("Debugging", "General", "EnableJustMyCode"); + } + + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Commands/NativeDebuggingToggleCmd.cs b/Tvl.VisualStudio.JustMyCodeToggle/Commands/NativeDebuggingToggleCmd.cs new file mode 100644 index 0000000..d0115c0 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Commands/NativeDebuggingToggleCmd.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Tvl.VisualStudio.JustMyCodeToggle.Managers; +namespace Tvl.VisualStudio.JustMyCodeToggle.Commands +{ + [Command(PackageGuids.guidJustMyCodeTogglePackageCmdSetString,PackageIds.JMCNativeCodeBtn)] + internal class OleNativeDebuggingBtn : OurOLEButton { + + protected override Task ExecuteAsync(OleMenuCmdEventArgs e) + { + return base.ExecuteAsync(e); + } + } + internal class NativeDebuggingCmd : ToggleSettingDynamicSetterCmd + { + private StartupProfileSetter profileSetter; + private BuildStoreSetter storageSetter; + + public NativeDebuggingCmd() : base(true, false) + { + this.profileSetter = new("nativeDebugging"); + this.storageSetter = new("EnableUnmanagedDebugging"); + profileSetter.StartManager.StartupProjectChanged += (_, _) => this.SyncCheckedToCurVal(); + } + protected override Task[]> GetSetters() + { + ISetSettingInterface setter = profileSetter.SupportsLaunchProfiles ? profileSetter : storageSetter; + return Task.FromResult[]>([setter]); + } + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Commands/OurBaseSettingCmd.cs b/Tvl.VisualStudio.JustMyCodeToggle/Commands/OurBaseSettingCmd.cs new file mode 100644 index 0000000..1610594 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Commands/OurBaseSettingCmd.cs @@ -0,0 +1,194 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.Commands; +using Microsoft.VisualStudio.Shell; +using Tvl.VisualStudio.JustMyCodeToggle.Managers; + + +namespace Tvl.VisualStudio.JustMyCodeToggle.Commands +{ + + + /// + /// Allows using multiple/dynamic setters IE depending on VS version, or if project supports CPS or not, if multiple setters are returned from GetSetters the first is used for GetSetting + /// + /// + /// + internal abstract class ToggleSettingDynamicSetterCmd : ToggleSettingCmd, ISetSettingInterface + { + + + + protected delegate SETTING_TYPE TransformValDelegate(ISetSettingInterface setter, SETTING_TYPE val, bool forGetSetting); + protected ToggleSettingDynamicSetterCmd(SETTING_TYPE enabled_val, SETTING_TYPE disabled_val, Func, SETTING_TYPE, SETTING_TYPE> TransformVal = null) : base(null, enabled_val, disabled_val) + { + this.setter = this; + this.TransformVal = TransformVal; + } + + protected abstract Task[]> GetSetters(); + public Func, SETTING_TYPE, SETTING_TYPE> TransformVal { get; } + + public async Task GetSetting() + { + var setters = await GetSetters(); + var ret = await setters[0].GetSetting(); + if (TransformVal != null) + ret = TransformVal(setters[0], ret); + return ret; + } + + public async Task SetSetting(SETTING_TYPE val) + { + var setters = await GetSetters(); + foreach (var setter in setters) + { + var toSet = val; + if (TransformVal != null) + toSet = TransformVal(setter, val); + await setter.SetSetting(toSet); + + } + } + } + public interface IOurCommand + { + Task Execute(); + Task Initialize(); + IButtonNative Native { set; } + } + public interface IButtonNative + { + IOurCommand OurCommand { set; } + bool IsChecked(); + void SetChecked(bool isChecked); + void SetEnabled(bool isEnabled); + } + internal abstract class OurExtensibilityToggleButton : ToggleCommand, IButtonNative where CMD_CLASS : class, IOurCommand, new() + { + public IOurCommand OurCommand { set; get; } + public OurExtensibilityToggleButton() + { + } + + public override async Task InitializeAsync(CancellationToken cancellationToken) + { + OurCommand = new CMD_CLASS(); + OurCommand.Native = this; + await OurCommand?.Initialize(); + await base.InitializeAsync(cancellationToken); + } + + public void SetChecked(bool isChecked) => this.IsChecked = isChecked; + + public void SetEnabled(bool isEnabled) => this.SetEnabledState(isEnabled); + + bool IButtonNative.IsChecked() => this.IsChecked; + + override public async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + await (OurCommand?.Execute() ?? Task.CompletedTask); + // base is abstract for this + } + } + internal abstract class OurOLEButton : BaseCommand, IButtonNative where OUR_CLASS : class, new() + where CMD_CLASS : class, IOurCommand, new() + { + public OurOLEButton() + { + OurCommand = new CMD_CLASS(); + OurCommand.Native = this; + } + public IOurCommand OurCommand { protected get; set; } + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + await (OurCommand?.Execute() ?? Task.CompletedTask); + await base.ExecuteAsync(e); + } + public bool IsChecked() => this.Command.Checked; + public void SetChecked(bool isChecked) => this.Command.Checked = isChecked; + public void SetEnabled(bool isEnabled) => this.Command.Enabled = isEnabled; + override protected async Task InitializeCompletedAsync() + { + await (OurCommand?.Initialize() ?? Task.CompletedTask); + await base.InitializeCompletedAsync(); + } + } + internal abstract class ToggleSettingCmd : IOurCommand + { + + + protected virtual ISetSettingInterface setter { get; set; } + protected virtual SETTING_TYPE enabled_val { get; set; } + protected virtual SETTING_TYPE disabled_val { get; set; } + public IButtonNative Native + { + set + { + field = value; + //field.OurCommand = this; + } + protected get; + } + + protected virtual async Task SyncCheckedToCurVal() + + { + try + { + this.SetCheckedToMatch(await setter.GetSetting()); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"JMC ToggleSettingCmd {this.GetType()} StartProjChanged exception: {ex}"); + } + } + + public ToggleSettingCmd(ISetSettingInterface setter, SETTING_TYPE enabled_val, SETTING_TYPE disabled_val) + { + this.setter = setter; + this.enabled_val = enabled_val; + this.disabled_val = disabled_val; + } + + public virtual async Task Execute() + { + try + { + + var cur = await setter.GetSetting(); + cur = cur.Equals(disabled_val) ? enabled_val : disabled_val; + await setter.SetSetting(cur); + SetCheckedToMatch(cur); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"JMC ToggleSettingCmd {this.GetType()} ExecuteAsync exception: {ex}"); + + } + + + } + protected virtual void SetCheckedToMatch(SETTING_TYPE cur) + { + Native.SetChecked(!cur.Equals(disabled_val)); + } + public async Task Initialize() + { + try + { + await SyncCheckedToCurVal(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"JMC ToggleSettingCmd InitializeCompletedAsync exception: {ex}"); + + } + + } + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/ExtensibilityExtensionEntrypoint.cs b/Tvl.VisualStudio.JustMyCodeToggle/ExtensibilityExtensionEntrypoint.cs new file mode 100644 index 0000000..140d36c --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/ExtensibilityExtensionEntrypoint.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.Extensibility; +using System.Diagnostics; +using Tvl.VisualStudio.JustMyCodeToggle.Managers; + +namespace Tvl.VisualStudio.JustMyCodeToggle +{ + /// + /// Extension entrypoint for the VisualStudio.Extensibility extension. + /// + [VisualStudioContribution] + internal class ExtensibilityExtensionEntrypoint : Extension + { + //public ExtensibilityExtensionEntrypoint(){ + + // Debug.WriteLine("Inited here..."); + // } + + /// + public override ExtensionConfiguration ExtensionConfiguration => new() + { + RequiresInProcessHosting = true, + }; + + + protected override void InitializeServices(IServiceCollection serviceCollection) + { + + Debug.WriteLine("ExtensionEntrypoint InitializeServices called!"); + + base.InitializeServices(serviceCollection); + + + + } + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Helpers.cs b/Tvl.VisualStudio.JustMyCodeToggle/Helpers.cs new file mode 100644 index 0000000..415b8ed --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Helpers.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Tvl.VisualStudio.JustMyCodeToggle +{ + + /// + /// Extension methods for Visual Studio project types + /// + public static class ProjectExtensions + { + public static bool IsVS2026 => _isVS2026.Value; + private static Lazy _isVS2026 = new ( () => + { + ThreadHelper.ThrowIfNotOnUIThread(); + try + { + var shell = (IVsShell)Package.GetGlobalService(typeof(SVsShell)); + if (shell != null) + { + shell.GetProperty((int)__VSSPROPID5.VSSPROPID_ReleaseVersion, out object version); + if (version is string versionString) + { + // VS 2026 is version 18.x + if (versionString.StartsWith("18.") || versionString.StartsWith("19.")) + { + return true; + } + } + } + } + catch + { + // If we can't detect version, assume older version + } + return false; + }); + public static T GetTypedService(this AsyncPackage package) where T : class => package.GetService(); + public static async Task GetDTEProjectFromIvsProject(this IVsProject proj) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + ((IVsHierarchy)proj).GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out object prop); + return prop as EnvDTE.Project; + } + + public static async Task GetCurrentConfigurationName(this IVsProject project) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var hProject = project as IVsHierarchy; + hProject.GetGuidProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ProjectIDGuid, out var projGuid); + var bm = await VS.Services.GetSolutionBuildManagerAsync() as IVsSolutionBuildManager5; + bm.FindActiveProjectCfgName(ref projGuid, out var currentConfigName); + return currentConfigName; + } + + public static async Task SaveProject(this IVsProject project, bool force) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var solution = await VS.Services.GetSolutionAsync(); + solution.SaveSolutionElement( + force ? (uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave : (uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_SaveIfDirty, + project as IVsHierarchy, + 0); + } + } + + +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggle.cs b/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggle.cs new file mode 100644 index 0000000..dbaab61 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggle.cs @@ -0,0 +1,35 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer 1.0.45 +// Available from https://marketplace.visualstudio.com/items?itemName=MadsKristensen.VsixSynchronizer64 +// +// ------------------------------------------------------------------------------ +namespace Tvl.VisualStudio.JustMyCodeToggle +{ + using System; + + /// + /// Helper class that exposes all GUIDs used across VS Package. + /// + internal sealed partial class PackageGuids + { + public const string guidJustMyCodeTogglePackageString = "c86c0c68-5ff5-43af-8a60-54540cbea3e2"; + public static Guid guidJustMyCodeTogglePackage = new Guid(guidJustMyCodeTogglePackageString); + + public const string guidJustMyCodeTogglePackageCmdSetString = "4e7a4be2-f4dd-4cc1-a142-e23645e43ecd"; + public static Guid guidJustMyCodeTogglePackageCmdSet = new Guid(guidJustMyCodeTogglePackageCmdSetString); + } + + /// + /// Helper class that encapsulates all CommandIDs uses across VS Package. + /// + internal sealed partial class PackageIds + { + public const int JMCToolbarGroup = 0x1101; + public const int JMCMenuController = 0x1102; + public const int JMCNativeCodeBtn = 0x1103; + public const int JMCJustMyCodeBtn = 0x1104; + public const int JMCSymbolLoadBtn = 0x1105; + public const int JMCDisableJitOptmizationsBtn = 0x1107; + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggle.vsct b/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggle.vsct index 38c4037..9368104 100644 --- a/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggle.vsct +++ b/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggle.vsct @@ -1,16 +1,66 @@ - + + - + + + + + + + + TextChanges + DontCache + TextIsAnchorCommand + + Debug.JMCTogglePackageMenu + + + + - + + + + + - + - + - - - + + + + + + + + + + diff --git a/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggleConstants.cs b/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggleConstants.cs deleted file mode 100644 index 34258d9..0000000 --- a/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeToggleConstants.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.JustMyCodeToggle -{ - using Guid = System.Guid; - - internal static class JustMyCodeToggleConstants - { - public const string GuidJustMyCodeTogglePackageString = "73702199-D0C5-4863-8203-99D41B34DD2D"; - - public const string GuidJustMyCodeToggleCommandSetString = "B1317E30-CFDA-47CA-90A0-95E894B150A0"; - public static readonly Guid GuidJustMyCodeToggleCommandSet = new Guid("{" + GuidJustMyCodeToggleCommandSetString + "}"); - - public static readonly int CmdidJustMyCodeToggle = 0x0100; - } -} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeTogglePackage.cs b/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeTogglePackage.cs index 45c13ed..872ec7b 100644 --- a/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeTogglePackage.cs +++ b/Tvl.VisualStudio.JustMyCodeToggle/JustMyCodeTogglePackage.cs @@ -1,92 +1,79 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. namespace Tvl.VisualStudio.JustMyCodeToggle { using System; - using System.ComponentModel.Design; + using System.Runtime.InteropServices; using System.Threading; - using Microsoft; + using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio; + using Microsoft.VisualStudio.Extensibility; + using Microsoft.VisualStudio.Extensibility.Shell; using Microsoft.VisualStudio.Shell; - using IMenuCommandService = System.ComponentModel.Design.IMenuCommandService; + using Microsoft.VisualStudio.Shell.Interop; + using Tvl.VisualStudio.JustMyCodeToggle.Managers; using Task = System.Threading.Tasks.Task; - [Guid(JustMyCodeToggleConstants.GuidJustMyCodeTogglePackageString)] + [Guid(PackageGuids.guidJustMyCodeTogglePackageString)] [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] - [ProvideMenuResource(1000, 1)] + [InstalledProductRegistration(Vsix.Name, Vsix.Description, Vsix.Version)] [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_string, PackageAutoLoadFlags.BackgroundLoad)] - internal class JustMyCodeTogglePackage : AsyncPackage + [ProvideMenuResource("Menus.ctmenu", 1)] + internal class JustMyCodeTogglePackage : ToolkitPackage { - private readonly OleMenuCommand _command; - public JustMyCodeTogglePackage() - { - var id = new CommandID(JustMyCodeToggleConstants.GuidJustMyCodeToggleCommandSet, JustMyCodeToggleConstants.CmdidJustMyCodeToggle); - EventHandler invokeHandler = HandleInvokeJustMyCodeToggle; - EventHandler changeHandler = HandleChangeJustMyCodeToggle; - EventHandler beforeQueryStatus = HandleBeforeQueryStatusJustMyCodeToggle; - _command = new OleMenuCommand(invokeHandler, changeHandler, beforeQueryStatus, id); - } + protected ProjectEventHandler ProjectEventHandler; - public EnvDTE.DTE ApplicationObject - { - get - { - return GetService(typeof(EnvDTE._DTE)) as EnvDTE.DTE; - } - } + private StartupProjectManager _startupProjectManager; + internal T RegisterService(T service){ + AddService(typeof(T),(_,_,_) => Task.FromResult(service),promote:true); + return service; + } + public JustMyCodeTogglePackage(){ + instance = this; + } + public static JustMyCodeTogglePackage instance; protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { - await base.InitializeAsync(cancellationToken, progress); + //await Helpers.Init(); + // Initialize managers + _startupProjectManager = RegisterService(new StartupProjectManager()); + RegisterService(new LaunchProfileManager(_startupProjectManager)); + RegisterService(new SettingsStoreManager((IVsSettingsManager)await VS.Services.GetSettingsManagerAsync())); + RegisterService(new DteManager()); + RegisterService(new DebuggerServiceManager()); await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await this.RegisterCommandsAsync(); + await base.InitializeAsync(cancellationToken, progress); + + + var reqType = typeof(VisualStudioExtensibility); + var asm = reqType.Assembly; + var globalSolutionSvc = await VS.GetServiceAsync(); + //VisualStudioExtensibility extensibility = await this.GetServiceAsync(); + //await extensibility.Shell().ShowPromptAsync("Hello from in-proc", PromptOptions.OK, cancellationToken); - var mcs = (IMenuCommandService)await GetServiceAsync(typeof(IMenuCommandService)); - Assumes.Present(mcs); - mcs.AddCommand(_command); - } - private void HandleInvokeJustMyCodeToggle(object sender, EventArgs e) - { - try - { - EnvDTE.Property enableJustMyCode = ApplicationObject.get_Properties("Debugging", "General").Item("EnableJustMyCode"); - if (enableJustMyCode.Value is bool value) - { - enableJustMyCode.Value = !value; - } - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - } - } + ProjectEventHandler = new(_startupProjectManager); + globalSolutionSvc.AdviseSolutionEvents(ProjectEventHandler, out _); + var monitor = await VS.GetServiceAsync(); + monitor.AdviseSelectionEvents(ProjectEventHandler, out _); + var bm = await VS.Services.GetSolutionBuildManagerAsync(); + bm.AdviseUpdateSolutionEvents(ProjectEventHandler, out _); - private void HandleChangeJustMyCodeToggle(object sender, EventArgs e) - { + AfterLoad(); } - private void HandleBeforeQueryStatusJustMyCodeToggle(object sender, EventArgs e) - { - try - { - _command.Supported = true; - - EnvDTE.Property enableJustMyCode = ApplicationObject.get_Properties("Debugging", "General").Item("EnableJustMyCode"); - if (enableJustMyCode.Value is bool value) - { - _command.Checked = value; - } - _command.Enabled = true; - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - _command.Supported = false; - _command.Enabled = false; - } + private async void AfterLoad() + { + await Task.Delay(2000); + _startupProjectManager.CheckStartupProjectChanged(true); } + } } diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Managers/DebuggerServiceManager.cs b/Tvl.VisualStudio.JustMyCodeToggle/Managers/DebuggerServiceManager.cs new file mode 100644 index 0000000..2f50b41 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Managers/DebuggerServiceManager.cs @@ -0,0 +1,70 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using EnvDTE; +using EnvDTE100; +using EnvDTE80; +using EnvDTE90; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Managers +{ + /// + /// Not currently used, hope was for Debugger5 interface to be able to set the loading state for VS2026 but it did not work, no error just no change. + /// + public class DebuggerServiceManager + { + + public bool GetLoadAllModules() + { + try + { + return GetDebugger5().OnlyLoadSymbolsManually == false; + } + catch (Exception ex) + { + Debug.WriteLine($"An error occurred while getting symbol load mode: {ex.Message}"); + } + return false; + } + + protected Debugger5 GetDebugger5() + { + var dte = Package.GetGlobalService(typeof(DTE)) as DTE2; + + if (dte == null) + throw new Exception("DTE2 service not found."); + + var debugger = dte.Debugger as Debugger5; + if (debugger == null) + throw new Exception("Debugger5 interface not available."); + return debugger; + } + + protected DTE2 GetDTE2() + { + var dte = Package.GetGlobalService(typeof(DTE)) as DTE2; + if (dte == null) + throw new Exception("DTE2 service not found."); + return dte; + } + + public async Task SetLoadAllModulesAsync(bool loadAllModulesUnlessExcluded) + { + try + { + var debugger = GetDebugger5(); + bool onlyLoadSymbolsManually = !loadAllModulesUnlessExcluded; + //ThreadHelper.ThrowIfNotOnUIThread(); + + debugger.SetSymbolSettings(debugger.SymbolPath, debugger.SymbolPathState, debugger.SymbolCachePath, onlyLoadSymbolsManually, true); + } + catch (Exception ex) + { + Debug.WriteLine($"An error occurred while setting symbol load mode: {ex.Message}"); + } + } + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Managers/DteManager.cs b/Tvl.VisualStudio.JustMyCodeToggle/Managers/DteManager.cs new file mode 100644 index 0000000..3472de4 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Managers/DteManager.cs @@ -0,0 +1,151 @@ +using System; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Shell; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Managers +{ + + /// + /// Manages DTE-based settings access for Visual Studio 2026+ + /// + public class DteManager + { + public T GetDteSetting(string category, string page, string propertyName) + { + try + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE; + var props = dte.Properties[category, page]; + if (props != null) + { + var prop = props.Item(propertyName); + if (prop != null) + { + var value = prop.Value; + + // Convert to requested type + if (typeof(T) == typeof(int) && value is bool) + { + return (T)(object)(((bool)value) ? 1 : 0); + } + else if (typeof(T) == typeof(bool) && value is int) + { + return (T)(object)((int)value == 1); + } + return (T)value; + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"GetDteSetting failed: {ex.Message}"); + } + return default; + } + + public void SetDteSetting(string category, string page, string propertyName, T value) + { + try + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE; + var props = dte.Properties[category, page]; + + if (props != null) + { + var prop = props.Item(propertyName); + if (prop != null) + { + + + // Set through DTE - this automatically updates settings.json and notifies all VS components + prop.Value = value; + + System.Diagnostics.Debug.WriteLine($"{propertyName} setting changed via DTE to: {value}"); + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"SetDteSetting failed: {ex.Message}"); + } + } + protected async Task GetDteAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE; + return dte; + } + public async Task ExecuteCommandAsync(string commandName, string args = "") + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dte = await GetDteAsync(); + dte.ExecuteCommand(commandName, args); + } + + + public async void DumpAllSettings() + { + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE.DTE; + + try + { + System.Diagnostics.Debug.WriteLine("=== All DTE Property Pages ==="); + + // DTE doesn't provide enumeration of all categories/pages, so try known ones + var knownPages = new System.Collections.Generic.Dictionary +{ + { "Debugging", new[] { "General", "Native","EditAndContinue","JustInTime" } }, + {"Debugger",["General","JIT","Symbols","VisibilityCmdUIContexts","symbols","symbols.load",] }, + { "Environment", new[] { "General", "Documents", "Keyboard", "Startup","Profiles","AutoRecover", "TaskList", "TabsAndWindows", "ProjectsandSolution", "FindAndReplace", "RoamingSettings", "WebBrowser", "Import and Export Settings", "ProductUpdates", "JavaScript Specific", "AddinMacrosSecurity", "ExtensionManager" } }, + // { "TextEditor", new[] { "AllLanguages", "C#", "C/C++", "Basic", "F#", "XML", "Basic-Specific", "C/C++ Specific", "CSS", "CSharp", "CSharp-Specific", "CoffeeScript", "General", "HTML", "HTML Specific", "HTMLX", "JSON", "JavaScript", "JavaScript Specific", "LESS", "PlainText", "ResJSON Resource", "SCSS", "SQL Server Tools", "T-SQL90", "TypeScript", "TypeScript Specific", "XAML", "XOML", "WebForms", "WebForms Specific", "FSharp", "Razor", "Rest", "CSS Specific", "JScript Specific", "Jade", "YAML" } }, + { "Projects", new[] { "General", "VBDefaults", "VCGeneral" } }, // , "Build and Run" works but causes a fatal crash + { "WindowsFormsDesigner", new[] { "General" } }, + { "Source Control", ["General" ]}, + { "XAML Designer", new[] { "Artboard", "General" } }, + { "SQL Server Tools", new[] { "Database Errors and Warnings", "General", "Online Editing" } }, + { "PkgdefLanguage", new[] { "Advanced" } }, + {"Search",["CommandScopes"] }, +}; + + foreach (var category in knownPages) + { + System.Diagnostics.Debug.WriteLine($"\n[Category: {category.Key}]"); + foreach (var page in category.Value) + { + try + { + var categoryProps = dte.Properties[category.Key, page]; + if (categoryProps != null) + { + System.Diagnostics.Debug.WriteLine($" [Page: {page}]"); + foreach (EnvDTE.Property prop in categoryProps) + { + try + { + System.Diagnostics.Debug.WriteLine($" {prop.Name} = {prop.Value}"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($" {prop.Name} = "); + } + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($" [Page: {page}] - Not accessible: {ex.Message}"); + } + } + } + } + catch { } + + } + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Managers/ExtensibilitySettingManager.cs b/Tvl.VisualStudio.JustMyCodeToggle/Managers/ExtensibilitySettingManager.cs new file mode 100644 index 0000000..b261f7d --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Managers/ExtensibilitySettingManager.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.Settings; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Managers +{ + internal class ExtensibilitySettingManager + { + public ExtensibilitySettingManager(VisualStudioExtensibility extensibility) + { + this.Extensibility = extensibility; + Inited = true; + } + + public static bool Inited { get; private set; } + public VisualStudioExtensibility Extensibility { get; } + + + public async Task GetSetting(string propertyName) + { + SettingIdentifier ident = propertyName; + + + var ret = await Extensibility.Settings().ReadEffectiveValueAsync(ident, CancellationToken.None); + + return ret.Value; + } + public async Task SetSetting(string propertyName, T val) + { + SettingIdentifier ident = propertyName; + await this.Extensibility.Settings().WriteAsync(batch => + { + batch.WriteSetting(ident, val); + }, $"JMC Updating {ident}", CancellationToken.None); + + } + public async Task WatchSetting(string propertyName, Action> onChange) + { + SettingIdentifier ident = propertyName; + return await Extensibility.Settings().SubscribeAsync(ident, default, changeHandler: onChange); + } + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Managers/LaunchProfileManager.cs b/Tvl.VisualStudio.JustMyCodeToggle/Managers/LaunchProfileManager.cs new file mode 100644 index 0000000..e955b9e --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Managers/LaunchProfileManager.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Debug; +using Microsoft.VisualStudio.ProjectSystem.VS; +using Microsoft.VisualStudio.Shell; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Managers +{ + /// + /// Handles launch profile configuration and management + /// + public class LaunchProfileManager + { + private readonly StartupProjectManager _startupProjectManager; + + public LaunchProfileManager(StartupProjectManager startupProjectManager) + { + _startupProjectManager = startupProjectManager; + } + public async Task GetProfileEnvVar(string varName) + { + return (await GetLaunchProfile())?.EnvironmentVariables.FirstOrDefault(a => a.Key == varName).Value; + } + public bool SupportsLaunchProfiles => _startupProjectManager.ActiveProjectSupportsCPSProfiles; + public Task UpdateLaunchProfileENVVars(bool isDelete, params KeyValuePair[] vars) + { + if (! SupportsLaunchProfiles) + return Task.CompletedTask; + + return TryModifyActiveProfile((profile) => + { + if (isDelete) + { + foreach (var var in vars) + profile.EnvironmentVariables.Remove(var.Key); + } + else + { + foreach (var var in vars) + profile.EnvironmentVariables[var.Key] = var.Value; + } + }); + } + + public Task UpdateLaunchProfileSetting(string key, object value) + { + return TryModifyActiveProfile((profile) => profile.OtherSettings[key] = value); + } + + private async Task TryModifyActiveProfile(Action onEditProfile) + { + var launchProvider = await GetLaunchProvider(); + var activeProfile = launchProvider.ActiveProfile?.Name; + if (activeProfile == null) + return; + await launchProvider.TryUpdateProfileAsync(activeProfile, onEditProfile); + } + + private async Task GetLaunchProvider() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var proj = await _startupProjectManager.GetStartupProject(); + var provider = proj.AsUnconfiguredProject()?.Services.ActiveConfiguredProjectProvider?.ActiveConfiguredProject?.Services.ExportProvider; + return provider?.GetService(); + } + + public async Task GetLaunchProfile() + { + var launchProvider = await GetLaunchProvider(); + return launchProvider?.ActiveProfile as ILaunchProfile2; + } + } + + +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Managers/ProjectEventHandler.cs b/Tvl.VisualStudio.JustMyCodeToggle/Managers/ProjectEventHandler.cs new file mode 100644 index 0000000..238d67d --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Managers/ProjectEventHandler.cs @@ -0,0 +1,63 @@ +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Managers +{ + internal class ProjectEventHandler(StartupProjectManager _startupProjectManager) : IVsUpdateSolutionEvents, IVsSolutionEvents, IVsSelectionEvents + { + + public int UpdateSolution_Begin(ref int pfCancelUpdate) => VSConstants.S_OK; + + public int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) => VSConstants.S_OK; + + + public int UpdateSolution_StartUpdate(ref int pfCancelUpdate) => VSConstants.S_OK; + + public int UpdateSolution_Cancel() => VSConstants.S_OK; + + public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + _startupProjectManager.CheckStartupProjectChanged(); + return VSConstants.S_OK; + } + + public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) => VSConstants.S_OK; + + public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) => VSConstants.S_OK; + + public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) => VSConstants.S_OK; + + public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) + { + _startupProjectManager.CheckStartupProjectChanged(true); + return VSConstants.S_OK; + } + + public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) => VSConstants.S_OK; + + public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) => VSConstants.S_OK; + + public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) + { + _startupProjectManager.CheckStartupProjectChanged(); return VSConstants.S_OK; + } + + public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) => VSConstants.S_OK; + + + public int OnBeforeCloseSolution(object pUnkReserved) => VSConstants.S_OK; + + public int OnAfterCloseSolution(object pUnkReserved) => VSConstants.S_OK; + + public int OnSelectionChanged(IVsHierarchy pHierOld, uint itemidOld, IVsMultiItemSelect pMISOld, ISelectionContainer pSCOld, IVsHierarchy pHierNew, uint itemidNew, IVsMultiItemSelect pMISNew, ISelectionContainer pSCNew) => VSConstants.S_OK; + public int OnElementValueChanged(uint elementid, object varValueOld, object varValueNew) + { + if (elementid == (uint)VSConstants.VSSELELEMID.SEID_StartupProject) + _startupProjectManager.CheckStartupProjectChanged(true); + return VSConstants.S_OK; + } + + public int OnCmdUIContextChanged(uint dwCmdUICookie, int fActive) => VSConstants.S_OK; + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Managers/SettingsStoreManager.cs b/Tvl.VisualStudio.JustMyCodeToggle/Managers/SettingsStoreManager.cs new file mode 100644 index 0000000..2aa8842 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Managers/SettingsStoreManager.cs @@ -0,0 +1,93 @@ +using System; +using System.ComponentModel; +using Microsoft.VisualStudio.Settings; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Managers +{ + /// + /// Manages Visual Studio settings storage and retrieval + /// + public class SettingsStoreManager + { + private readonly IVsSettingsManager _settingsManager; + + public SettingsStoreManager(IVsSettingsManager settingsManager) + { + _settingsManager = settingsManager; + } + + + + public T GetSetting(string collectionPath, string propertyName) + { + // Legacy settings store for VS 2022 and earlier + var store = GetSettingsStore(); + object ret = null; + int res = 0; + + if (typeof(T) == typeof(string)) + { + res = store.GetString(collectionPath, propertyName, out var val); + ret = val; + } + else if (typeof(T) == typeof(int)) + { + res = store.GetInt(collectionPath, propertyName, out var val); + ret = val; + } + else if (typeof(T) == typeof(bool)) + { + res = store.GetBool(collectionPath, propertyName, out var val); + ret = val == 1; + } + else + throw new NotImplementedException($"Type {typeof(T)} is not supported for settings"); + + return res != 0 ? default : (T)ret; + } + + public void SetSetting(string collectionPath, string propertyName, T value) + { + var store = (IVsWritableSettingsStore)GetSettingsStore(true); + int res = 0; + + switch (value) + { + case int v: + res = store.SetInt(collectionPath, propertyName, v); + break; + case bool v: + res = store.SetBool(collectionPath, propertyName, v ? 1 : 0); + break; + case string v: + res = store.SetString(collectionPath, propertyName, v); + break; + default: + throw new NotImplementedException($"Type {typeof(T)} is not supported for settings"); + } + + if (res != 0) + throw new Win32Exception(); + } + + private IVsSettingsStore GetSettingsStore(bool writable = false) + { + IVsSettingsStore ret; + int result; + + if (!writable) + result = _settingsManager.GetReadOnlySettingsStore((uint)SettingsScope.UserSettings, out ret); + else + { + result = _settingsManager.GetWritableSettingsStore((uint)SettingsScope.UserSettings, out var wret); + ret = wret; + } + + if (result != 0) + throw new Win32Exception(); + return ret; + } + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Managers/StartupProjectManager.cs b/Tvl.VisualStudio.JustMyCodeToggle/Managers/StartupProjectManager.cs new file mode 100644 index 0000000..39697b1 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/Managers/StartupProjectManager.cs @@ -0,0 +1,79 @@ +using System; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Tvl.VisualStudio.JustMyCodeToggle.Managers +{ + /// + /// Manages startup project state and notifications + /// + public class StartupProjectManager + { + + public bool ActiveProjectSupportsCPSProfiles + { + get; private set + { + if (field == value) + return; + field = value; + ActiveProjectSupportsCPSProfilesChanged?.Invoke(null, EventArgs.Empty); + } + } + public event EventHandler StartupProjectChanged; + public event EventHandler ActiveProjectSupportsCPSProfilesChanged; + private string lastStartupProject; + public bool HasStartupProject => !string.IsNullOrWhiteSpace(lastStartupProject); + + public async Task GetStartupProject() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + try + { + var bm = await VS.Services.GetSolutionBuildManagerAsync(); + if (bm?.get_StartupProject(out var hstartupProject) == VSConstants.S_OK && hstartupProject != null) + return (IVsProject)hstartupProject; + } + catch { } // Some project types can throw with get_startupproject + return null; + } + + public async void CheckStartupProjectChanged(bool forceEvent = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "") + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + try + { + await Task.Delay(50); // Make sure it has actually updated first + var project = await GetStartupProject(); + var hProject = project as IVsHierarchy; + + string startupProject = null; + if (project != null) + { + if (project.GetMkDocument((uint)VSConstants.VSITEMID.Root, out startupProject) != VSConstants.S_OK) + if (hProject.GetCanonicalName((uint)VSConstants.VSITEMID.Root, out startupProject) != VSConstants.S_OK) + startupProject = null; + } + + var oldVal = lastStartupProject; + lastStartupProject = startupProject; + ActiveProjectSupportsCPSProfiles = hProject?.IsCapabilityMatch("LaunchProfiles") == true; + + if (oldVal != lastStartupProject || forceEvent) + { + ActiveProjectSupportsCPSProfilesChanged?.Invoke(null, EventArgs.Empty); + StartupProjectChanged?.Invoke(null, EventArgs.Empty); + } + } + catch + { + ActiveProjectSupportsCPSProfiles = false; + } + } + } + + +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Properties/AssemblyInfo.cs b/Tvl.VisualStudio.JustMyCodeToggle/Properties/AssemblyInfo.cs deleted file mode 100644 index e16d031..0000000 --- a/Tvl.VisualStudio.JustMyCodeToggle/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -using System; -using System.Runtime.InteropServices; - -[assembly: CLSCompliant(false)] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/Tvl.VisualStudio.JustMyCodeToggle/Tvl.VisualStudio.JustMyCodeToggle.csproj b/Tvl.VisualStudio.JustMyCodeToggle/Tvl.VisualStudio.JustMyCodeToggle.csproj index 729399a..2222d19 100644 --- a/Tvl.VisualStudio.JustMyCodeToggle/Tvl.VisualStudio.JustMyCodeToggle.csproj +++ b/Tvl.VisualStudio.JustMyCodeToggle/Tvl.VisualStudio.JustMyCodeToggle.csproj @@ -1,94 +1,110 @@ - - - - - - net45 - - Adds the Just My Code command button to Visual Studio. - Tunnel Vision Laboratories, LLC - Copyright © Sam Harwell 2017 - 1.3.0.0 - 1.3.0.0 - 1.3.0-dev - - true - true - true - false - false - true - false - - true - SharedKey.snk - - - - - False - - - true - - - - full - true - - - - pdbonly - true - - - - - true - $(MSBuildThisFileDirectory)..\JustMyCodeToggle.ruleset - - - - - - - - - - - - - - - - - - - - - - - LICENSE.txt - true - - - - - - 1000 - Designer - - - - - - VSPackage.resources - true - - - - - - - + + + net480 + + Properties + Adds the Just My Code command button to Visual Studio. + Tunnel Vision Laboratories, LLC + Copyright © Sam Harwell 2017 + 1.4.1.0 + preview + 1.4.1.0 + 1.4.1-dev + true + true + true + false + false + true + true + obj + obj/int + True + Debug;Release + true + + + $(MSBuildThisFileDirectory)..\JustMyCodeToggle.ruleset + + + pdbonly + true + + + + False + + + true + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + compile; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + Designer + VsixManifestGenerator + + + + + LICENSE.txt + true + + + VsixManifestGenerator + source.extension.cs + + + + + Never + true + + + Menus.ctmenu + + + + + True + True + source.extension.vsixmanifest + + + + + + $(GetVsixSourceItemsDependsOn);IncludeNuGetResolvedAssets + + + + + + diff --git a/Tvl.VisualStudio.JustMyCodeToggle/VSPackage.resx b/Tvl.VisualStudio.JustMyCodeToggle/VSPackage.resx deleted file mode 100644 index 41903cb..0000000 --- a/Tvl.VisualStudio.JustMyCodeToggle/VSPackage.resx +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Tvl.VisualStudio.JustMyCodeToggle/source.extension.cs b/Tvl.VisualStudio.JustMyCodeToggle/source.extension.cs new file mode 100644 index 0000000..395aeb4 --- /dev/null +++ b/Tvl.VisualStudio.JustMyCodeToggle/source.extension.cs @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer 1.0.45 +// Available from https://marketplace.visualstudio.com/items?itemName=MadsKristensen.VsixSynchronizer64 +// +// ------------------------------------------------------------------------------ +namespace Tvl.VisualStudio.JustMyCodeToggle +{ + internal sealed partial class Vsix + { + public const string Id = "Tvl.VisualStudio.JustMyCodeToggle.0E2F0645-E87E-433C-B82B-C89EAE52A0FD"; + public const string Name = "Just My Code Toggle"; + public const string Description = @"Adds the Just My Code command button to Visual Studio."; + public const string Language = "en-US"; + public const string Version = "1.4.5"; + public const string Author = "Sam Harwell"; + public const string Tags = "debugging, just my code"; + public const bool IsPreview = false; + } +} diff --git a/Tvl.VisualStudio.JustMyCodeToggle/source.extension.vsixmanifest b/Tvl.VisualStudio.JustMyCodeToggle/source.extension.vsixmanifest index 80a4a5e..fe01fed 100644 --- a/Tvl.VisualStudio.JustMyCodeToggle/source.extension.vsixmanifest +++ b/Tvl.VisualStudio.JustMyCodeToggle/source.extension.vsixmanifest @@ -1,25 +1,30 @@ - + - - - Just My Code Toggle - Adds the Just My Code command button to Visual Studio. - https://github.com/tunnelvisionlabs/JustMyCodeToggle - LICENSE.txt - - - https://github.com/tunnelvisionlabs/JustMyCodeToggle/releases/latest - - - debugging, just my code - - - - - - - - - - + + + Just My Code Toggle + Adds the Just My Code command button to Visual Studio. + https://github.com/tunnelvisionlabs/JustMyCodeToggle + LICENSE.txt + + + https://github.com/tunnelvisionlabs/JustMyCodeToggle/releases/latest + + + debugging, just my code + + + + amd64 + + + + + + + + + + + diff --git a/doc/toolbar.png b/doc/toolbar.png index 9dc6545..7bee6d0 100644 Binary files a/doc/toolbar.png and b/doc/toolbar.png differ diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..aaae437 --- /dev/null +++ b/notes.txt @@ -0,0 +1,3 @@ +https://github.com/madskristensen/Tweakster/blob/master/src/TweaksterShared/Tweaks/Debugger/JustMyCode.cs an additional way to set it next time we can repro it not working properly + +native Code Debugging with .net sdk style newer project launches does not detect when enabled properly. \ No newline at end of file