private EditorCommandHandlerServiceState InitializeExecutionState <T>(T args) where T : EditorCommandArgs
        {
            var state = new EditorCommandHandlerServiceState(args, IsTypingCommand(args));
            var uiThreadOperationContext = _factory.UIThreadOperationExecutor.BeginExecute(
                new UIThreadOperationExecutionOptions(
                    title: null, // We want same caption as the main window
                    defaultDescription: WaitForCommandExecutionString, allowCancellation: true, showProgress: true,
                    timeoutController: new TimeoutController(state, _textView, _factory.LoggingService)));
            var commandExecutionContext = new CommandExecutionContext(uiThreadOperationContext);

            commandExecutionContext.OperationContext.UserCancellationToken.Register(OnExecutionCancellationRequested, state);
            state.ExecutionContext = commandExecutionContext;

            // Per internal convention hosts can add additional host specific input context properties into
            // text view's property bag. We then surface it to command handlers (first item in case it's a list) via
            // CommandExecutionContext properties using type name as a key.
            if (_textView.Properties.TryGetProperty(CommandingConstants.AdditionalCommandExecutionContext, out object hostSpecificInputContext))
            {
                if (hostSpecificInputContext != null)
                {
                    if (hostSpecificInputContext is IList hostSpecificInputContextList &&
                        hostSpecificInputContextList.Count > 0)
                    {
                        hostSpecificInputContext = hostSpecificInputContextList[0];
                    }

                    commandExecutionContext.Properties.AddProperty(hostSpecificInputContext.GetType(), hostSpecificInputContext);
                }
            }

            return(state);
        }
 private void ExecuteCommandHandlerChain(
     EditorCommandHandlerServiceState state,
     Action handlerChain,
     Action nextCommandHandler)
 {
     try
     {
         // Kick off the first command handler.
         handlerChain();
         if (state.ExecutionContext.OperationContext.UserCancellationToken.IsCancellationRequested)
         {
             LogCancellationWasIgnored(state);
         }
     }
     catch (OperationCanceledException)
     {
         OnCommandExecutionCancelled(nextCommandHandler, state);
     }
     catch (AggregateException aggregate) when(aggregate.InnerExceptions.All(e => e is OperationCanceledException))
     {
         OnCommandExecutionCancelled(nextCommandHandler, state);
     }
     finally
     {
         state.ExecutionContext?.OperationContext?.Dispose();
     }
 }
        private EditorCommandHandlerServiceState InitializeExecutionState <T>(T args) where T : EditorCommandArgs
        {
            var state = new EditorCommandHandlerServiceState(args, IsTypingCommand(args));
            var uiThreadOperationContext = _factory.UIThreadOperationExecutor.BeginExecute(
                new UIThreadOperationExecutionOptions(
                    title: null, // We want same caption as the main window
                    defaultDescription: WaitForCommandExecutionString, allowCancellation: true, showProgress: true,
                    timeoutController: new TimeoutController(state, _textView, _factory.LoggingService)));
            var commandExecutionContext = new CommandExecutionContext(uiThreadOperationContext);

            commandExecutionContext.OperationContext.UserCancellationToken.Register(OnExecutionCancellationRequested, state);
            state.ExecutionContext = commandExecutionContext;
            return(state);
        }
        private void OnCommandExecutionCancelled(Action nextCommandHandler, EditorCommandHandlerServiceState state)
        {
            var  executingHandler = state.GetCurrentlyExecutingCommandHander();
            var  executingCommand = state.ExecutingCommand;
            bool userCancelled    = !state.ExecutionHasTimedOut;

            _factory.JoinableTaskContext.Factory.RunAsync(async() =>
            {
                LogCommandExecutionCancelled(executingHandler, executingCommand, userCancelled);

                string statusBarMessage = string.Format(CultureInfo.CurrentCulture, CommandingStrings.CommandCancelled, executingHandler?.DisplayName);
                await _factory.StatusBar.SetTextAsync(statusBarMessage).ConfigureAwait(false);
            });

            nextCommandHandler?.Invoke();
        }
        public void Execute <T>(Func <ITextView, ITextBuffer, T> argsFactory, Action nextCommandHandler) where T : EditorCommandArgs
        {
            if (!_factory.JoinableTaskContext.IsOnMainThread)
            {
                throw new InvalidOperationException($"{nameof(IEditorCommandHandlerService.Execute)} method should only be called on the UI thread.");
            }

            // In contained languge (Razor) scenario it's possible that EditorCommandHandlerService is called re-entrantly
            // for the same command, first by contained language command filter and then by editor command chain.
            // To preserve Razor commanding semantics, only execute handlers once for the same command.
            if (IsReentrantCall <T>())
            {
                nextCommandHandler?.Invoke();
                return;
            }

            EditorCommandHandlerServiceState state = null;

            using (var reentrancyGuard = new ReentrancyGuard <T>(_textView))
            {
                // Build up chain of handlers per buffer
                Action handlerChain = nextCommandHandler ?? EmptyAction;
                // TODO: realize the chain dynamically and without Reverse()
                foreach (var bufferAndHandler in GetOrderedBuffersAndCommandHandlers <T>().Reverse())
                {
                    T args = null;
                    // Declare locals to ensure that we don't end up capturing the wrong thing
                    var nextHandler = handlerChain;
                    var handler     = bufferAndHandler.handler;
                    args = args ?? (args = argsFactory(_textView, bufferAndHandler.buffer));
                    if (args == null)
                    {
                        // Args factory failed, skip command handlers and just call next
                        handlerChain();
                        return;
                    }

                    if (handler is IDynamicCommandHandler <T> dynamicCommandHandler &&
                        !dynamicCommandHandler.CanExecuteCommand(args))
                    {
                        // Skip this one as it cannot execute the command.
                        continue;
                    }

                    if (state == null)
                    {
                        state = InitializeExecutionState(args);
                    }

                    handlerChain = () => _factory.GuardedOperations.CallExtensionPoint(handler,
                                                                                       () =>
                    {
                        state.OnExecutingCommandHandlerBegin(handler);
                        handler.ExecuteCommand(args, nextHandler, state.ExecutionContext);
                        state.OnExecutingCommandHandlerEnd(handler);
                    },
                                                                                       // Do not guard against cancellation exceptions, they are handled by ExecuteCommandHandlerChain
                                                                                       exceptionGuardFilter: (e) => !IsOperationCancelledException(e));
                }

                if (state == null)
                {
                    // No matching command handlers, just call next
                    handlerChain();
                    return;
                }

                _telemetrySession?.BeforeKeyProcessed();
                ExecuteCommandHandlerChain(state, handlerChain, nextCommandHandler);
                _telemetrySession?.AfterKeyProcessed();
            }
        }