-
Notifications
You must be signed in to change notification settings - Fork 364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make all features switchable #525
Conversation
Thank you for the pull request, and the explanation of the changes. There's a lot to go over and I need to think through some of the implications so please be patient with me! :) |
Sure, I'm on holiday these days with limited internet access (phone, no PC), I'm not familiar with all code, maybe some places could be optimized. Feel free to ask me to do that or help me do that. |
Hi, I'm back, did you have chance to review these, any question for me? |
Unfortunately no I have not yet, but it is still on my radar! |
Thanks again for your patience, I'm starting to look into it. It's really interesting and I like what you have done! :) |
No problem, as I'm not very good at English, feel free to correct any nams/strings in the code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you again for your hard work on this, I really appreciate it and I think the users will really appreciate it as well. I have a number of questions and suggestions, please let me know if there's anything I can help clarify or we can figure out together!
Also, as a heads up there is another pull request for localization going on (i.e. to support Chinese menus/labels/etc.). I think there will be a fair amount of conflict between the changes on this pull request and that one so that may be something we have to work through later (that one is further along).
int callbackTimes = 0; | ||
monitor.Watch(s => s.Feature_CleanupAllCode, _ => callbackTimes++); | ||
|
||
Assert.AreEqual(/*Initial Call Times*/1, callbackTimes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is /Initial Call Times/?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we call monitor.Watch(setting, callback)
, the Watch
method will invoke the callback at once to initialize the callback based on existing setting value for initialization.
@@ -16,6 +16,12 @@ | |||
<setting name="Reorganizing_RunAtStartOfCleanup" serializeAs="String"> | |||
<value>False</value> | |||
</setting> | |||
<setting name="Feature_CleanupActiveCode" serializeAs="String"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these features added here, but not all the other ones? I think perhaps they could be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I see you force auto cleanup on file save for this repo, this feature requires Feature_CleanupActiveCode
and Feature_SettingCleanupOnSave
features on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I see, so you're trying to ensure those features are present in case someone normally has them disabled in their user-level settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
CodeMaid/Helpers/SettingMonitor.cs
Outdated
|
||
namespace SteveCadwallader.CodeMaid.Helpers | ||
{ | ||
public sealed class SettingMonitor<TSetting> where TSetting : ApplicationSettingsBase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to have where
statements indented on the next line please.
Also I think this class could be SettingsMonitor
since its monitoring a set vs. a single item.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's ok, I will pull the update later.
public static void Initialize(CodeMaidPackage package) | ||
{ | ||
Instance = new AboutCommand(package); | ||
Instance.Switch(on: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So are all features switched on initially, and then turned back off? Or why does this default to true vs. referencing settings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made all features are switchable, but not all menus, the 'About' menu will always be there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, I was looking at About as an example of all of them but I can see why it is unique.
@@ -81,6 +80,18 @@ protected virtual void OnExecute() | |||
OutputWindowHelper.DiagnosticWriteLine($"{GetType().Name}.OnExecute invoked"); | |||
} | |||
|
|||
public virtual void Switch(bool on) | |||
{ | |||
if (on && Package.MenuCommandService.FindCommand(CommandID) == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is FindCommand needed before AddCommand, but not before RemoveCommand? If AddCommand/RemoveCommand will gracefully fail if it doesn't exist then its probably redundant. If it won't gracefully fail then it should probably be done in both cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By design the Switch
method should be idempotent. In my test, I found the AddCommand
method will fail when add one command twice, while the RemoveCommand
won't fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so it's an optimization in the RemoveCommand to not bother finding the command since it will gracefully fail. Makes sense. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
@@ -24,8 +34,14 @@ internal RunningDocumentTableEventListener(CodeMaidPackage package) | |||
// Create and store a reference to the running document table. | |||
RunningDocumentTable = new RunningDocumentTable(package); | |||
|
|||
// Register with the running document table for events. | |||
EventCookie = RunningDocumentTable.Advise(this); | |||
// This listener services multiple features, watching if any of them switched. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above in the SettingsMonitor
class.. I'm wondering if this couldn't be simplified to just make two watch statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but the code would be more complex I think, for example, if settingA and settingB are related to featureA, and featureA only works when both settings checked, we have to check another setting value(s) in callback code.
monitor.watch(settingA, value=> { if(value && Settings.Default.SettingB){ } })
monitor.watch(settingB, value=> { if(value && Settings.Default.SettingA){ } })
Of course we can define a method for both callback parameter
void callbackmethod()=> if(Settings.Default.settingA && Settings.Default.settingB) {}
monitor.watch(settingA, callbackmethod )
monitor.watch(settingB, callbackmethod )
But another problem, if both settings changed, the callback will be invoked multiple times, so I let the Watch
method accepts mutliple setting parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that makes sense. Thanks for the explanation!
CodeMaid/Integration/ISwitchable.cs
Outdated
@@ -0,0 +1,7 @@ | |||
namespace SteveCadwallader.CodeMaid.Integration | |||
{ | |||
internal interface ISwitchable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ISwitchableFeature might be more obvious how it is intended to be used. Even if another "Switch" concept was introduced we would want it to have its own interface (and perhaps they'd both inherit from this more generic interface).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, I'll pull a request later.
}; | ||
} | ||
|
||
public override string Header => "Feature Switch"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets change this to Features
, and rename the classes accordingly (e.g. FeaturesDataTemplate.xaml
FeaturesViewModel.cs
please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree.
xmlns:local="clr-namespace:SteveCadwallader.CodeMaid.UI.Dialogs.Options.General"> | ||
<DataTemplate DataType="{x:Type local:FeatureViewModel}"> | ||
<StackPanel> | ||
<GroupBox Header="Cleanup"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think perhaps we should reorganize these settings. One way would be to line them up with the Options (e.g. Cleaning, Collapsing, Digging, Finding, Formatting, Reorganizing, Switching, etc.) but they would be pretty fine grained group boxes. Another way would be to line them up by where they are found in the UI (e.g. Main Menu, Context Menus, Tool Windows). Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll take the first suggestion, because a feature can have multiple menus in different context (main menus, context menus...), the second one might not work.
@@ -1,160 +0,0 @@ | |||
using Microsoft.VisualStudio; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this class, how do you know when the user changes their Visual Studio theme (e.g. from Light to Dark) so that Spade can refresh?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use the built-in help class instead, see line 331 of CodeMaidPackage.cs
VSColorTheme.ThemeChanged += _ => ThemeManager.ApplyTheme();
Hi, I commented your questions and committed the code update, feel free to let me know if you have any question. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates, those all look good! I don't think I have any further questions right now. I'll need to play around/test it some more before I can merge, but looking great!
|
||
public static AboutCommand Instance { get; private set; } | ||
|
||
public static void Initialize(CodeMaidPackage package) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for confirming!
@@ -16,6 +16,12 @@ | |||
<setting name="Reorganizing_RunAtStartOfCleanup" serializeAs="String"> | |||
<value>False</value> | |||
</setting> | |||
<setting name="Feature_CleanupActiveCode" serializeAs="String"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I see, so you're trying to ensure those features are present in case someone normally has them disabled in their user-level settings.
@@ -81,6 +80,18 @@ protected virtual void OnExecute() | |||
OutputWindowHelper.DiagnosticWriteLine($"{GetType().Name}.OnExecute invoked"); | |||
} | |||
|
|||
public virtual void Switch(bool on) | |||
{ | |||
if (on && Package.MenuCommandService.FindCommand(CommandID) == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so it's an optimization in the RemoveCommand to not bother finding the command since it will gracefully fail. Makes sense. 👍
@@ -33,7 +43,7 @@ private IVsWindowFrame BuildProgressWindowFrame | |||
{ | |||
get | |||
{ | |||
var buildProgress = Package.BuildProgress; | |||
var buildProgress = Package.BuildProgressForceLoad; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for improving the consistency.
One thing I was wondering is if someone disables a feature at their user settings level, but the solution they're loading has configuration options expecting it to be present.. what happens? I think since the feature isn't loaded it would simply fail to execute, and that's probably fine. I think when you explicitly turned on features inside CodeMaid's own solution settings was an example of overriding features to be enabled at the solution level (ignoring user preferences). Does that sound right and do you have any other thoughts around that? |
It is working fantastically - thank you! I will likely make a few tweaks to the options dialog but this is awesome and I'm really excited to have this improvement to the extension! BIG thank you for your time and contribution! |
This can be tested (by anyone interested) in our CI build here: http://vsixgallery.com/extension/4c82e17d-927e-42d2-8460-b473ac7df316/ |
Thx, It's my pleasure. About the setting you're wondering, if a feature is turned off in user setting but configured in solution level, it will be ignored as a turned off feature means all its related code won't be executed. |
@heku FYI: For the order of the features I came up with a third option where we would organize the commands in the same way they are organized on the main menu. I think this makes the most sense and is more consistent, so I went ahead and made this change. Please let me know if you have any different thoughts! |
@codecadwallader Thank you, most make sense to me, except the Cleanup Selected Code and Cleanup All Code are placed in different group, this might be a little confused, in my opinion, it's better to always put these two items together. Maybe just move the Cleanup Selected Code up to the Cleaning group, or move the Cleanup All Code down to the Solution Explorer group. How do you think? Feel free to disagree my comment if it doesn't make sense. |
@heku Thanks for the feedback, makes sense. Cleanup All Code exists both inside and outside of the Solution Explorer but is more easily found outside of it. I'm moving Cleanup Selected Code up to the Cleaning section per your suggestion. |
Great, I have no problem now. Thank you. |
Hi, I complete this feature #296 finally. I made lots of changes, for simplifying your review, I give you a few explanations here.
OnBeforeQueryStatus
method, to reduce any potential overhead, I remove/cut off/close as many as possible things, like remove menu from package's MenuCommandService (so that VS wont invokeOnBeforeQueryStatus
any more), cut off all related event listeners (so that VS wont go into our event handlers), close its tool window (e.g. Spade, Build Progress). Regarding event listeners, if possible I only cut off the relationship between VS event and event listener.VS Event ---- EventListener ---- EventConsumer
VS Event -X- EventListener ---- EventConsumer
DynamicVisibility
andDefaultInvisible
, so the .vsct file was updated.ISwitchable
is added to indicate a switchable feature. now all commands and event listeners are implemented this interface.SettingMonitor
is added, it's responsible for watching setting(s) and invoke callback(s) when setting(s) changed.ShellAvailable
are removed.All unit tests passed, but I'm not able to run the integration tests, I've selected the IntegrationTests.testsettings file as doc. I tried both VS 15.6 preview 3.0 and 15.5.5, if you know the reason, please teach me.
Please have it reviewed and feel free to let me know if you have any question or concern. As I'm not good at English, I don't write too many comments in code, if this is required, I'll try to add them later.
This PR also completes the #335, which you planned for v10.5.