From: Mike McLaughlin Date: Wed, 8 Dec 2021 06:44:40 +0000 (-0800) Subject: Move command service impl to M.D.DS.Implementation (#2772) X-Git-Tag: accepted/tizen/unified/20221103.165810~28^2^2~146 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=771e362304bae5ac1f5b85a491f3e5bd9b411aa1;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Move command service impl to M.D.DS.Implementation (#2772) * Move command service impl to M.D.DS.Implementation Rename to CommandService. The lldb/dbgeng SOS doesn't need the repl. One less assembly in that package. Clean up console service/repl assembly. Rename to ConsoleService. * Code review feedback --- diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs new file mode 100644 index 000000000..60fdbbbc3 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs @@ -0,0 +1,562 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Help; +using System.CommandLine.Invocation; +using System.CommandLine.IO; +using System.CommandLine.Parsing; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostics.DebugServices.Implementation +{ + /// + /// Implements the ICommandService interface using System.CommandLine. + /// + public class CommandService : ICommandService + { + private Parser _parser; + private readonly CommandLineBuilder _rootBuilder; + private readonly Dictionary _commandHandlers = new Dictionary(); + + /// + /// Create an instance of the command processor; + /// + /// command prompted used in help message + public CommandService(string commandPrompt = null) + { + _rootBuilder = new CommandLineBuilder(new Command(commandPrompt ?? ">")); + _rootBuilder.UseHelpBuilder((bindingContext) => new LocalHelpBuilder(this, bindingContext.Console, useHelpBuilder: false)); + } + + /// + /// Parse and execute the command line. + /// + /// command line text + /// services for the command + /// exit code + public int Execute(string commandLine, IServiceProvider services) + { + // Parse the command line and invoke the command + ParseResult parseResult = Parser.Parse(commandLine); + + var context = new InvocationContext(parseResult, new LocalConsole(services)); + if (parseResult.Errors.Count > 0) + { + context.InvocationResult = new ParseErrorResult(); + } + else + { + if (parseResult.CommandResult.Command is Command command) + { + if (command.Handler is CommandHandler handler) + { + ITarget target = services.GetService(); + if (!handler.IsValidPlatform(target)) + { + if (target != null) + { + context.Console.Error.WriteLine($"Command '{command.Name}' not supported on this target"); + } + else + { + context.Console.Error.WriteLine($"Command '{command.Name}' needs a target"); + } + return 1; + } + try + { + handler.Invoke(context, services); + } + catch (Exception ex) + { + OnException(ex, context); + } + } + } + } + + context.InvocationResult?.Apply(context); + return context.ResultCode; + } + + /// + /// Displays the help for a command + /// + /// name of the command or alias + /// service provider + /// true if success, false if command not found + public bool DisplayHelp(string commandName, IServiceProvider services) + { + Command command = null; + if (!string.IsNullOrEmpty(commandName)) + { + command = _rootBuilder.Command.Children.OfType().FirstOrDefault((cmd) => commandName == cmd.Name || cmd.Aliases.Any((alias) => commandName == alias)); + if (command == null) + { + return false; + } + if (command.Handler is CommandHandler handler) + { + ITarget target = services.GetService(); + if (!handler.IsValidPlatform(target)) + { + return false; + } + } + } + else + { + ITarget target = services.GetService(); + + // Create temporary builder adding only the commands that are valid for the target + var builder = new CommandLineBuilder(new Command(_rootBuilder.Command.Name)); + foreach (Command cmd in _rootBuilder.Command.Children.OfType()) + { + if (cmd.Handler is CommandHandler handler) + { + if (handler.IsValidPlatform(target)) + { + builder.AddCommand(cmd); + } + } + } + command = builder.Command; + } + Debug.Assert(command != null); + IHelpBuilder helpBuilder = new LocalHelpBuilder(this, new LocalConsole(services), useHelpBuilder: true); + helpBuilder.Write(command); + return true; + } + + /// + /// Enumerates all the command's name and help + /// + public IEnumerable<(string name, string help, IEnumerable aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases)); + + /// + /// Add the commands and aliases attributes found in the type. + /// + /// Command type to search + /// function to create command instance + public void AddCommands(Type type, Func factory) + { + for (Type baseType = type; baseType != null; baseType = baseType.BaseType) + { + if (baseType == typeof(CommandBase)) { + break; + } + var commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: false); + foreach (CommandAttribute commandAttribute in commandAttributes) + { + if (factory == null) + { + // Assumes zero parameter constructor + ConstructorInfo constructor = type.GetConstructors().SingleOrDefault((info) => info.GetParameters().Length == 0) ?? + throw new ArgumentException($"No eligible constructor found in {type}"); + + factory = (services) => constructor.Invoke(Array.Empty()); + } + CreateCommand(baseType, commandAttribute, factory); + } + } + + // Build or re-build parser instance after all these commands and aliases are added + FlushParser(); + } + + private void CreateCommand(Type type, CommandAttribute commandAttribute, Func factory) + { + var command = new Command(commandAttribute.Name, commandAttribute.Help); + var properties = new List<(PropertyInfo, Option)>(); + var arguments = new List<(PropertyInfo, Argument)>(); + + foreach (string alias in commandAttribute.Aliases) + { + command.AddAlias(alias); + } + + foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite)) + { + var argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault(); + if (argumentAttribute != null) + { + IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne; + + var argument = new Argument { + Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(), + Description = argumentAttribute.Help, + ArgumentType = property.PropertyType, + Arity = arity + }; + command.AddArgument(argument); + arguments.Add((property, argument)); + } + else + { + var optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault(); + if (optionAttribute != null) + { + var option = new Option(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help) { + Argument = new Argument { ArgumentType = property.PropertyType } + }; + command.AddOption(option); + properties.Add((property, option)); + + foreach (string alias in optionAttribute.Aliases) + { + option.AddAlias(alias); + } + } + else + { + // If not an option, add as just a settable properties + properties.Add((property, null)); + } + } + } + + var handler = new CommandHandler(commandAttribute, arguments, properties, type, factory); + _commandHandlers.Add(command.Name, handler); + command.Handler = handler; + _rootBuilder.AddCommand(command); + } + + private Parser Parser => _parser ??= _rootBuilder.Build(); + + private void FlushParser() => _parser = null; + + private void OnException(Exception ex, InvocationContext context) + { + if (ex is TargetInvocationException) + { + ex = ex.InnerException; + } + if (ex is NullReferenceException || + ex is ArgumentException || + ex is ArgumentNullException || + ex is ArgumentOutOfRangeException || + ex is NotImplementedException) + { + context.Console.Error.WriteLine(ex.ToString()); + } + else + { + context.Console.Error.WriteLine(ex.Message); + } + Trace.TraceError(ex.ToString()); + } + + private static string BuildOptionAlias(string parameterName) + { + if (string.IsNullOrWhiteSpace(parameterName)) { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName)); + } + return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}"; + } + + /// + /// The normal command handler. + /// + class CommandHandler : ICommandHandler + { + private readonly CommandAttribute _commandAttribute; + private readonly IEnumerable<(PropertyInfo Property, Argument Argument)> _arguments; + private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties; + + private readonly Func _factory; + private readonly MethodInfo _methodInfo; + private readonly MethodInfo _methodInfoHelp; + + public CommandHandler( + CommandAttribute commandAttribute, + IEnumerable<(PropertyInfo, Argument)> arguments, + IEnumerable<(PropertyInfo, Option)> properties, + Type type, + Func factory) + { + _commandAttribute = commandAttribute; + _arguments = arguments; + _properties = properties; + _factory = factory; + + _methodInfo = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute() != null).SingleOrDefault() ?? + throw new ArgumentException($"No command invoke method found in {type}"); + + _methodInfoHelp = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute() != null).SingleOrDefault(); + } + + Task ICommandHandler.InvokeAsync(InvocationContext context) + { + return Task.FromException(new NotImplementedException()); + } + + /// + /// Returns the command name + /// + internal string Name => _commandAttribute.Name; + + /// + /// Returns the command's help text + /// + internal string Help => _commandAttribute.Help; + + /// + /// Returns the list of the command's aliases. + /// + internal IEnumerable Aliases => _commandAttribute.Aliases; + + /// + /// Returns true if the command should be added. + /// + internal bool IsValidPlatform(ITarget target) + { + if ((_commandAttribute.Platform & CommandPlatform.Global) != 0) + { + return true; + } + if (target != null) + { + if (target.OperatingSystem == OSPlatform.Windows) + { + return (_commandAttribute.Platform & CommandPlatform.Windows) != 0; + } + if (target.OperatingSystem == OSPlatform.Linux) + { + return (_commandAttribute.Platform & CommandPlatform.Linux) != 0; + } + if (target.OperatingSystem == OSPlatform.OSX) + { + return (_commandAttribute.Platform & CommandPlatform.OSX) != 0; + } + } + return false; + } + + /// + /// Execute the command synchronously. + /// + /// invocation context + /// service provider + internal void Invoke(InvocationContext context, IServiceProvider services) => Invoke(_methodInfo, context, context.Parser, services); + + /// + /// Executes the command's help invoke function if exists + /// + /// parser instance + /// service provider + /// true help called, false no help function + internal bool InvokeHelp(Parser parser, IServiceProvider services) + { + if (_methodInfoHelp == null) { + return false; + } + // The InvocationContext is null so the options and arguments in the + // command instance created are not set. The context for the command + // requesting help (either the help command or some other command using + // --help) won't work for the command instance that implements it's own + // help (SOS command). + Invoke(_methodInfoHelp, context: null, parser, services); + return true; + } + + private void Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services) + { + object instance = _factory(services); + SetProperties(context, parser, services, instance); + + object[] arguments = BuildArguments(methodInfo, services); + methodInfo.Invoke(instance, arguments); + } + + private void SetProperties(InvocationContext context, Parser parser, IServiceProvider services, object instance) + { + ParseResult defaultParseResult = null; + + // Parse the default options if any + string defaultOptions = _commandAttribute.DefaultOptions; + if (defaultOptions != null) { + defaultParseResult = parser.Parse(Name + " " + defaultOptions); + } + + // Now initialize the option and service properties from the default and command line options + foreach ((PropertyInfo Property, Option Option) property in _properties) + { + object value = property.Property.GetValue(instance); + + if (property.Option != null) + { + if (defaultParseResult != null) + { + OptionResult defaultOptionResult = defaultParseResult.FindResultFor(property.Option); + if (defaultOptionResult != null) { + value = defaultOptionResult.GetValueOrDefault(); + } + } + if (context != null) + { + OptionResult optionResult = context.ParseResult.FindResultFor(property.Option); + if (optionResult != null) { + value = optionResult.GetValueOrDefault(); + } + } + } + else + { + Type propertyType = property.Property.PropertyType; + object service = services.GetService(propertyType); + if (service != null) { + value = service; + } + } + + property.Property.SetValue(instance, value); + } + + // Initialize any argument properties from the default and command line arguments + foreach ((PropertyInfo Property, Argument Argument) argument in _arguments) + { + object value = argument.Property.GetValue(instance); + + List array = null; + if (argument.Property.PropertyType.IsArray && argument.Property.PropertyType.GetElementType() == typeof(string)) + { + array = new List(); + if (value is IEnumerable entries) { + array.AddRange(entries); + } + } + + if (defaultParseResult != null) + { + ArgumentResult defaultArgumentResult = defaultParseResult.FindResultFor(argument.Argument); + if (defaultArgumentResult != null) + { + value = defaultArgumentResult.GetValueOrDefault(); + if (array != null && value is IEnumerable entries) { + array.AddRange(entries); + } + } + } + if (context != null) + { + ArgumentResult argumentResult = context.ParseResult.FindResultFor(argument.Argument); + if (argumentResult != null) + { + value = argumentResult.GetValueOrDefault(); + if (array != null && value is IEnumerable entries) { + array.AddRange(entries); + } + } + } + + argument.Property.SetValue(instance, array != null ? array.ToArray() : value); + } + } + + private object[] BuildArguments(MethodBase methodBase, IServiceProvider services) + { + ParameterInfo[] parameters = methodBase.GetParameters(); + object[] arguments = new object[parameters.Length]; + for (int i = 0; i < parameters.Length; i++) + { + Type parameterType = parameters[i].ParameterType; + + // The parameter will passed as null to allow for "optional" services. The invoked + // method needs to check for possible null parameters. + arguments[i] = services.GetService(parameterType); + } + return arguments; + } + } + + /// + /// Local help builder that allows commands to provide more detailed help + /// text via the "InvokeHelp" function. + /// + class LocalHelpBuilder : IHelpBuilder + { + private readonly CommandService _commandService; + private readonly IConsole _console; + private readonly bool _useHelpBuilder; + + public LocalHelpBuilder(CommandService commandService, IConsole console, bool useHelpBuilder) + { + _commandService = commandService; + _console = console; + _useHelpBuilder = useHelpBuilder; + } + + void IHelpBuilder.Write(ICommand command) + { + bool useHelpBuilder = _useHelpBuilder; + if (_commandService._commandHandlers.TryGetValue(command.Name, out CommandHandler handler)) + { + if (handler.InvokeHelp(_commandService.Parser, LocalConsole.ToServices(_console))) { + return; + } + useHelpBuilder = true; + } + if (useHelpBuilder) + { + var helpBuilder = new HelpBuilder(_console, maxWidth: Console.WindowWidth); + helpBuilder.Write(command); + } + } + } + + /// + /// This class does two things: wraps the IConsoleService and provides the IConsole interface and + /// pipes through the System.CommandLine parsing allowing per command invocation data (service + /// provider and raw command line) to be passed through. + /// + class LocalConsole : IConsole + { + public static IServiceProvider ToServices(IConsole console) => ((LocalConsole)console).Services; + + public readonly IServiceProvider Services; + + private readonly IConsoleService _console; + + public LocalConsole(IServiceProvider services) + { + Services = services; + _console = services.GetService(); + Debug.Assert(_console != null); + Out = new StandardStreamWriter((text) => _console.Write(text)); + Error = new StandardStreamWriter((text) => _console.WriteError(text)); + } + + #region IConsole + + public IStandardStreamWriter Out { get; } + + bool IStandardOut.IsOutputRedirected { get { return false; } } + + public IStandardStreamWriter Error { get; } + + bool IStandardError.IsErrorRedirected { get { return false; } } + + bool IStandardIn.IsInputRedirected { get { return false; } } + + class StandardStreamWriter : IStandardStreamWriter + { + readonly Action _write; + + public StandardStreamWriter(Action write) => _write = write; + + void IStandardStreamWriter.Write(string value) => _write(value); + } + + #endregion + } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj index bb0e43429..a7ede1880 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs b/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs index 8f37d47c6..7b4643842 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs @@ -23,5 +23,13 @@ namespace Microsoft.Diagnostics.DebugServices /// Command type to search /// function to create command instance void AddCommands(Type type, Func factory); + + /// + /// Displays the help for a command + /// + /// name of the command or alias + /// service provider + /// true if success, false if command not found + bool DisplayHelp(string commandName, IServiceProvider services); } } diff --git a/src/Microsoft.Diagnostics.Repl/CharToLineConverter.cs b/src/Microsoft.Diagnostics.Repl/CharToLineConverter.cs new file mode 100644 index 000000000..323b27bbd --- /dev/null +++ b/src/Microsoft.Diagnostics.Repl/CharToLineConverter.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text; + +namespace Microsoft.Diagnostics.Repl +{ + public sealed class CharToLineConverter + { + readonly Action m_callback; + readonly StringBuilder m_text = new StringBuilder(); + + public CharToLineConverter(Action callback) + { + m_callback = callback; + } + + public void Input(byte[] buffer, int offset, int count) + { + for (int i = 0; i < count; i++) { + char c = (char)buffer[offset + i]; + if (c == '\r') { + continue; + } + if (c == '\n') { + Flush(); + } + else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) { + m_text.Append(c); + } + } + } + + public void Input(string text) + { + foreach (char c in text) { + if (c == '\r') { + continue; + } + if (c == '\n') { + Flush(); + } + else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) { + m_text.Append(c); + } + } + } + + public void Flush() + { + m_callback(m_text.ToString()); + m_text.Clear(); + } + } +} diff --git a/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs b/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs deleted file mode 100644 index cce0c9a8b..000000000 --- a/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs +++ /dev/null @@ -1,566 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Diagnostics.DebugServices; -using System; -using System.Collections.Generic; -using System.CommandLine; -using System.CommandLine.Builder; -using System.CommandLine.Help; -using System.CommandLine.Invocation; -using System.CommandLine.IO; -using System.CommandLine.Parsing; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Repl -{ - /// - /// Implements the ICommandService interface using System.CommandLine. - /// - public class CommandProcessor : ICommandService - { - private Parser _parser; - private readonly CommandLineBuilder _rootBuilder; - private readonly Dictionary _commandHandlers = new Dictionary(); - - /// - /// Create an instance of the command processor; - /// - /// command prompted used in help message - public CommandProcessor(string commandPrompt = null) - { - _rootBuilder = new CommandLineBuilder(new Command(commandPrompt ?? ">")); - _rootBuilder.UseHelpBuilder((bindingContext) => new LocalHelpBuilder(this, bindingContext.Console, useHelpBuilder: false)); - } - - /// - /// Parse and execute the command line. - /// - /// command line text - /// services for the command - /// exit code - public int Execute(string commandLine, IServiceProvider services) - { - Debug.Assert(_parser != null); - - // Parse the command line and invoke the command - ParseResult parseResult = _parser.Parse(commandLine); - - var context = new InvocationContext(parseResult, new LocalConsole(services)); - if (parseResult.Errors.Count > 0) - { - context.InvocationResult = new ParseErrorResult(); - } - else - { - if (parseResult.CommandResult.Command is Command command) - { - if (command.Handler is CommandHandler handler) - { - ITarget target = services.GetService(); - if (!handler.IsValidPlatform(target)) - { - if (target != null) - { - context.Console.Error.WriteLine($"Command '{command.Name}' not supported on this target"); - } - else - { - context.Console.Error.WriteLine($"Command '{command.Name}' needs a target"); - } - return 1; - } - try - { - handler.Invoke(context, services); - } - catch (Exception ex) - { - OnException(ex, context); - } - } - } - } - - context.InvocationResult?.Apply(context); - return context.ResultCode; - } - - /// - /// Displays the help for a command - /// - /// name of the command or alias - /// service provider - /// true if success, false if command not found - public bool DisplayHelp(string commandName, IServiceProvider services) - { - Command command = null; - if (!string.IsNullOrEmpty(commandName)) - { - command = _rootBuilder.Command.Children.OfType().FirstOrDefault((cmd) => commandName == cmd.Name || cmd.Aliases.Any((alias) => commandName == alias)); - if (command == null) - { - return false; - } - if (command.Handler is CommandHandler handler) - { - ITarget target = services.GetService(); - if (!handler.IsValidPlatform(target)) - { - return false; - } - } - } - else - { - ITarget target = services.GetService(); - - // Create temporary builder adding only the commands that are valid for the target - var builder = new CommandLineBuilder(new Command(_rootBuilder.Command.Name)); - foreach (Command cmd in _rootBuilder.Command.Children.OfType()) - { - if (cmd.Handler is CommandHandler handler) - { - if (handler.IsValidPlatform(target)) - { - builder.AddCommand(cmd); - } - } - } - command = builder.Command; - } - Debug.Assert(command != null); - IHelpBuilder helpBuilder = new LocalHelpBuilder(this, new LocalConsole(services), useHelpBuilder: true); - helpBuilder.Write(command); - return true; - } - - /// - /// Enumerates all the command's name and help - /// - public IEnumerable<(string name, string help, IEnumerable aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases)); - - /// - /// Add the commands and aliases attributes found in the type. - /// - /// Command type to search - /// function to create command instance - public void AddCommands(Type type, Func factory) - { - for (Type baseType = type; baseType != null; baseType = baseType.BaseType) - { - if (baseType == typeof(CommandBase)) { - break; - } - var commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: false); - foreach (CommandAttribute commandAttribute in commandAttributes) - { - if (factory == null) - { - // Assumes zero parameter constructor - ConstructorInfo constructor = type.GetConstructors().SingleOrDefault((info) => info.GetParameters().Length == 0) ?? - throw new ArgumentException($"No eligible constructor found in {type}"); - - factory = (services) => constructor.Invoke(Array.Empty()); - } - CreateCommand(baseType, commandAttribute, factory); - } - } - - // Build parser instance after all the commands and aliases are added - BuildParser(); - } - - private void CreateCommand(Type type, CommandAttribute commandAttribute, Func factory) - { - var command = new Command(commandAttribute.Name, commandAttribute.Help); - var properties = new List<(PropertyInfo, Option)>(); - var arguments = new List<(PropertyInfo, Argument)>(); - - foreach (string alias in commandAttribute.Aliases) - { - command.AddAlias(alias); - } - - foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite)) - { - var argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault(); - if (argumentAttribute != null) - { - IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne; - - var argument = new Argument { - Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(), - Description = argumentAttribute.Help, - ArgumentType = property.PropertyType, - Arity = arity - }; - command.AddArgument(argument); - arguments.Add((property, argument)); - } - else - { - var optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault(); - if (optionAttribute != null) - { - var option = new Option(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help) { - Argument = new Argument { ArgumentType = property.PropertyType } - }; - command.AddOption(option); - properties.Add((property, option)); - - foreach (string alias in optionAttribute.Aliases) - { - option.AddAlias(alias); - } - } - else - { - // If not an option, add as just a settable properties - properties.Add((property, null)); - } - } - } - - var handler = new CommandHandler(commandAttribute, arguments, properties, type, factory); - _commandHandlers.Add(command.Name, handler); - command.Handler = handler; - _rootBuilder.AddCommand(command); - } - - private void BuildParser() - { - _parser = _rootBuilder.Build(); - } - - private void OnException(Exception ex, InvocationContext context) - { - if (ex is TargetInvocationException) - { - ex = ex.InnerException; - } - if (ex is NullReferenceException || - ex is ArgumentException || - ex is ArgumentNullException || - ex is ArgumentOutOfRangeException || - ex is NotImplementedException) - { - context.Console.Error.WriteLine(ex.ToString()); - } - else - { - context.Console.Error.WriteLine(ex.Message); - } - Trace.TraceError(ex.ToString()); - } - - private static string BuildOptionAlias(string parameterName) - { - if (string.IsNullOrWhiteSpace(parameterName)) { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName)); - } - return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}"; - } - - /// - /// The normal command handler. - /// - class CommandHandler : ICommandHandler - { - private readonly CommandAttribute _commandAttribute; - private readonly IEnumerable<(PropertyInfo Property, Argument Argument)> _arguments; - private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties; - - private readonly Func _factory; - private readonly MethodInfo _methodInfo; - private readonly MethodInfo _methodInfoHelp; - - public CommandHandler( - CommandAttribute commandAttribute, - IEnumerable<(PropertyInfo, Argument)> arguments, - IEnumerable<(PropertyInfo, Option)> properties, - Type type, - Func factory) - { - _commandAttribute = commandAttribute; - _arguments = arguments; - _properties = properties; - _factory = factory; - - _methodInfo = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute() != null).SingleOrDefault() ?? - throw new ArgumentException($"No command invoke method found in {type}"); - - _methodInfoHelp = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute() != null).SingleOrDefault(); - } - - Task ICommandHandler.InvokeAsync(InvocationContext context) - { - return Task.FromException(new NotImplementedException()); - } - - /// - /// Returns the command name - /// - internal string Name => _commandAttribute.Name; - - /// - /// Returns the command's help text - /// - internal string Help => _commandAttribute.Help; - - /// - /// Returns the list of the command's aliases. - /// - internal IEnumerable Aliases => _commandAttribute.Aliases; - - /// - /// Returns true if the command should be added. - /// - internal bool IsValidPlatform(ITarget target) - { - if ((_commandAttribute.Platform & CommandPlatform.Global) != 0) - { - return true; - } - if (target != null) - { - if (target.OperatingSystem == OSPlatform.Windows) - { - return (_commandAttribute.Platform & CommandPlatform.Windows) != 0; - } - if (target.OperatingSystem == OSPlatform.Linux) - { - return (_commandAttribute.Platform & CommandPlatform.Linux) != 0; - } - if (target.OperatingSystem == OSPlatform.OSX) - { - return (_commandAttribute.Platform & CommandPlatform.OSX) != 0; - } - } - return false; - } - - /// - /// Execute the command synchronously. - /// - /// invocation context - /// service provider - internal void Invoke(InvocationContext context, IServiceProvider services) => Invoke(_methodInfo, context, context.Parser, services); - - /// - /// Executes the command's help invoke function if exists - /// - /// parser instance - /// service provider - /// true help called, false no help function - internal bool InvokeHelp(Parser parser, IServiceProvider services) - { - if (_methodInfoHelp == null) { - return false; - } - // The InvocationContext is null so the options and arguments in the - // command instance created are not set. The context for the command - // requesting help (either the help command or some other command using - // --help) won't work for the command instance that implements it's own - // help (SOS command). - Invoke(_methodInfoHelp, context: null, parser, services); - return true; - } - - private void Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services) - { - object instance = _factory(services); - SetProperties(context, parser, services, instance); - - object[] arguments = BuildArguments(methodInfo, services); - methodInfo.Invoke(instance, arguments); - } - - private void SetProperties(InvocationContext context, Parser parser, IServiceProvider services, object instance) - { - ParseResult defaultParseResult = null; - - // Parse the default options if any - string defaultOptions = _commandAttribute.DefaultOptions; - if (defaultOptions != null) { - defaultParseResult = parser.Parse(Name + " " + defaultOptions); - } - - // Now initialize the option and service properties from the default and command line options - foreach ((PropertyInfo Property, Option Option) property in _properties) - { - object value = property.Property.GetValue(instance); - - if (property.Option != null) - { - if (defaultParseResult != null) - { - OptionResult defaultOptionResult = defaultParseResult.FindResultFor(property.Option); - if (defaultOptionResult != null) { - value = defaultOptionResult.GetValueOrDefault(); - } - } - if (context != null) - { - OptionResult optionResult = context.ParseResult.FindResultFor(property.Option); - if (optionResult != null) { - value = optionResult.GetValueOrDefault(); - } - } - } - else - { - Type propertyType = property.Property.PropertyType; - object service = services.GetService(propertyType); - if (service != null) { - value = service; - } - } - - property.Property.SetValue(instance, value); - } - - // Initialize any argument properties from the default and command line arguments - foreach ((PropertyInfo Property, Argument Argument) argument in _arguments) - { - object value = argument.Property.GetValue(instance); - - List array = null; - if (argument.Property.PropertyType.IsArray && argument.Property.PropertyType.GetElementType() == typeof(string)) - { - array = new List(); - if (value is IEnumerable entries) { - array.AddRange(entries); - } - } - - if (defaultParseResult != null) - { - ArgumentResult defaultArgumentResult = defaultParseResult.FindResultFor(argument.Argument); - if (defaultArgumentResult != null) - { - value = defaultArgumentResult.GetValueOrDefault(); - if (array != null && value is IEnumerable entries) { - array.AddRange(entries); - } - } - } - if (context != null) - { - ArgumentResult argumentResult = context.ParseResult.FindResultFor(argument.Argument); - if (argumentResult != null) - { - value = argumentResult.GetValueOrDefault(); - if (array != null && value is IEnumerable entries) { - array.AddRange(entries); - } - } - } - - argument.Property.SetValue(instance, array != null ? array.ToArray() : value); - } - } - - private object[] BuildArguments(MethodBase methodBase, IServiceProvider services) - { - ParameterInfo[] parameters = methodBase.GetParameters(); - object[] arguments = new object[parameters.Length]; - for (int i = 0; i < parameters.Length; i++) - { - Type parameterType = parameters[i].ParameterType; - - // The parameter will passed as null to allow for "optional" services. The invoked - // method needs to check for possible null parameters. - arguments[i] = services.GetService(parameterType); - } - return arguments; - } - } - - /// - /// Local help builder that allows commands to provide more detailed help - /// text via the "InvokeHelp" function. - /// - class LocalHelpBuilder : IHelpBuilder - { - private readonly CommandProcessor _commandProcessor; - private readonly IConsole _console; - private readonly bool _useHelpBuilder; - - public LocalHelpBuilder(CommandProcessor commandProcessor, IConsole console, bool useHelpBuilder) - { - _commandProcessor = commandProcessor; - _console = console; - _useHelpBuilder = useHelpBuilder; - } - - void IHelpBuilder.Write(ICommand command) - { - bool useHelpBuilder = _useHelpBuilder; - if (_commandProcessor._commandHandlers.TryGetValue(command.Name, out CommandHandler handler)) - { - if (handler.InvokeHelp(_commandProcessor._parser, LocalConsole.ToServices(_console))) { - return; - } - useHelpBuilder = true; - } - if (useHelpBuilder) - { - var helpBuilder = new HelpBuilder(_console, maxWidth: Console.WindowWidth); - helpBuilder.Write(command); - } - } - } - - /// - /// This class does two things: wraps the IConsoleService and provides the IConsole interface and - /// pipes through the System.CommandLine parsing allowing per command invocation data (service - /// provider and raw command line) to be passed through. - /// - class LocalConsole : IConsole - { - public static IServiceProvider ToServices(IConsole console) => ((LocalConsole)console).Services; - - public readonly IServiceProvider Services; - - private readonly IConsoleService _console; - - public LocalConsole(IServiceProvider services) - { - Services = services; - _console = services.GetService(); - Debug.Assert(_console != null); - Out = new StandardStreamWriter((text) => _console.Write(text)); - Error = new StandardStreamWriter((text) => _console.WriteError(text)); - } - - #region IConsole - - public IStandardStreamWriter Out { get; } - - bool IStandardOut.IsOutputRedirected { get { return false; } } - - public IStandardStreamWriter Error { get; } - - bool IStandardError.IsErrorRedirected { get { return false; } } - - bool IStandardIn.IsInputRedirected { get { return false; } } - - class StandardStreamWriter : IStandardStreamWriter - { - readonly Action _write; - - public StandardStreamWriter(Action write) => _write = write; - - void IStandardStreamWriter.Write(string value) => _write(value); - } - - #endregion - } - } -} diff --git a/src/Microsoft.Diagnostics.Repl/Command/HelpCommand.cs b/src/Microsoft.Diagnostics.Repl/Command/HelpCommand.cs deleted file mode 100644 index 309a7868b..000000000 --- a/src/Microsoft.Diagnostics.Repl/Command/HelpCommand.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Diagnostics.DebugServices; -using System; -using System.CommandLine; -using System.CommandLine.Help; - -namespace Microsoft.Diagnostics.Repl -{ - [Command(Name = "help", Help = "Display help for a command.", Platform = CommandPlatform.Global)] - public class HelpCommand : CommandBase - { - [Argument(Help = "Command to find help.")] - public string Command { get; set; } - - private readonly CommandProcessor _commandProcessor; - private readonly IServiceProvider _services; - - public HelpCommand(CommandProcessor commandProcessor, IServiceProvider services) - { - _commandProcessor = commandProcessor; - _services = services; - } - - public override void Invoke() - { - if (!_commandProcessor.DisplayHelp(Command, _services)) - { - throw new NotSupportedException($"Help for {Command} not found"); - } - } - } -} diff --git a/src/Microsoft.Diagnostics.Repl/Console/CharToLineConverter.cs b/src/Microsoft.Diagnostics.Repl/Console/CharToLineConverter.cs deleted file mode 100644 index 323b27bbd..000000000 --- a/src/Microsoft.Diagnostics.Repl/Console/CharToLineConverter.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Text; - -namespace Microsoft.Diagnostics.Repl -{ - public sealed class CharToLineConverter - { - readonly Action m_callback; - readonly StringBuilder m_text = new StringBuilder(); - - public CharToLineConverter(Action callback) - { - m_callback = callback; - } - - public void Input(byte[] buffer, int offset, int count) - { - for (int i = 0; i < count; i++) { - char c = (char)buffer[offset + i]; - if (c == '\r') { - continue; - } - if (c == '\n') { - Flush(); - } - else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) { - m_text.Append(c); - } - } - } - - public void Input(string text) - { - foreach (char c in text) { - if (c == '\r') { - continue; - } - if (c == '\n') { - Flush(); - } - else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) { - m_text.Append(c); - } - } - } - - public void Flush() - { - m_callback(m_text.ToString()); - m_text.Clear(); - } - } -} diff --git a/src/Microsoft.Diagnostics.Repl/Console/ConsoleProvider.cs b/src/Microsoft.Diagnostics.Repl/Console/ConsoleProvider.cs deleted file mode 100644 index 2272d2ec4..000000000 --- a/src/Microsoft.Diagnostics.Repl/Console/ConsoleProvider.cs +++ /dev/null @@ -1,556 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Diagnostics.DebugServices; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Microsoft.Diagnostics.Repl -{ - public sealed class ConsoleProvider : IConsoleService - { - private readonly List m_history; - - private readonly CharToLineConverter m_consoleConverter; - private readonly CharToLineConverter m_warningConverter; - private readonly CharToLineConverter m_errorConverter; - - private string m_prompt = "> "; - - private bool m_shutdown; - private CancellationTokenSource m_interruptExecutingCommand; - - private string m_clearLine; - private bool m_interactiveConsole; - private bool m_outputRedirected; - private bool m_refreshingLine; - private StringBuilder m_activeLine; - - private int m_selectedHistory; - - private bool m_modified; - private int m_cursorPosition; - private int m_scrollPosition; - private bool m_insertMode; - - private int m_commandExecuting; - private string m_lastCommandLine; - - /// - /// Create an instance of the console provider - /// - /// error color (default red) - /// warning color (default yellow) - public ConsoleProvider(ConsoleColor errorColor = ConsoleColor.Red, ConsoleColor warningColor = ConsoleColor.Yellow) - { - m_history = new List(); - m_activeLine = new StringBuilder(); - m_shutdown = false; - - m_consoleConverter = new CharToLineConverter(text => { - NewOutput(text); - }); - - m_warningConverter = new CharToLineConverter(text => { - NewOutput(text, warningColor); - }); - - m_errorConverter = new CharToLineConverter(text => { - NewOutput(text, errorColor); - }); - - // Hook ctrl-C and ctrl-break - Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCtrlBreakKeyPress); - } - - /// - /// Start input processing and command dispatching - /// - /// Called to dispatch a command on ENTER - public void Start(Action dispatchCommand) - { - m_lastCommandLine = null; - m_interactiveConsole = !Console.IsInputRedirected; - m_outputRedirected = Console.IsOutputRedirected; - RefreshLine(); - - // The special prompts for the test runner are built into this - // console provider when the output has been redirected. - if (!m_interactiveConsole) { - WriteLine(OutputType.Normal, ""); - } - - // Start keyboard processing - while (!m_shutdown) { - if (m_interactiveConsole) - { - ConsoleKeyInfo keyInfo = Console.ReadKey(true); - ProcessKeyInfo(keyInfo, dispatchCommand); - } - else - { - // The input has been redirected (i.e. testing or in script) - string line = Console.ReadLine(); - if (string.IsNullOrEmpty(line)) { - continue; - } - bool result = Dispatch(line, dispatchCommand); - if (!m_shutdown) - { - if (result) { - WriteLine(OutputType.Normal, ""); - } - else { - WriteLine(OutputType.Normal, ""); - } - } - } - } - } - - public bool Shutdown { get { return m_shutdown; } } - - /// - /// Stop input processing/dispatching - /// - public void Stop() - { - ClearLine(); - m_shutdown = true; - // Delete the last command (usually q or exit) that caused Stop() to be - // called so the history doesn't fill up with exit commands. - if (m_selectedHistory > 0) { - m_history.RemoveAt(--m_selectedHistory); - } - Console.CancelKeyPress -= new ConsoleCancelEventHandler(OnCtrlBreakKeyPress); - } - - /// - /// Returns the current command history for serialization. - /// - public IEnumerable GetCommandHistory() - { - return m_history.Select((sb) => sb.ToString()); - } - - /// - /// Adds the command history. - /// - /// command history strings to add - public void AddCommandHistory(IEnumerable commandHistory) - { - m_history.AddRange(commandHistory.Select((s) => new StringBuilder(s))); - m_selectedHistory = m_history.Count; - } - - /// - /// Change the command prompt - /// - /// new prompt - public void SetPrompt(string prompt) - { - m_prompt = prompt; - RefreshLine(); - } - - /// - /// Writes a message with a new line to console. - /// - public void WriteLine(string format, params object[] parameters) - { - WriteLine(OutputType.Normal, format, parameters); - } - - /// - /// Writes a message with a new line to console. - /// - public void WriteLine(OutputType type, string format, params object[] parameters) - { - WriteOutput(type, string.Format(format, parameters) + Environment.NewLine); - } - - /// - /// Write text on the console screen - /// - /// output type - /// text - /// ctrl-c interrupted the command - public void WriteOutput(OutputType type, string message) - { - switch (type) - { - case OutputType.Normal: - m_consoleConverter.Input(message); - break; - - case OutputType.Warning: - m_warningConverter.Input(message); - break; - - case OutputType.Error: - m_errorConverter.Input(message); - break; - } - } - - /// - /// Clear the console screen - /// - public void ClearScreen() - { - Console.Clear(); - PrintActiveLine(); - } - - /// - /// Write a line to the console. - /// - /// line of text - /// color of the text - private void NewOutput(string text, ConsoleColor? color = null) - { - ClearLine(); - - ConsoleColor? originalColor = null; - if (color.HasValue) { - originalColor = Console.ForegroundColor; - Console.ForegroundColor = color.Value; - } - Console.WriteLine(text); - if (originalColor.HasValue) { - Console.ForegroundColor = originalColor.Value; - } - - PrintActiveLine(); - } - - /// - /// This is the ctrl-c/ctrl-break handler - /// - private void OnCtrlBreakKeyPress(object sender, ConsoleCancelEventArgs e) - { - if (!m_shutdown && m_interactiveConsole) { - if (m_interruptExecutingCommand != null) { - m_interruptExecutingCommand.Cancel(); - } - e.Cancel = true; - } - } - - private void CommandStarting() - { - if (m_commandExecuting == 0) { - ClearLine(); - } - m_commandExecuting++; - } - - private void CommandFinished() - { - if (--m_commandExecuting == 0) { - RefreshLine(); - } - } - - private void ClearLine() - { - if (!m_interactiveConsole) { - return; - } - - if (m_commandExecuting != 0) { - return; - } - - if (m_clearLine == null || m_clearLine.Length != Console.WindowWidth) { - m_clearLine = "\r" + new string(' ', Console.WindowWidth - 1); - } - - Console.Write(m_clearLine); - - if (!m_outputRedirected) { - Console.CursorLeft = 0; - } - } - - private void PrintActiveLine() - { - if (!m_interactiveConsole) { - return; - } - - if (m_shutdown) { - return; - } - - if (m_commandExecuting != 0) { - return; - } - - string prompt = m_prompt; - - int lineWidth = 80; - if (Console.WindowWidth > prompt.Length) { - lineWidth = Console.WindowWidth - prompt.Length - 1; - } - int scrollIncrement = lineWidth / 3; - - int activeLineLen = m_activeLine.Length; - - m_scrollPosition = Math.Min(Math.Max(m_scrollPosition, 0), activeLineLen); - m_cursorPosition = Math.Min(Math.Max(m_cursorPosition, 0), activeLineLen); - - while (m_cursorPosition < m_scrollPosition) { - m_scrollPosition = Math.Max(m_scrollPosition - scrollIncrement, 0); - } - - while (m_cursorPosition - m_scrollPosition > lineWidth - 5) { - m_scrollPosition += scrollIncrement; - } - - int lineRest = activeLineLen - m_scrollPosition; - int max = Math.Min(lineRest, lineWidth); - string text = m_activeLine.ToString(m_scrollPosition, max); - - Console.Write("{0}{1}", prompt, text); - - if (!m_outputRedirected) { - Console.CursorLeft = prompt.Length + (m_cursorPosition - m_scrollPosition); - } - } - - private void RefreshLine() - { - // Check for recursions. - if (m_refreshingLine) { - return; - } - m_refreshingLine = true; - ClearLine(); - PrintActiveLine(); - m_refreshingLine = false; - } - - private void ProcessKeyInfo(ConsoleKeyInfo keyInfo, Action dispatchCommand) - { - int activeLineLen = m_activeLine.Length; - - switch (keyInfo.Key) { - case ConsoleKey.Backspace: // The BACKSPACE key. - if (m_cursorPosition > 0) { - EnsureNewEntry(); - m_activeLine.Remove(m_cursorPosition - 1, 1); - m_cursorPosition--; - RefreshLine(); - } - break; - - case ConsoleKey.Insert: // The INS (INSERT) key. - m_insertMode = !m_insertMode; - RefreshLine(); - break; - - case ConsoleKey.Delete: // The DEL (DELETE) key. - if (m_cursorPosition < activeLineLen) { - EnsureNewEntry(); - m_activeLine.Remove(m_cursorPosition, 1); - RefreshLine(); - } - break; - - case ConsoleKey.Enter: // The ENTER key. - string newCommand = m_activeLine.ToString(); - - if (m_modified) { - m_history.Add(m_activeLine); - } - m_selectedHistory = m_history.Count; - - Dispatch(newCommand, dispatchCommand); - - SwitchToHistoryEntry(); - break; - - case ConsoleKey.Escape: // The ESC (ESCAPE) key. - EnsureNewEntry(); - m_activeLine.Clear(); - m_cursorPosition = 0; - RefreshLine(); - break; - - case ConsoleKey.End: // The END key. - m_cursorPosition = activeLineLen; - RefreshLine(); - break; - - case ConsoleKey.Home: // The HOME key. - m_cursorPosition = 0; - RefreshLine(); - break; - - case ConsoleKey.LeftArrow: // The LEFT ARROW key. - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - while (m_cursorPosition > 0 && char.IsWhiteSpace(m_activeLine[m_cursorPosition - 1])) { - m_cursorPosition--; - } - - while (m_cursorPosition > 0 && !char.IsWhiteSpace(m_activeLine[m_cursorPosition - 1])) { - m_cursorPosition--; - } - } - else { - m_cursorPosition--; - } - - RefreshLine(); - break; - - case ConsoleKey.UpArrow: // The UP ARROW key. - if (m_selectedHistory > 0) { - m_selectedHistory--; - } - SwitchToHistoryEntry(); - break; - - case ConsoleKey.RightArrow: // The RIGHT ARROW key. - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - while (m_cursorPosition < activeLineLen && !char.IsWhiteSpace(m_activeLine[m_cursorPosition])) { - m_cursorPosition++; - } - - while (m_cursorPosition < activeLineLen && char.IsWhiteSpace(m_activeLine[m_cursorPosition])) { - m_cursorPosition++; - } - } - else { - m_cursorPosition++; - } - - RefreshLine(); - break; - - case ConsoleKey.DownArrow: // The DOWN ARROW key. - if (m_selectedHistory < m_history.Count) { - m_selectedHistory++; - } - SwitchToHistoryEntry(); - - RefreshLine(); - break; - - default: - if (keyInfo.KeyChar != 0) { - if ((keyInfo.Modifiers & (ConsoleModifiers.Control | ConsoleModifiers.Alt)) == 0) { - AppendNewText(new string(keyInfo.KeyChar, 1)); - } - } - break; - } - } - - private bool Dispatch(string newCommand, Action dispatchCommand) - { - bool result = true; - CommandStarting(); - m_interruptExecutingCommand = new CancellationTokenSource(); - ((IConsoleService)this).CancellationToken = m_interruptExecutingCommand.Token; - try - { - newCommand = newCommand.Trim(); - if (string.IsNullOrEmpty(newCommand) && m_lastCommandLine != null) { - newCommand = m_lastCommandLine; - } - try - { - WriteLine(OutputType.Normal, "{0}{1}", m_prompt, newCommand); - dispatchCommand(newCommand, m_interruptExecutingCommand.Token); - m_lastCommandLine = newCommand; - } - catch (OperationCanceledException) - { - // ctrl-c interrupted the command - m_lastCommandLine = null; - } - catch (Exception ex) when (!(ex is NullReferenceException || ex is ArgumentNullException || ex is ArgumentException)) - { - WriteLine(OutputType.Error, "ERROR: {0}", ex.Message); - Trace.TraceError(ex.ToString()); - m_lastCommandLine = null; - result = false; - } - } - finally - { - m_interruptExecutingCommand = null; - CommandFinished(); - } - return result; - } - - private void AppendNewText(string text) - { - EnsureNewEntry(); - - foreach (char c in text) { - // Filter unwanted characters. - switch (c) { - case '\t': - case '\r': - case '\n': - continue; - } - - if (m_insertMode && m_cursorPosition < m_activeLine.Length) { - m_activeLine[m_cursorPosition] = c; - } - else { - m_activeLine.Insert(m_cursorPosition, c); - } - m_modified = true; - m_cursorPosition++; - } - - RefreshLine(); - } - - private void SwitchToHistoryEntry() - { - if (m_selectedHistory < m_history.Count) { - m_activeLine = m_history[m_selectedHistory]; - } - else { - m_activeLine = new StringBuilder(); - } - - m_cursorPosition = m_activeLine.Length; - m_modified = false; - - RefreshLine(); - } - - private void EnsureNewEntry() - { - if (!m_modified) { - m_activeLine = new StringBuilder(m_activeLine.ToString()); - m_modified = true; - } - } - - #region IConsoleService - - void IConsoleService.Write(string text) => WriteOutput(OutputType.Normal, text); - - void IConsoleService.WriteWarning(string text) => WriteOutput(OutputType.Warning, text); - - void IConsoleService.WriteError(string text) => WriteOutput(OutputType.Error, text); - - CancellationToken IConsoleService.CancellationToken { get; set; } - - #endregion - } -} diff --git a/src/Microsoft.Diagnostics.Repl/Console/ExitCommand.cs b/src/Microsoft.Diagnostics.Repl/Console/ExitCommand.cs deleted file mode 100644 index c7678c3f1..000000000 --- a/src/Microsoft.Diagnostics.Repl/Console/ExitCommand.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Diagnostics.DebugServices; -using System; - -namespace Microsoft.Diagnostics.Repl -{ - [Command(Name = "exit", Aliases = new string[] { "quit", "q" }, Help = "Exit interactive mode.", Platform = CommandPlatform.Global)] - public class ExitCommand : CommandBase - { - private readonly Action _exit; - - public ExitCommand(Action exit) - { - _exit = exit; - } - - public override void Invoke() - { - _exit(); - } - } -} diff --git a/src/Microsoft.Diagnostics.Repl/Console/OutputType.cs b/src/Microsoft.Diagnostics.Repl/Console/OutputType.cs deleted file mode 100644 index 53fb714f8..000000000 --- a/src/Microsoft.Diagnostics.Repl/Console/OutputType.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Diagnostics.Repl -{ - /// - /// The type of output. - /// - public enum OutputType - { - Normal = 1, - Error = 2, - Warning = 3, - } -} diff --git a/src/Microsoft.Diagnostics.Repl/ConsoleService.cs b/src/Microsoft.Diagnostics.Repl/ConsoleService.cs new file mode 100644 index 000000000..c2f96afdd --- /dev/null +++ b/src/Microsoft.Diagnostics.Repl/ConsoleService.cs @@ -0,0 +1,556 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Diagnostics.DebugServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Microsoft.Diagnostics.Repl +{ + public sealed class ConsoleService : IConsoleService + { + private readonly List m_history; + + private readonly CharToLineConverter m_consoleConverter; + private readonly CharToLineConverter m_warningConverter; + private readonly CharToLineConverter m_errorConverter; + + private string m_prompt = "> "; + + private bool m_shutdown; + private CancellationTokenSource m_interruptExecutingCommand; + + private string m_clearLine; + private bool m_interactiveConsole; + private bool m_outputRedirected; + private bool m_refreshingLine; + private StringBuilder m_activeLine; + + private int m_selectedHistory; + + private bool m_modified; + private int m_cursorPosition; + private int m_scrollPosition; + private bool m_insertMode; + + private int m_commandExecuting; + private string m_lastCommandLine; + + /// + /// Create an instance of the console provider + /// + /// error color (default red) + /// warning color (default yellow) + public ConsoleService(ConsoleColor errorColor = ConsoleColor.Red, ConsoleColor warningColor = ConsoleColor.Yellow) + { + m_history = new List(); + m_activeLine = new StringBuilder(); + m_shutdown = false; + + m_consoleConverter = new CharToLineConverter(text => { + NewOutput(text); + }); + + m_warningConverter = new CharToLineConverter(text => { + NewOutput(text, warningColor); + }); + + m_errorConverter = new CharToLineConverter(text => { + NewOutput(text, errorColor); + }); + + // Hook ctrl-C and ctrl-break + Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCtrlBreakKeyPress); + } + + /// + /// Start input processing and command dispatching + /// + /// Called to dispatch a command on ENTER + public void Start(Action dispatchCommand) + { + m_lastCommandLine = null; + m_interactiveConsole = !Console.IsInputRedirected; + m_outputRedirected = Console.IsOutputRedirected; + RefreshLine(); + + // The special prompts for the test runner are built into this + // console provider when the output has been redirected. + if (!m_interactiveConsole) { + WriteLine(OutputType.Normal, ""); + } + + // Start keyboard processing + while (!m_shutdown) { + if (m_interactiveConsole) + { + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + ProcessKeyInfo(keyInfo, dispatchCommand); + } + else + { + // The input has been redirected (i.e. testing or in script) + string line = Console.ReadLine(); + if (string.IsNullOrEmpty(line)) { + continue; + } + bool result = Dispatch(line, dispatchCommand); + if (!m_shutdown) + { + if (result) { + WriteLine(OutputType.Normal, ""); + } + else { + WriteLine(OutputType.Normal, ""); + } + } + } + } + } + + public bool Shutdown { get { return m_shutdown; } } + + /// + /// Stop input processing/dispatching + /// + public void Stop() + { + ClearLine(); + m_shutdown = true; + // Delete the last command (usually q or exit) that caused Stop() to be + // called so the history doesn't fill up with exit commands. + if (m_selectedHistory > 0) { + m_history.RemoveAt(--m_selectedHistory); + } + Console.CancelKeyPress -= new ConsoleCancelEventHandler(OnCtrlBreakKeyPress); + } + + /// + /// Returns the current command history for serialization. + /// + public IEnumerable GetCommandHistory() + { + return m_history.Select((sb) => sb.ToString()); + } + + /// + /// Adds the command history. + /// + /// command history strings to add + public void AddCommandHistory(IEnumerable commandHistory) + { + m_history.AddRange(commandHistory.Select((s) => new StringBuilder(s))); + m_selectedHistory = m_history.Count; + } + + /// + /// Change the command prompt + /// + /// new prompt + public void SetPrompt(string prompt) + { + m_prompt = prompt; + RefreshLine(); + } + + /// + /// Writes a message with a new line to console. + /// + public void WriteLine(string format, params object[] parameters) + { + WriteLine(OutputType.Normal, format, parameters); + } + + /// + /// Writes a message with a new line to console. + /// + public void WriteLine(OutputType type, string format, params object[] parameters) + { + WriteOutput(type, string.Format(format, parameters) + Environment.NewLine); + } + + /// + /// Write text on the console screen + /// + /// output type + /// text + /// ctrl-c interrupted the command + public void WriteOutput(OutputType type, string message) + { + switch (type) + { + case OutputType.Normal: + m_consoleConverter.Input(message); + break; + + case OutputType.Warning: + m_warningConverter.Input(message); + break; + + case OutputType.Error: + m_errorConverter.Input(message); + break; + } + } + + /// + /// Clear the console screen + /// + public void ClearScreen() + { + Console.Clear(); + PrintActiveLine(); + } + + /// + /// Write a line to the console. + /// + /// line of text + /// color of the text + private void NewOutput(string text, ConsoleColor? color = null) + { + ClearLine(); + + ConsoleColor? originalColor = null; + if (color.HasValue) { + originalColor = Console.ForegroundColor; + Console.ForegroundColor = color.Value; + } + Console.WriteLine(text); + if (originalColor.HasValue) { + Console.ForegroundColor = originalColor.Value; + } + + PrintActiveLine(); + } + + /// + /// This is the ctrl-c/ctrl-break handler + /// + private void OnCtrlBreakKeyPress(object sender, ConsoleCancelEventArgs e) + { + if (!m_shutdown && m_interactiveConsole) { + if (m_interruptExecutingCommand != null) { + m_interruptExecutingCommand.Cancel(); + } + e.Cancel = true; + } + } + + private void CommandStarting() + { + if (m_commandExecuting == 0) { + ClearLine(); + } + m_commandExecuting++; + } + + private void CommandFinished() + { + if (--m_commandExecuting == 0) { + RefreshLine(); + } + } + + private void ClearLine() + { + if (!m_interactiveConsole) { + return; + } + + if (m_commandExecuting != 0) { + return; + } + + if (m_clearLine == null || m_clearLine.Length != Console.WindowWidth) { + m_clearLine = "\r" + new string(' ', Console.WindowWidth - 1); + } + + Console.Write(m_clearLine); + + if (!m_outputRedirected) { + Console.CursorLeft = 0; + } + } + + private void PrintActiveLine() + { + if (!m_interactiveConsole) { + return; + } + + if (m_shutdown) { + return; + } + + if (m_commandExecuting != 0) { + return; + } + + string prompt = m_prompt; + + int lineWidth = 80; + if (Console.WindowWidth > prompt.Length) { + lineWidth = Console.WindowWidth - prompt.Length - 1; + } + int scrollIncrement = lineWidth / 3; + + int activeLineLen = m_activeLine.Length; + + m_scrollPosition = Math.Min(Math.Max(m_scrollPosition, 0), activeLineLen); + m_cursorPosition = Math.Min(Math.Max(m_cursorPosition, 0), activeLineLen); + + while (m_cursorPosition < m_scrollPosition) { + m_scrollPosition = Math.Max(m_scrollPosition - scrollIncrement, 0); + } + + while (m_cursorPosition - m_scrollPosition > lineWidth - 5) { + m_scrollPosition += scrollIncrement; + } + + int lineRest = activeLineLen - m_scrollPosition; + int max = Math.Min(lineRest, lineWidth); + string text = m_activeLine.ToString(m_scrollPosition, max); + + Console.Write("{0}{1}", prompt, text); + + if (!m_outputRedirected) { + Console.CursorLeft = prompt.Length + (m_cursorPosition - m_scrollPosition); + } + } + + private void RefreshLine() + { + // Check for recursions. + if (m_refreshingLine) { + return; + } + m_refreshingLine = true; + ClearLine(); + PrintActiveLine(); + m_refreshingLine = false; + } + + private void ProcessKeyInfo(ConsoleKeyInfo keyInfo, Action dispatchCommand) + { + int activeLineLen = m_activeLine.Length; + + switch (keyInfo.Key) { + case ConsoleKey.Backspace: // The BACKSPACE key. + if (m_cursorPosition > 0) { + EnsureNewEntry(); + m_activeLine.Remove(m_cursorPosition - 1, 1); + m_cursorPosition--; + RefreshLine(); + } + break; + + case ConsoleKey.Insert: // The INS (INSERT) key. + m_insertMode = !m_insertMode; + RefreshLine(); + break; + + case ConsoleKey.Delete: // The DEL (DELETE) key. + if (m_cursorPosition < activeLineLen) { + EnsureNewEntry(); + m_activeLine.Remove(m_cursorPosition, 1); + RefreshLine(); + } + break; + + case ConsoleKey.Enter: // The ENTER key. + string newCommand = m_activeLine.ToString(); + + if (m_modified) { + m_history.Add(m_activeLine); + } + m_selectedHistory = m_history.Count; + + Dispatch(newCommand, dispatchCommand); + + SwitchToHistoryEntry(); + break; + + case ConsoleKey.Escape: // The ESC (ESCAPE) key. + EnsureNewEntry(); + m_activeLine.Clear(); + m_cursorPosition = 0; + RefreshLine(); + break; + + case ConsoleKey.End: // The END key. + m_cursorPosition = activeLineLen; + RefreshLine(); + break; + + case ConsoleKey.Home: // The HOME key. + m_cursorPosition = 0; + RefreshLine(); + break; + + case ConsoleKey.LeftArrow: // The LEFT ARROW key. + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + while (m_cursorPosition > 0 && char.IsWhiteSpace(m_activeLine[m_cursorPosition - 1])) { + m_cursorPosition--; + } + + while (m_cursorPosition > 0 && !char.IsWhiteSpace(m_activeLine[m_cursorPosition - 1])) { + m_cursorPosition--; + } + } + else { + m_cursorPosition--; + } + + RefreshLine(); + break; + + case ConsoleKey.UpArrow: // The UP ARROW key. + if (m_selectedHistory > 0) { + m_selectedHistory--; + } + SwitchToHistoryEntry(); + break; + + case ConsoleKey.RightArrow: // The RIGHT ARROW key. + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + while (m_cursorPosition < activeLineLen && !char.IsWhiteSpace(m_activeLine[m_cursorPosition])) { + m_cursorPosition++; + } + + while (m_cursorPosition < activeLineLen && char.IsWhiteSpace(m_activeLine[m_cursorPosition])) { + m_cursorPosition++; + } + } + else { + m_cursorPosition++; + } + + RefreshLine(); + break; + + case ConsoleKey.DownArrow: // The DOWN ARROW key. + if (m_selectedHistory < m_history.Count) { + m_selectedHistory++; + } + SwitchToHistoryEntry(); + + RefreshLine(); + break; + + default: + if (keyInfo.KeyChar != 0) { + if ((keyInfo.Modifiers & (ConsoleModifiers.Control | ConsoleModifiers.Alt)) == 0) { + AppendNewText(new string(keyInfo.KeyChar, 1)); + } + } + break; + } + } + + private bool Dispatch(string newCommand, Action dispatchCommand) + { + bool result = true; + CommandStarting(); + m_interruptExecutingCommand = new CancellationTokenSource(); + ((IConsoleService)this).CancellationToken = m_interruptExecutingCommand.Token; + try + { + newCommand = newCommand.Trim(); + if (string.IsNullOrEmpty(newCommand) && m_lastCommandLine != null) { + newCommand = m_lastCommandLine; + } + try + { + WriteLine(OutputType.Normal, "{0}{1}", m_prompt, newCommand); + dispatchCommand(newCommand, m_interruptExecutingCommand.Token); + m_lastCommandLine = newCommand; + } + catch (OperationCanceledException) + { + // ctrl-c interrupted the command + m_lastCommandLine = null; + } + catch (Exception ex) when (!(ex is NullReferenceException || ex is ArgumentNullException || ex is ArgumentException)) + { + WriteLine(OutputType.Error, "ERROR: {0}", ex.Message); + Trace.TraceError(ex.ToString()); + m_lastCommandLine = null; + result = false; + } + } + finally + { + m_interruptExecutingCommand = null; + CommandFinished(); + } + return result; + } + + private void AppendNewText(string text) + { + EnsureNewEntry(); + + foreach (char c in text) { + // Filter unwanted characters. + switch (c) { + case '\t': + case '\r': + case '\n': + continue; + } + + if (m_insertMode && m_cursorPosition < m_activeLine.Length) { + m_activeLine[m_cursorPosition] = c; + } + else { + m_activeLine.Insert(m_cursorPosition, c); + } + m_modified = true; + m_cursorPosition++; + } + + RefreshLine(); + } + + private void SwitchToHistoryEntry() + { + if (m_selectedHistory < m_history.Count) { + m_activeLine = m_history[m_selectedHistory]; + } + else { + m_activeLine = new StringBuilder(); + } + + m_cursorPosition = m_activeLine.Length; + m_modified = false; + + RefreshLine(); + } + + private void EnsureNewEntry() + { + if (!m_modified) { + m_activeLine = new StringBuilder(m_activeLine.ToString()); + m_modified = true; + } + } + + #region IConsoleService + + void IConsoleService.Write(string text) => WriteOutput(OutputType.Normal, text); + + void IConsoleService.WriteWarning(string text) => WriteOutput(OutputType.Warning, text); + + void IConsoleService.WriteError(string text) => WriteOutput(OutputType.Error, text); + + CancellationToken IConsoleService.CancellationToken { get; set; } + + #endregion + } +} diff --git a/src/Microsoft.Diagnostics.Repl/ExitCommand.cs b/src/Microsoft.Diagnostics.Repl/ExitCommand.cs new file mode 100644 index 000000000..c7678c3f1 --- /dev/null +++ b/src/Microsoft.Diagnostics.Repl/ExitCommand.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Diagnostics.DebugServices; +using System; + +namespace Microsoft.Diagnostics.Repl +{ + [Command(Name = "exit", Aliases = new string[] { "quit", "q" }, Help = "Exit interactive mode.", Platform = CommandPlatform.Global)] + public class ExitCommand : CommandBase + { + private readonly Action _exit; + + public ExitCommand(Action exit) + { + _exit = exit; + } + + public override void Invoke() + { + _exit(); + } + } +} diff --git a/src/Microsoft.Diagnostics.Repl/HelpCommand.cs b/src/Microsoft.Diagnostics.Repl/HelpCommand.cs new file mode 100644 index 000000000..cbf3699f4 --- /dev/null +++ b/src/Microsoft.Diagnostics.Repl/HelpCommand.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Diagnostics.DebugServices; +using System; + +namespace Microsoft.Diagnostics.Repl +{ + [Command(Name = "help", Help = "Display help for a command.", Platform = CommandPlatform.Global)] + public class HelpCommand : CommandBase + { + [Argument(Help = "Command to find help.")] + public string Command { get; set; } + + private readonly ICommandService _commandService; + private readonly IServiceProvider _services; + + public HelpCommand(ICommandService commandService, IServiceProvider services) + { + _commandService = commandService; + _services = services; + } + + public override void Invoke() + { + if (!_commandService.DisplayHelp(Command, _services)) + { + throw new NotSupportedException($"Help for {Command} not found"); + } + } + } +} diff --git a/src/Microsoft.Diagnostics.Repl/Microsoft.Diagnostics.Repl.csproj b/src/Microsoft.Diagnostics.Repl/Microsoft.Diagnostics.Repl.csproj index 4523fa90d..9016c7419 100644 --- a/src/Microsoft.Diagnostics.Repl/Microsoft.Diagnostics.Repl.csproj +++ b/src/Microsoft.Diagnostics.Repl/Microsoft.Diagnostics.Repl.csproj @@ -12,10 +12,6 @@ false - - - - diff --git a/src/Microsoft.Diagnostics.Repl/OutputType.cs b/src/Microsoft.Diagnostics.Repl/OutputType.cs new file mode 100644 index 000000000..53fb714f8 --- /dev/null +++ b/src/Microsoft.Diagnostics.Repl/OutputType.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Diagnostics.Repl +{ + /// + /// The type of output. + /// + public enum OutputType + { + Normal = 1, + Error = 2, + Warning = 3, + } +} diff --git a/src/SOS/SOS.Extensions/HostServices.cs b/src/SOS/SOS.Extensions/HostServices.cs index 29572e36b..aac039999 100644 --- a/src/SOS/SOS.Extensions/HostServices.cs +++ b/src/SOS/SOS.Extensions/HostServices.cs @@ -5,7 +5,6 @@ using Microsoft.Diagnostics.DebugServices; using Microsoft.Diagnostics.DebugServices.Implementation; using Microsoft.Diagnostics.ExtensionCommands; -using Microsoft.Diagnostics.Repl; using Microsoft.Diagnostics.Runtime; using Microsoft.Diagnostics.Runtime.Utilities; using SOS.Hosting; @@ -38,7 +37,7 @@ namespace SOS.Extensions internal DebuggerServices DebuggerServices { get; private set; } private readonly ServiceProvider _serviceProvider; - private readonly CommandProcessor _commandProcessor; + private readonly CommandService _commandService; private readonly SymbolService _symbolService; private readonly HostWrapper _hostWrapper; private ContextServiceFromDebuggerServices _contextService; @@ -99,12 +98,12 @@ namespace SOS.Extensions { _serviceProvider = new ServiceProvider(); _symbolService = new SymbolService(this); - _commandProcessor = new CommandProcessor(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ">!ext" : null); - _commandProcessor.AddCommands(new Assembly[] { typeof(HostServices).Assembly }); - _commandProcessor.AddCommands(new Assembly[] { typeof(ClrMDHelper).Assembly }); + _commandService = new CommandService(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ">!ext" : null); + _commandService.AddCommands(new Assembly[] { typeof(HostServices).Assembly }); + _commandService.AddCommands(new Assembly[] { typeof(ClrMDHelper).Assembly }); _serviceProvider.AddService(this); - _serviceProvider.AddService(_commandProcessor); + _serviceProvider.AddService(_commandService); _serviceProvider.AddService(_symbolService); _hostWrapper = new HostWrapper(this, () => _targetWrapper); @@ -220,7 +219,7 @@ namespace SOS.Extensions }); // Add each extension command to the native debugger - foreach ((string name, string help, IEnumerable aliases) in _commandProcessor.Commands) + foreach ((string name, string help, IEnumerable aliases) in _commandService.Commands) { hr = DebuggerServices.AddCommand(name, help, aliases); if (hr != HResult.S_OK) @@ -325,7 +324,7 @@ namespace SOS.Extensions } try { - return _commandProcessor.Execute(commandLine, _contextService.Services); + return _commandService.Execute(commandLine, _contextService.Services); } catch (Exception ex) { @@ -340,7 +339,7 @@ namespace SOS.Extensions { try { - if (!_commandProcessor.DisplayHelp(command, _contextService.Services)) + if (!_commandService.DisplayHelp(command, _contextService.Services)) { return HResult.E_INVALIDARG; } diff --git a/src/SOS/SOS.Extensions/SOS.Extensions.csproj b/src/SOS/SOS.Extensions/SOS.Extensions.csproj index b03fef310..fc0ab84f3 100644 --- a/src/SOS/SOS.Extensions/SOS.Extensions.csproj +++ b/src/SOS/SOS.Extensions/SOS.Extensions.csproj @@ -20,7 +20,6 @@ - diff --git a/src/Tools/dotnet-dump/Analyzer.cs b/src/Tools/dotnet-dump/Analyzer.cs index c088ed697..0a1e7e8ae 100644 --- a/src/Tools/dotnet-dump/Analyzer.cs +++ b/src/Tools/dotnet-dump/Analyzer.cs @@ -2,30 +2,29 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Diagnostics.ExtensionCommands; using Microsoft.Diagnostics.DebugServices; using Microsoft.Diagnostics.DebugServices.Implementation; +using Microsoft.Diagnostics.ExtensionCommands; using Microsoft.Diagnostics.Repl; using Microsoft.Diagnostics.Runtime; using SOS.Hosting; using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Security; using System.Threading; using System.Threading.Tasks; -using System.Collections.Generic; -using System.Linq; -using System.Security; -using System.Diagnostics; namespace Microsoft.Diagnostics.Tools.Dump { public class Analyzer : IHost { private readonly ServiceProvider _serviceProvider; - private readonly ConsoleProvider _consoleProvider; - private readonly CommandProcessor _commandProcessor; + private readonly ConsoleService _consoleProvider; + private readonly CommandService _commandService; private readonly SymbolService _symbolService; private readonly ContextService _contextService; private int _targetIdFactory; @@ -36,14 +35,14 @@ namespace Microsoft.Diagnostics.Tools.Dump LoggingCommand.Initialize(); _serviceProvider = new ServiceProvider(); - _consoleProvider = new ConsoleProvider(); - _commandProcessor = new CommandProcessor(); + _consoleProvider = new ConsoleService(); + _commandService = new CommandService(); _symbolService = new SymbolService(this); _contextService = new ContextService(this); _serviceProvider.AddService(this); _serviceProvider.AddService(_consoleProvider); - _serviceProvider.AddService(_commandProcessor); + _serviceProvider.AddService(_commandService); _serviceProvider.AddService(_symbolService); _serviceProvider.AddService(_contextService); _serviceProvider.AddServiceFactory(() => SOSLibrary.Create(this)); @@ -53,11 +52,11 @@ namespace Microsoft.Diagnostics.Tools.Dump return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null; }); - _commandProcessor.AddCommands(new Assembly[] { typeof(Analyzer).Assembly }); - _commandProcessor.AddCommands(new Assembly[] { typeof(ClrMDHelper).Assembly }); - _commandProcessor.AddCommands(new Assembly[] { typeof(SOSHost).Assembly }); - _commandProcessor.AddCommands(typeof(HelpCommand), (services) => new HelpCommand(_commandProcessor, services)); - _commandProcessor.AddCommands(typeof(ExitCommand), (services) => new ExitCommand(_consoleProvider.Stop)); + _commandService.AddCommands(new Assembly[] { typeof(Analyzer).Assembly }); + _commandService.AddCommands(new Assembly[] { typeof(ClrMDHelper).Assembly }); + _commandService.AddCommands(new Assembly[] { typeof(SOSHost).Assembly }); + _commandService.AddCommands(typeof(HelpCommand), (services) => new HelpCommand(_commandService, services)); + _commandService.AddCommands(typeof(ExitCommand), (services) => new ExitCommand(_consoleProvider.Stop)); } public Task Analyze(FileInfo dump_path, string[] command) @@ -112,7 +111,7 @@ namespace Microsoft.Diagnostics.Tools.Dump { foreach (string cmd in command) { - _commandProcessor.Execute(cmd, _contextService.Services); + _commandService.Execute(cmd, _contextService.Services); if (_consoleProvider.Shutdown) { break; } @@ -125,7 +124,7 @@ namespace Microsoft.Diagnostics.Tools.Dump _consoleProvider.WriteLine("Type 'quit' or 'exit' to exit the session."); _consoleProvider.Start((string commandLine, CancellationToken cancellation) => { - _commandProcessor.Execute(commandLine, _contextService.Services); + _commandService.Execute(commandLine, _contextService.Services); }); } } @@ -228,7 +227,7 @@ namespace Microsoft.Diagnostics.Tools.Dump } if (assembly is not null) { - _commandProcessor.AddCommands(assembly); + _commandService.AddCommands(assembly); _consoleProvider.WriteLine($"Extension loaded {extensionPath}"); } }