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(); } }