Migrate command handlers to the new editor commanding#23769
Migrate command handlers to the new editor commanding#23769olegtk merged 1 commit intodotnet:dev15.6-preview3-vs-depsfrom
Conversation
| ITextUndoHistoryRegistry undoRegistry, | ||
| IEditorOperationsFactoryService editorOperations) | ||
| { | ||
| return new AutomaticLineEnderCommandHandler(waitIndicator, undoRegistry, editorOperations); |
There was a problem hiding this comment.
how does wait handling work in the new system?
There was a problem hiding this comment.
- IWaitIndicator/IWaitContext are platformazed and public (need better names though - we end up with IWaitableUIOperationContext kind of name). I'll use old names to make simpler to explain.
- All command handlers are executed under a shared wait indicator/context set up by the editor and can obtain wait context via CommandExecutionContext argument.
- With the wait context command handlers get shared cancellation token.
- Because the wait context is shared between command handlers, I added a concept of "wait context scope". When a command handler needs to affect the shared wait context (allow or disallow cancellation, add its own message or progress) it pushes a new wait context scope and disposes it when it's done. Shared wait context deals with aggregating these scopes into the wait dialog. Cancellability is aggregated in a sticky way though - if one handler pushed a scope to disallow cancellation and didn't just handle the command, it stays disabled even after the handler is done.
There was a problem hiding this comment.
@CyrusNajmabadi I linked the spec in description and did a bunch of improvements. Please take another look when you have time. Thanks!
There was a problem hiding this comment.
@CyrusNajmabadi
Regarding additional 3000 lines: most of them are coming from new localized command handler names in xlf files (25 handlers x 10 lines per new string x number of languages).
Regarding chaining - I'm going to disappoint you but we gave up on pure non-chained approach and ended up with dual model (simple and if needed - chained). See spec for design consideration.
There was a problem hiding this comment.
Regarding chaining - I'm going to disappoint you but we gave up on pure non-chained approach and ended up with dual model (simple and if needed - chained)
That doesn't disappoint me :) I'm not actually surprised given the complexity of some of our chains.
| Assert.True(delegatedToNext); | ||
| Assert.False(state.IsAvailable); | ||
| var state = handler.GetCommandState(new RemoveParametersCommandArgs(textView, textView.TextBuffer)); | ||
| Assert.True(state.IsUndetermined); |
There was a problem hiding this comment.
which did this change from IsAvailable to IsUndetermined?
There was a problem hiding this comment.
This test used to verify that in some condition this handler delegates GetComandState() to the next handler and result is CommandState.Unavailable. After the migration, this handler is now a simple non-chained command handler that has no next handler reference. Instead of delegating to the next, it returns CommandState.Undetermined, which indicates that it should be ignored and next handler should be called. So the test was updated to validate this instead.
| } | ||
|
|
||
| internal override ICommandHandler<TypeCharCommandArgs> CreateCommandHandler(ITextUndoHistoryRegistry undoHistory) | ||
| internal override IChainedCommandHandler<TypeCharCommandArgs> CreateCommandHandler(ITextUndoHistoryRegistry undoHistory) |
There was a problem hiding this comment.
Is there a link to the docs on these types?
There was a problem hiding this comment.
I wish I could just publish the spec on GitHub :( Hopefully next features we will spec that way.
Essentially, the goal was to make simple handling simple and still allow advanced chaining scenarios. So we ended up with 2 ways to implement a command handler:
public interface ICommandHandler<T> : IDiagnosableCommandHandler where T : CommandArgs
{
CommandState GetCommandState(T args);
bool ExecuteCommand(T args, CommandExecutionContext executionContext);
}
and
public interface IChainedCommandHandler<T> : IDiagnosableCommandHandler where T : CommandArgs
{
CommandState GetCommandState(T args, Func<CommandState> nextCommandHandler);
void ExecuteCommand(T args, Action nextCommandHandler, CommandExecutionContext executionContext);
}
IDiagnosableCommandHandler simply adds a DisplayName for diagnostics:
public interface IDiagnosableCommandHandler : ICommandHandler
{
/// <summary>
/// Gets display name of the command handler used to represent it to the user, for
/// example when blaming it for delays or for commanding diagnostics.
/// </summary>
string DisplayName { get; }
}
IChainedCommandHandler works just as before, but ICommandHandler has no access to the next handler so the commanding service takes care of calling its next handler if needed (specifically when GetCommandState() returns CommandState.Undetermined or ExecuteCommand() returns false).
There was a problem hiding this comment.
@olegtk I admit I would have guessed IDiagnosableCommandHandler had ICommandHandler as a base type, not the other way around. It seems like IDiagnosableCommandHandler is just IHasDisplayName or something? Can we make a better name? Because I suspect you'll want that exact same thing for completion providers being slow, etc.
There was a problem hiding this comment.
good point, I'm going to go with MS.VS.Utilities.IHasDisplayName and
public interface ICommandHandler<T> : ICommandHandler, IHasDisplayName where T : CommandArgs
public interface IChainedCommandHandler<T> : ICommandHandler, IHasDisplayName where T : CommandArgs
| </resheader> | ||
| <resheader name="writer"> | ||
| <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||
| </resheader> |
There was a problem hiding this comment.
what happened here?
| { | ||
| ExecuteRenameWorker(args, waitContext.CancellationToken); | ||
| }); | ||
| ExecuteRenameWorker(args, context.WaitContext.CancellationToken); |
There was a problem hiding this comment.
i'm tring to understand the relation between allowCancellation and .CancellationToken. Is it a stack? What happens if some other code sets allowCancellation to false? Will that affect our cancelation token?
There was a problem hiding this comment.
Maybe it would be clearer if the scope itself had the CancellationToken?
There was a problem hiding this comment.
It's a two way cancellability supported by the threaded wait dialog underneath this abstraction. UserCancellationToken allows user to (attempt to) cancel a command and AllowCancellation allows command handler to disable Cancel button.
I renamed it to UserCancellation token, does it look more readable now?
using (context.WaitContext.AddScope(allowCancellation: true, EditorFeaturesResources.Finding_token_to_rename))
{
ExecuteRenameWorker(args, context.WaitContext.UserCancellationToken);
}
| } | ||
|
|
||
| public void ExecuteCommand(PasteCommandArgs args, Action nextHandler) | ||
| public bool ExecuteCommand(PasteCommandArgs args, CommandExecutionContext context) |
There was a problem hiding this comment.
is there docs on this context obejct?
There was a problem hiding this comment.
It's just a holder of useful stuff commanding service wants to provide to command handlers. Currently it only has wait context:
namespace Microsoft.VisualStudio.Commanding
{
/// <summary>
/// Represents a command execution context, which is set up by a command handler service
/// and provided to each command handler.
/// </summary>
public sealed class CommandExecutionContext
{
/// <summary>
/// Creates new instance of the <see cref="CommandExecutionContext"/>.
/// </summary>
public CommandExecutionContext(IUIThreadOperationContext waitContext)
{
this.WaitContext = waitContext ?? throw new ArgumentNullException(nameof(waitContext));
}
/// <summary>
/// Provides a context of executing a command handler on the UI thread, which
/// enables two way shared cancellability and wait indication.
/// </summary>
public IUIThreadOperationContext WaitContext { get; }
}
}
namespace Microsoft.VisualStudio.Utilities
{
/// <summary>
/// Represents a context of executing potentially long running operation on the UI thread, which
/// enables shared two way cancellability and wait indication.
/// </summary>
/// <remarks>
/// Instances implementing this interface are produced by <see cref="IUIThreadOperationExecutor"/>
/// MEF component.
/// </remarks>
public interface IUIThreadOperationContext : IPropertyOwner, IDisposable
{
/// <summary>
/// Cancellation token that allows user to cancel the operation unless the operation
/// is not cancellable.
/// </summary>
CancellationToken UserCancellationToken { get; }
/// <summary>
/// Gets whether the operation can be cancelled.
/// </summary>
/// <remarks>This value is composed of initial AllowCancellation value and
/// <see cref="IUIThreadOperationScope.AllowCancellation"/> values of all currently added scopes.
/// The value composition logic takes into acount disposed scopes too - if any of added scopes
/// were disposed while its <see cref="IUIThreadOperationScope.AllowCancellation"/> was false,
/// this property will stay false regardless of all other scopes' <see cref="IUIThreadOperationScope.AllowCancellation"/>
/// values.
/// </remarks>
bool AllowCancellation { get; }
/// <summary>
/// Gets user readable operation description, composed of initial context description and
/// descriptions of all currently added scopes.
/// </summary>
string Description { get; }
/// <summary>
/// Gets current list of <see cref="IUIThreadOperationScope"/>s in this context.
/// </summary>
IEnumerable<IUIThreadOperationScope> Scopes { get; }
/// <summary>
/// Adds a UI thread operation scope with its own two way cancellability, description and progress tracker.
/// The scope is removed from the context on dispose.
/// </summary>
IUIThreadOperationScope AddScope(bool allowCancellation, string description);
/// <summary>
/// Allows a component to take full ownership over this UI thread operation, for example
/// when it shows its own modal UI dialog and handles cancellability through that dialog instead.
/// </summary>
void TakeOwnership();
}
}
| using Microsoft.VisualStudio.Commanding; | ||
| using Microsoft.VisualStudio.Text; | ||
| using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; | ||
| using Microsoft.VisualStudio.Utilities; |
There was a problem hiding this comment.
system should sort before MS.
There was a problem hiding this comment.
will do in all modified files
| public VisualStudio.Commanding.CommandState GetCommandState(FindReferencesCommandArgs args) | ||
| { | ||
| return nextHandler(); | ||
| return VisualStudio.Commanding.CommandState.Undetermined; |
There was a problem hiding this comment.
What does Undetermined mean?
There was a problem hiding this comment.
It's a declarative way of expressing "command is enabled if anybody else says so, but I personally have no opinion whatsoever", a simpler alternative to implementing IChainedComamndHandler and delegating GetCommandState to the next handler.
About 25% of Roslyn command handlers (typically those "customizing" editor commands) are like that so I assume this is pretty important case for any lang service and I'd like to make it simple.
Better naming suggestions are welcome.
There was a problem hiding this comment.
CommandState.NoOpinion :D .EnabledIfSomeoneElseWantsIt :D
There was a problem hiding this comment.
Yeah, "Undetermined" still isn't what I'd call it. NoOpinion seems great.
There was a problem hiding this comment.
Or perhaps even "Unspecified". Undetermined is the result of a computation, not an input.
There was a problem hiding this comment.
@jasonmalinowski Unspecified is better than Undetermined indeed. What about Unknown? #Resolved
There was a problem hiding this comment.
| public void ExecuteCommand(AutomaticLineEnderCommandArgs args, Action nextHandler, CommandExecutionContext context) | ||
| { | ||
| // get editor operation | ||
| var operations = _editorOperationsFactoryService.GetEditorOperations(args.TextView); |
There was a problem hiding this comment.
the next line (that checks of operations is null) seems to eb gone.
There was a problem hiding this comment.
I can see it here below
| return; | ||
| } | ||
|
|
||
| using (var transaction = args.TextView.CreateEditTransaction(EditorFeaturesResources.Automatic_Line_Ender, _undoRegistry, _editorOperationsFactoryService)) |
There was a problem hiding this comment.
can you explain the old/new logic here. (alternatively, you can try to keep indentation the same so that hte diff is clearer).
There was a problem hiding this comment.
there is no change in logic here, just 4 less spaces. Instead of
waitIndicator.Wait(
title: EditorFeaturesResources.Automatic_Line_Ender,
message: EditorFeaturesResources.Automatically_completing,
allowCancel: false, action: w =>
{
... code ...
}
it's now
using (context.WaitContext.AddScope(allowCancellation: false, EditorFeaturesResources.Automatically_completing))
{
... code ...
}
Hmm, I don't see how can I increase indentation in the modified code :(
There was a problem hiding this comment.
There was a problem hiding this comment.
Not certain if i can use codeflow :-/
| nextHandler(); | ||
| } | ||
|
|
||
| private bool TryHandleReturnKey(ReturnKeyCommandArgs args) |
There was a problem hiding this comment.
where did this code go?
There was a problem hiding this comment.
AbstractBlockCommentEditingCommandHandler is used by TypeScript so I cannot change it. So I refactored it into BaseAbstractBlockCommentEditingCommandHandler and PlatformAbstractBlockCommentEditingCommandHandler (not happy about that name though). Once TypeScript migrates to the new commanding, I will simplify it back.
There was a problem hiding this comment.
Never mind, it's better to have this class implement both ICommandHandler interfaces now and once TypeScript migrates over I can just delete legacy part.
In reply to: 159565154 [](ancestors = 159565154)
| action: waitContext => | ||
| using (context.WaitContext.AddScope(allowCancellation: false, message)) | ||
| { | ||
|
|
|
I got overloaded halfway through :) |
Is typescript in the loop here? That way we can eventually undo the roslyn commanding stuff and move them over as well? Rgd readability, the general approach we taking is using aliases. i.e. "using VSCommand = ...; using RoslynCommand = ...". Then you refer to VSCommand RoslynCommand as appropropriate. You can alias the namespace, the types, or both.
Can you link to docs, or APIs that show all the editor stuff.
I noticed there was still some code calling 'nextXXX' in the roslyn codebase. (or maybe i'm mistaken). canyou explain what this is for and if it's necessary? Also, were you cable to get completion chaining working properly? If so, awesome! |
| { | ||
| VisualStudio.Workspace.SetUseSuggestionMode(true); | ||
| VisualStudio.InteractiveWindow.InsertCode("#"); | ||
| VisualStudio.Workspace.SetUseSuggestionMode(true); |
There was a problem hiding this comment.
hmmm, this indicates some important difference. Seems like the editor cannot find proper buffer to execute "ToggleSuggestionMode" command before "#" is typed, while Roslyn could.
There was a problem hiding this comment.
fixed the issue in the editor bits so now reverting this change back
jasonmalinowski
left a comment
There was a problem hiding this comment.
Good first round, some questions.
| <value>Split string</value> | ||
| </data> | ||
| <data name="Split_String_Literal_Command_Handler_Name" xml:space="preserve"> | ||
| <value>Split String Literal Command Handler</value> |
There was a problem hiding this comment.
Is the intent that these strings can be shown to the user? If so, do we need to make them semi-understandable by users?
| [ExportCommandHandler("XmlTagCompletionCommandHandler", ContentTypeNames.CSharpContentType)] | ||
| [Export(typeof(VisualStudio.Commanding.ICommandHandler))] | ||
| [ContentType(ContentTypeNames.CSharpContentType)] | ||
| [Name("XmlTagCompletionCommandHandler")] |
| <data name="Split_string" xml:space="preserve"> | ||
| <value>Split string</value> | ||
| </data> | ||
| <data name="Split_String_Literal_Command_Handler_Name" xml:space="preserve"> |
There was a problem hiding this comment.
Convention is we are using the real value between _s not the semantic name. In this case, not sure why we can't just use the previous value.
There was a problem hiding this comment.
will update all new strings to follow the convention
| public VisualStudio.Commanding.CommandState GetCommandState(ReturnKeyCommandArgs args) | ||
| { | ||
| return nextHandler(); | ||
| return VisualStudio.Commanding.CommandState.Undetermined; |
There was a problem hiding this comment.
using Microsoft.VisualStudio.Commanding?
| } | ||
|
|
||
| internal override ICommandHandler<TypeCharCommandArgs> CreateCommandHandler(ITextUndoHistoryRegistry undoHistory) | ||
| internal override IChainedCommandHandler<TypeCharCommandArgs> CreateCommandHandler(ITextUndoHistoryRegistry undoHistory) |
There was a problem hiding this comment.
@olegtk I admit I would have guessed IDiagnosableCommandHandler had ICommandHandler as a base type, not the other way around. It seems like IDiagnosableCommandHandler is just IHasDisplayName or something? Can we make a better name? Because I suspect you'll want that exact same thing for completion providers being slow, etc.
| /// represent the same abstraction. The only place where it's needed so far is FindReferencesCommandHandler, | ||
| /// which operates within IWaitableUIOperationScope, but calls to <see cref="IFindReferencesService"/>, which | ||
| /// requires Roslyn's IWaitContext. | ||
| /// Going forward this adapter can be deleted once Roslyn's IWaitContext/Indicator is retired in favor of editor's |
There was a problem hiding this comment.
|
|
||
| namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities | ||
| { | ||
| internal class TestCommandExecutionContext |
| { | ||
| internal class TestWaitableUIOperationContext : AbstractWaitableUIOperationContext | ||
| { | ||
| CancellationTokenSource _cancellationTokenSource; |
|
|
||
| namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities | ||
| { | ||
| internal class TestWaitableUIOperationContext : AbstractWaitableUIOperationContext |
There was a problem hiding this comment.
Is this the editor-defined base type? Not sure "Abstract" should actually be in the type name...?
| { | ||
| [Export] | ||
| [CommandBinding(Guids.RoslynGroupIdString, ID.RoslynCommands.GoToImplementation, typeof(GoToImplementationCommandArgs))] | ||
| internal CommandBindingDefinition gotoImplementationCommandBinding; |
There was a problem hiding this comment.
I forget the layering here -- should this be somewhere lower in EditorFeatures? Or CommandBindingDefinition is the VS-only bit?
There was a problem hiding this comment.
Yes, binding is host specific so it's VS only thing, VS for Mac would need another way to bind.
| Document document, IFindReferencesService service, int caretPosition, CommandExecutionContext context) | ||
| { | ||
| _waitIndicator.Wait( | ||
| title: EditorFeaturesResources.Find_References, |
There was a problem hiding this comment.
title: EditorFeaturesResources.Find_References, [](start = 16, length = 47)
note that we are losing the title now
There was a problem hiding this comment.
Not much of value was lost.
| waitContext.AllowCancel = false; | ||
| waitScope.AllowCancellation = false; | ||
|
|
||
| var finalSolution = result.GetSolutionAsync(cancellationToken).WaitAndGetResult(cancellationToken); |
There was a problem hiding this comment.
cancellationToken [](start = 60, length = 17)
here is another case, the cancellation is disabled but still using token.
Also I don't really understand why we disable cancellability here, but not in AbstractChangeSignatureCommandHandler for example.
There was a problem hiding this comment.
but I'm going to leave it as is unless anybody objects
In reply to: 160064271 [](ancestors = 160064271)
this is different from AbstractChangeSignatureCommandHandler and similar ones, none of them call next handler on failure. It probably doesn't matter much because there is never a next handler for these commands anyway. Refers to: src/EditorFeatures/Core/Implementation/EncapsulateField/AbstractEncapsulateFieldCommandHandler.cs:123 in c640ccc. [](commit_id = c640ccc, deletion_comment = False) |
| [ContentType(ContentTypeNames.RoslynContentType)] | ||
| [Name(PredefinedCommandHandlerNames.NavigateToHighlightedReference)] | ||
| internal partial class NavigateToHighlightReferenceCommandHandler : | ||
| ICommandHandler<NavigateToHighlightedReferenceCommandArgs> |
There was a problem hiding this comment.
NavigateToHighlightedReferenceCommandArgs [](start = 24, length = 41)
somehow this was converted into 2 commands on the editor side in 15.3 (NavigateToNextHighlightedReferenceCommandArgs and NavigateToPreviousHighlightedReferenceCommandArgs)
| public void ExecuteCommand(ReturnKeyCommandArgs args, Action nextHandler) | ||
| public void ExecuteCommand(ReturnKeyCommandArgs args, Action nextHandler, CommandExecutionContext context) | ||
| { | ||
| ExecuteReturnOrTypeCommand(args, nextHandler, CancellationToken.None); |
There was a problem hiding this comment.
CancellationToken.None [](start = 58, length = 22)
not cancellable for some reason so I won't change it now (we do plan to catch and log commands not reacting to user cancellation going forward)
| namespace Microsoft.CodeAnalysis.Editor.CommandHandlers | ||
| { | ||
| [ExportCommandHandler(PredefinedCommandHandlerNames.GoToAdjacentMember, ContentTypeNames.RoslynContentType)] | ||
| internal class GoToAdjacentMemberCommandHandler : ICommandHandler<GoToAdjacentMemberCommandArgs> |
There was a problem hiding this comment.
GoToAdjacentMemberCommandArgs [](start = 70, length = 29)
somehow this command got converted into a pair commands on the editor side back in 15.3
| { | ||
| internal partial class Controller : | ||
| AbstractController<Session<Controller, Model, IQuickInfoPresenterSession>, Model, IQuickInfoPresenterSession, IQuickInfoSession>, | ||
| ICommandHandler<InvokeQuickInfoCommandArgs> |
There was a problem hiding this comment.
ICommandHandler [](start = 7, length = 44)
actually dead code, QuickInfo is invoked by the editor since 15.3 and Roslyn has no command handler for this command
|
Note: it's quite possible that many CancellationToken.None (and blocking async) cases are unintentional. It's worthwhile for these to all be audited once everything goes in. |
| using Microsoft.CodeAnalysis.Shared.Utilities; | ||
| using Microsoft.VisualStudio.Language.NavigateTo.Interfaces; | ||
|
|
||
| #pragma warning disable CS0618 // Type or member is obsolete |
| } | ||
|
|
||
| return new CommandState(isAvailable: !enabled); | ||
| return enabled ? VSCommanding.CommandState.Unspecified : VSCommanding.CommandState.Available; |
There was a problem hiding this comment.
enabled ? VSCommanding.CommandState.Unspecified : VSCommanding.CommandState.Available; [](start = 19, length = 86)
doesn't seem right, fixed
| #Disable Warning BC40000 ' IQuickInfo* is obsolete | ||
| Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense | ||
|
|
||
| #Disable Warning BC40000 ' Type or member is obsolete |
55a3908 to
c9d1e06
Compare
c9d1e06 to
229394d
Compare
rchande
left a comment
There was a problem hiding this comment.
- Spot checked a few places and changes generally seemed sane
- Looked at all of the completion changes and concur with the move to IChainedCommandHandler
- Can you confirm that invoking a refactoring command while rename is active has the same behavior as in the past (rename commits, then the refactoring is invoked)?
|
@rchande yes, I manually verified executing a refactoring command when inline rename session is active, it works as before (rename completes then refactorings runs). I think there is an integration test that covers it too. |
229394d to
aa892f0
Compare
Dismissing as I need to-rereview now
jasonmalinowski
left a comment
There was a problem hiding this comment.
![]()
Any comments marked [mop-up in Preview 4] explicitly means we probably need to fix it, but there's no reason to block this PR in it. They're all small changes that don't impact any API concerns or anything, so we can just fix it in the next week.
I think there's one API tweak I'd like to see too but we can stagger it nicely: I love the IUIOperationConext name over WaitContext, which makes it seem really unfortunately that CommandExecutionContext.WaitContext is still that vs. CommandExecutionContext.OperationContext. I'd totally support using the next week to just add a new member with the new name while deprecating the old one, bumping Roslyn ahead again (happy to even do that myself), and then you can remove the old member in CommandExecutionContext. Ideally that can all happen in ask mode. If we have to take the last step (removing the old member) in escrow, that would be trivial.
| <value>Split string</value> | ||
| </data> | ||
| <data name="Split_String_Literal_Command_Handler" xml:space="preserve"> | ||
| <value>Split String Literal Command Handler</value> |
There was a problem hiding this comment.
[mop-up in Preview 4] These are going to be potentially displayed if we ever do a "disable" feature, right? I'm guessing we shouldn't say "command handler" then since that's just internal meaningless goo to them?
| private void HandlePossibleTypingCommand(CommandArgs args, Action nextHandler, Action<SnapshotSpan> actionIfInsideActiveSpan) | ||
| private VSCommanding.CommandState GetCommandState() | ||
| { | ||
| return _renameService.ActiveSession != null ? VSCommanding.CommandState.Available : VSCommanding.CommandState.Unspecified; |
There was a problem hiding this comment.
[no change needed] Really liking Unspecified as the word. Feels natural here.
| namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename | ||
| { | ||
| internal partial class RenameCommandHandler : ICommandHandler<OpenLineAboveCommandArgs> | ||
| internal partial class RenameCommandHandler : IChainedCommandHandler<OpenLineAboveCommandArgs> |
There was a problem hiding this comment.
[mop-up in Preview 4] I suspect this can get your non-IChanedCommandHandler treatment like you did for MoveSelectedLinesHandler with the CommitIfActive then return false.
There was a problem hiding this comment.
I converted all obvious ones (missed 2, doing in mop-up). Those remaining call HandlePossibleTypingCommand, refactoring which seems too risky at this point. I'd rather do it in 15.7. Filed #24430 to track it
| } | ||
|
|
||
| public void ExecuteCommand(StartAutomaticOutliningCommandArgs args, Action nextHandler) | ||
| public string DisplayName => EditorFeaturesResources.Outlining_Command_Handler; |
There was a problem hiding this comment.
[mop-up in Preview 4] These are going to be potentially displayed if we ever do a "disable" feature, right? I'm guessing we shouldn't say "command handler" then since that's just internal meaningless goo to them?
| ICommandHandler<DeleteKeyCommandArgs>, | ||
| ICommandHandler<SelectAllCommandArgs> | ||
| IChainedCommandHandler<TabKeyCommandArgs>, | ||
| IChainedCommandHandler<ToggleCompletionModeCommandArgs>, |
There was a problem hiding this comment.
Makes sense, especially because the hope is in 15.7 this code is deleted.
| context.WaitContext.TakeOwnership(); | ||
| var notificationService = document.Project.Solution.Workspace.Services.GetService<INotificationService>(); | ||
| notificationService.SendNotification(messageToShow, | ||
| title: EditorFeaturesResources.Go_To_Implementation, |
There was a problem hiding this comment.
No reason we couldn't have SendNotification take the IUIOperationContext to then call this for you, which would seem natural if it wasn't an API break that might impact TypeScript/F#.
| { | ||
| return; | ||
| var reorderParametersService = document.GetLanguageService<AbstractChangeSignatureService>(); | ||
| result = reorderParametersService.ChangeSignature( |
There was a problem hiding this comment.
@dpoeschl can this prompt a dialog sooner? i.e. do we have to move the TakeOwnership() call earlier?
| nextHandler() | ||
| Return | ||
| End If | ||
| Using context.WaitContext.AddScope(allowCancellation:=True, VBEditorResources.Formatting_Document) |
There was a problem hiding this comment.
This needs a try/catch now, right?
| _subjectBuffer = subjectBuffer; | ||
| CurrentHandlers = commandHandlerServiceFactory.GetService(subjectBuffer); | ||
| NextCommandTarget = nextCommandTarget; | ||
| // Setup all command handlers migrated to the modern editor commandig to be execited next |
There was a problem hiding this comment.
[mop-up in Preview 4] Multiple typos here.
| CurrentHandlers = commandHandlerServiceFactory.GetService(subjectBuffer); | ||
| NextCommandTarget = nextCommandTarget; | ||
| // Setup all command handlers migrated to the modern editor commandig to be execited next | ||
| var componentModel = Shell.ServiceProvider.GlobalProvider.GetService(typeof(SComponentModel)) as IComponentModel; |
There was a problem hiding this comment.
[mop-up in Preview 4] We generally avoid GlobalProvider since it has potenetial surprises. You should have a Package type here you can grab it from. Also we hard cast because if that as ... doesn't work, you're in deep trouble. 😄
|
Approved to merge via https://devdiv.visualstudio.com/DevDiv/_workitems/edit/553445 |

Ask Mode template
Customer scenario
This change migrates all Roslyn command handlers to the new editor commanding system, which is based on Roslyn's commanding, see Modern Editor Commanding API Spec.
Besides other benefits, migrating to the new editor commanding allows the editor to diagnose typing performance in a more fine grained way, for example it will be now possible to measure/collect traces of individual command handlers introducing typing delays.
Bugs this fixes
#24194
https://devdiv.visualstudio.com/DevDiv/_workitems/edit/513714
https://devdiv.visualstudio.com/DevDiv/_workitems/edit/511623
Risk
This change affects execution of every Roslyn commands handler, but the risk is mitigated by the fact that the new editor commanding is designed and implemented based on existing Roslyn commanding and preserves command handling semantics and design principles as is so most command handlers are migrated by just changing the namespace of interface they implement and the way they are exported.
Performance impact
According to RPS results, this change introduces a below degrade bar regression in C# typing scenario, caused by the new editor commanding infrastructure:
Validation
The following validation was performed:
This change migrates all Roslyn command handlers to the new platform's commanding system, which is based on Roslyn's commanding, see Modern Editor Commanding API Spec.
The migration strategy is to keep Roslyn commanding infrastructure in place as is, but migrate all C#/VB command handlers over. Given that the migration mostly required pretty boilerplate changes: