Skip to content

Migrate command handlers to the new editor commanding#23769

Merged
olegtk merged 1 commit intodotnet:dev15.6-preview3-vs-depsfrom
olegtk:MigrateToPlatformCommanding
Jan 18, 2018
Merged

Migrate command handlers to the new editor commanding#23769
olegtk merged 1 commit intodotnet:dev15.6-preview3-vs-depsfrom
olegtk:MigrateToPlatformCommanding

Conversation

@olegtk
Copy link
Copy Markdown
Contributor

@olegtk olegtk commented Dec 13, 2017

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:

  1. Half the regression is the cost of initialization/disposal of VS threaded wait dialog which is RPS machine setup specific issue (code markers writing to a file) and will not affect customers.
  2. We are doing a bit more work in the new editor commanding than Roslyn used to because of a more generalized projection-aware approach to ordering command handlers. We plan to address that by making focused optimizations on the editor side in Preview 4.

Validation

The following validation was performed:

  1. Roslyn unit, integration tests are passing
  2. DDRITs are passing
  3. RPS is passing (see perf impact notes above)
  4. Manual regression testing by the Editor, Roslyn, TypeScript, WebTools, F# and XAML teams on a validation build, no regressions found.
  5. I prototyped migrating VS for Mac to the new editor bits + Roslyn build of this PR and confirmed no issues or leaked WPF dependencies. Current Razor support in VS for Mac can be straightforwardly migrated from using Roslyn commanding to the new editor commanding.

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:

  1. Namespace changes, e.g. all common editor *CommandArgs classes are now in Microsoft.VisualStudio.Text.Editor.Commanding.Commands namespace.
  2. Different way to export a command handler
  3. Independent command handlers are migrated to a simpler ICommandHandler interface that doesn't require dealing with next handler chain.
  4. Command handlers requiring access to the next handler are migrated to IChainedCommandHandler with no change in semantics.
  5. Existing Roslyn commanding infrastructure is left intact as languages such as TypeScript still depend on it. That unfortunately required a lot of verbose name qualifications to resolve name conflicts, any suggestions to improve readability are welcome.
  6. Platform command handlers are required to provide their display name.
  7. Platform's command handlers are executed in a shared wait context that is provided to handlers via CommandExecutionContext argument. It contains cancellation token and also allows handlers to push a wait scope with their own cancellability flag, description and progress tracker. So all _waitIndicator.Wait() are replaced with context.WaitContext.AddScope().
  8. Some Roslyn commands are Roslyn specific and stay in Roslyn (e.g. SortAndRemoveUnnecessaryImportsCommandArgs). Those required creating new CommandArgs classes deriving from Platform's EditorCommandArgs and exporting VS specific command bindings.

@olegtk olegtk requested review from a team as code owners December 13, 2017 23:25
@dnfclas
Copy link
Copy Markdown

dnfclas commented Dec 13, 2017

CLA assistant check
All CLA requirements met.

ITextUndoHistoryRegistry undoRegistry,
IEditorOperationsFactoryService editorOperations)
{
return new AutomaticLineEnderCommandHandler(waitIndicator, undoRegistry, editorOperations);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does wait handling work in the new system?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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.
  2. All command handlers are executed under a shared wait indicator/context set up by the editor and can obtain wait context via CommandExecutionContext argument.
  3. With the wait context command handlers get shared cancellation token.
  4. 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.

Copy link
Copy Markdown
Contributor Author

@olegtk olegtk Jan 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CyrusNajmabadi I linked the spec in description and did a bunch of improvements. Please take another look when you have time. Thanks!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which did this change from IsAvailable to IsUndetermined?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a link to the docs on these types?

Copy link
Copy Markdown
Contributor Author

@olegtk olegtk Dec 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Contributor Author

@olegtk olegtk Jan 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happened here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undone

{
ExecuteRenameWorker(args, waitContext.CancellationToken);
});
ExecuteRenameWorker(args, context.WaitContext.CancellationToken);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be clearer if the scope itself had the CancellationToken?

Copy link
Copy Markdown
Contributor Author

@olegtk olegtk Jan 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there docs on this context obejct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

system should sort before MS.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do in all modified files

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

public VisualStudio.Commanding.CommandState GetCommandState(FindReferencesCommandArgs args)
{
return nextHandler();
return VisualStudio.Commanding.CommandState.Undetermined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does Undetermined mean?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommandState.NoOpinion :D .EnabledIfSomeoneElseWantsIt :D

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, "Undetermined" still isn't what I'd call it. NoOpinion seems great.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or perhaps even "Unspecified". Undetermined is the result of a computation, not an input.

Copy link
Copy Markdown
Contributor Author

@olegtk olegtk Jan 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasonmalinowski Unspecified is better than Undetermined indeed. What about Unknown? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, went with Unspecified


In reply to: 159562654 [](ancestors = 159562654)

public void ExecuteCommand(AutomaticLineEnderCommandArgs args, Action nextHandler, CommandExecutionContext context)
{
// get editor operation
var operations = _editorOperationsFactoryService.GetEditorOperations(args.TextView);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the next line (that checks of operations is null) seems to eb gone.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see it here below

return;
}

using (var transaction = args.TextView.CreateEditTransaction(EditorFeaturesResources.Automatic_Line_Ender, _undoRegistry, _editorOperationsFactoryService))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain the old/new logic here. (alternatively, you can try to keep indentation the same so that hte diff is clearer).

Copy link
Copy Markdown
Contributor Author

@olegtk olegtk Jan 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 :(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, this looks sooo much cleaner in CodeFlow :)


In reply to: 159564396 [](ancestors = 159564396)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not certain if i can use codeflow :-/

nextHandler();
}

private bool TryHandleReturnKey(ReturnKeyCommandArgs args)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where did this code go?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra newline.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

I got overloaded halfway through :)

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

Existing Roslyn commanding infrastructure is left intact as languages such as TypeScript still depend on it. That unfortunately required a lot of verbose name qualifications to resolve name conflicts, any suggestions to improve readability are welcome.

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.

Platform's command handlers are executed in a shared wait context that is provided to handlers via CommandExecutionContext argument. It contains cancellation token and also allows handlers to push a wait scope with their own cancellability flag, description and progress tracker. So all _waitIndicator.Wait() are replaced with context.WaitContext.AddScope().

Can you link to docs, or APIs that show all the editor stuff.

Some Roslyn commands are Roslyn specific and stay in Roslyn (e.g. SortAndRemoveUnnecessaryImportsCommandArgs). Those required creating new CommandArgs classes deriving from Platform's EditorCommandArgs and exporting VS specific command bindings.

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!

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

Also... this is a bit concerning:

image

Can you summarize where those 3k new lines are coming from?

{
VisualStudio.Workspace.SetUseSuggestionMode(true);
VisualStudio.InteractiveWindow.InsertCode("#");
VisualStudio.Workspace.SetUseSuggestionMode(true);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, this indicates some important difference. Seems like the editor cannot find proper buffer to execute "ToggleSuggestionMode" command before "#" is typed, while Roslyn could.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed the issue in the editor bits so now reverting this change back

@jasonmalinowski jasonmalinowski changed the base branch from features/vs-for-mac-packages to master-vs-deps December 22, 2017 18:04
@jasonmalinowski jasonmalinowski changed the title Migrate C#/VB command handlers to the new Platform's commanding [WIP] Migrate C#/VB command handlers to the new Platform's commanding Dec 22, 2017
Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nameof?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

<data name="Split_string" xml:space="preserve">
<value>Split string</value>
</data>
<data name="Split_String_Literal_Command_Handler_Name" xml:space="preserve">
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will update all new strings to follow the convention

public VisualStudio.Commanding.CommandState GetCommandState(ReturnKeyCommandArgs args)
{
return nextHandler();
return VisualStudio.Commanding.CommandState.Undetermined;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using Microsoft.VisualStudio.Commanding?

}

internal override ICommandHandler<TypeCharCommandArgs> CreateCommandHandler(ITextUndoHistoryRegistry undoHistory)
internal override IChainedCommandHandler<TypeCharCommandArgs> CreateCommandHandler(ITextUndoHistoryRegistry undoHistory)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crefs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


In reply to: 158573958 [](ancestors = 158573958)


namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities
{
internal class TestCommandExecutionContext
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static

{
internal class TestWaitableUIOperationContext : AbstractWaitableUIOperationContext
{
CancellationTokenSource _cancellationTokenSource;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private readonly


namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities
{
internal class TestWaitableUIOperationContext : AbstractWaitableUIOperationContext
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forget the layering here -- should this be somewhere lower in EditorFeatures? Or CommandBindingDefinition is the VS-only bit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

title: EditorFeaturesResources.Find_References, [](start = 16, length = 47)

note that we are losing the title now

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not much of value was lost.

waitContext.AllowCancel = false;
waitScope.AllowCancellation = false;

var finalSolution = result.GetSolutionAsync(cancellationToken).WaitAndGetResult(cancellationToken);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I'm going to leave it as is unless anybody objects


In reply to: 160064271 [](ancestors = 160064271)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed #24153 to track it.

@olegtk
Copy link
Copy Markdown
Contributor Author

olegtk commented Jan 7, 2018

                    return false;

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>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #24155 to improve it later.

namespace Microsoft.CodeAnalysis.Editor.CommandHandlers
{
[ExportCommandHandler(PredefinedCommandHandlerNames.GoToAdjacentMember, ContentTypeNames.RoslynContentType)]
internal class GoToAdjacentMemberCommandHandler : ICommandHandler<GoToAdjacentMemberCommandArgs>
Copy link
Copy Markdown
Contributor Author

@olegtk olegtk Jan 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mismerge, deleted

}

return new CommandState(isAvailable: !enabled);
return enabled ? VSCommanding.CommandState.Unspecified : VSCommanding.CommandState.Available;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mismerge, deleted

@olegtk olegtk added Area-IDE 4 - In Review A fix for the issue is submitted for review. Area-Interactive labels Jan 9, 2018
@olegtk olegtk self-assigned this Jan 9, 2018
@olegtk
Copy link
Copy Markdown
Contributor Author

olegtk commented Jan 9, 2018

For some reason can't request reviewers on this PR, so tagging @Pilchie and @rchande old fashioned way. Please take a look, it's a big but mostly boilerplate PR, the more people review the better. Thanks!

@olegtk olegtk force-pushed the MigrateToPlatformCommanding branch 2 times, most recently from 55a3908 to c9d1e06 Compare January 10, 2018 18:20
@jasonmalinowski jasonmalinowski changed the base branch from master-vs-deps to dev15.6.x-vs-deps January 10, 2018 19:08
@olegtk olegtk force-pushed the MigrateToPlatformCommanding branch from c9d1e06 to 229394d Compare January 10, 2018 19:36
@olegtk olegtk changed the title [WIP] Migrate C#/VB command handlers to the new Platform's commanding Migrate command handlers to the new editor commanding Jan 11, 2018
Copy link
Copy Markdown
Contributor

@rchande rchande left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 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)?

@olegtk
Copy link
Copy Markdown
Contributor Author

olegtk commented Jan 12, 2018

@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.

@jasonmalinowski jasonmalinowski changed the base branch from dev15.6.x-vs-deps to dev15.6-preview3-vs-deps January 13, 2018 00:51
@olegtk olegtk force-pushed the MigrateToPlatformCommanding branch from 229394d to aa892f0 Compare January 17, 2018 18:27
@jasonmalinowski jasonmalinowski dismissed their stale review January 17, 2018 18:32

Dismissing as I need to-rereview now

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

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>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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. 😄

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@olegtk olegtk mentioned this pull request Jan 18, 2018
5 tasks
@natidea
Copy link
Copy Markdown
Contributor

natidea commented Jan 18, 2018

@olegtk olegtk merged commit d044590 into dotnet:dev15.6-preview3-vs-deps Jan 18, 2018
@gafter gafter removed 4 - In Review A fix for the issue is submitted for review. labels Dec 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants