--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Implements the ICommandService interface using System.CommandLine.
+ /// </summary>
+ public class CommandService : ICommandService
+ {
+ private Parser _parser;
+ private readonly CommandLineBuilder _rootBuilder;
+ private readonly Dictionary<string, CommandHandler> _commandHandlers = new Dictionary<string, CommandHandler>();
+
+ /// <summary>
+ /// Create an instance of the command processor;
+ /// </summary>
+ /// <param name="commandPrompt">command prompted used in help message</param>
+ public CommandService(string commandPrompt = null)
+ {
+ _rootBuilder = new CommandLineBuilder(new Command(commandPrompt ?? ">"));
+ _rootBuilder.UseHelpBuilder((bindingContext) => new LocalHelpBuilder(this, bindingContext.Console, useHelpBuilder: false));
+ }
+
+ /// <summary>
+ /// Parse and execute the command line.
+ /// </summary>
+ /// <param name="commandLine">command line text</param>
+ /// <param name="services">services for the command</param>
+ /// <returns>exit code</returns>
+ 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<ITarget>();
+ 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;
+ }
+
+ /// <summary>
+ /// Displays the help for a command
+ /// </summary>
+ /// <param name="commandName">name of the command or alias</param>
+ /// <param name="services">service provider</param>
+ /// <returns>true if success, false if command not found</returns>
+ public bool DisplayHelp(string commandName, IServiceProvider services)
+ {
+ Command command = null;
+ if (!string.IsNullOrEmpty(commandName))
+ {
+ command = _rootBuilder.Command.Children.OfType<Command>().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<ITarget>();
+ if (!handler.IsValidPlatform(target))
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ ITarget target = services.GetService<ITarget>();
+
+ // 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<Command>())
+ {
+ 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;
+ }
+
+ /// <summary>
+ /// Enumerates all the command's name and help
+ /// </summary>
+ public IEnumerable<(string name, string help, IEnumerable<string> aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases));
+
+ /// <summary>
+ /// Add the commands and aliases attributes found in the type.
+ /// </summary>
+ /// <param name="type">Command type to search</param>
+ /// <param name="factory">function to create command instance</param>
+ public void AddCommands(Type type, Func<IServiceProvider, object> 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<object>());
+ }
+ 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<IServiceProvider, object> 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()}";
+ }
+
+ /// <summary>
+ /// The normal command handler.
+ /// </summary>
+ 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<IServiceProvider, object> _factory;
+ private readonly MethodInfo _methodInfo;
+ private readonly MethodInfo _methodInfoHelp;
+
+ public CommandHandler(
+ CommandAttribute commandAttribute,
+ IEnumerable<(PropertyInfo, Argument)> arguments,
+ IEnumerable<(PropertyInfo, Option)> properties,
+ Type type,
+ Func<IServiceProvider, object> factory)
+ {
+ _commandAttribute = commandAttribute;
+ _arguments = arguments;
+ _properties = properties;
+ _factory = factory;
+
+ _methodInfo = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute<CommandInvokeAttribute>() != null).SingleOrDefault() ??
+ throw new ArgumentException($"No command invoke method found in {type}");
+
+ _methodInfoHelp = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute<HelpInvokeAttribute>() != null).SingleOrDefault();
+ }
+
+ Task<int> ICommandHandler.InvokeAsync(InvocationContext context)
+ {
+ return Task.FromException<int>(new NotImplementedException());
+ }
+
+ /// <summary>
+ /// Returns the command name
+ /// </summary>
+ internal string Name => _commandAttribute.Name;
+
+ /// <summary>
+ /// Returns the command's help text
+ /// </summary>
+ internal string Help => _commandAttribute.Help;
+
+ /// <summary>
+ /// Returns the list of the command's aliases.
+ /// </summary>
+ internal IEnumerable<string> Aliases => _commandAttribute.Aliases;
+
+ /// <summary>
+ /// Returns true if the command should be added.
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// Execute the command synchronously.
+ /// </summary>
+ /// <param name="context">invocation context</param>
+ /// <param name="services">service provider</param>
+ internal void Invoke(InvocationContext context, IServiceProvider services) => Invoke(_methodInfo, context, context.Parser, services);
+
+ /// <summary>
+ /// Executes the command's help invoke function if exists
+ /// </summary>
+ /// <param name="parser">parser instance</param>
+ /// <param name="services">service provider</param>
+ /// <returns>true help called, false no help function</returns>
+ 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<string> array = null;
+ if (argument.Property.PropertyType.IsArray && argument.Property.PropertyType.GetElementType() == typeof(string))
+ {
+ array = new List<string>();
+ if (value is IEnumerable<string> 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<string> 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<string> 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;
+ }
+ }
+
+ /// <summary>
+ /// Local help builder that allows commands to provide more detailed help
+ /// text via the "InvokeHelp" function.
+ /// </summary>
+ 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);
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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<IConsoleService>();
+ 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<string> _write;
+
+ public StandardStreamWriter(Action<string> write) => _write = write;
+
+ void IStandardStreamWriter.Write(string value) => _write(value);
+ }
+
+ #endregion
+ }
+ }
+}
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="$(MicrosoftDiagnosticsRuntimeVersion)" />
<PackageReference Include="Microsoft.SymbolStore" Version="$(MicrosoftSymbolStoreVersion)" />
<PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
+ <PackageReference Include="System.CommandLine" Version="$(SystemCommandLineVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
</ItemGroup>
/// <param name="type">Command type to search</param>
/// <param name="factory">function to create command instance</param>
void AddCommands(Type type, Func<IServiceProvider, object> factory);
+
+ /// <summary>
+ /// Displays the help for a command
+ /// </summary>
+ /// <param name="commandName">name of the command or alias</param>
+ /// <param name="services">service provider</param>
+ /// <returns>true if success, false if command not found</returns>
+ bool DisplayHelp(string commandName, IServiceProvider services);
}
}
--- /dev/null
+// 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<string> m_callback;
+ readonly StringBuilder m_text = new StringBuilder();
+
+ public CharToLineConverter(Action<string> 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();
+ }
+ }
+}
+++ /dev/null
-// 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
-{
- /// <summary>
- /// Implements the ICommandService interface using System.CommandLine.
- /// </summary>
- public class CommandProcessor : ICommandService
- {
- private Parser _parser;
- private readonly CommandLineBuilder _rootBuilder;
- private readonly Dictionary<string, CommandHandler> _commandHandlers = new Dictionary<string, CommandHandler>();
-
- /// <summary>
- /// Create an instance of the command processor;
- /// </summary>
- /// <param name="commandPrompt">command prompted used in help message</param>
- public CommandProcessor(string commandPrompt = null)
- {
- _rootBuilder = new CommandLineBuilder(new Command(commandPrompt ?? ">"));
- _rootBuilder.UseHelpBuilder((bindingContext) => new LocalHelpBuilder(this, bindingContext.Console, useHelpBuilder: false));
- }
-
- /// <summary>
- /// Parse and execute the command line.
- /// </summary>
- /// <param name="commandLine">command line text</param>
- /// <param name="services">services for the command</param>
- /// <returns>exit code</returns>
- 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<ITarget>();
- 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;
- }
-
- /// <summary>
- /// Displays the help for a command
- /// </summary>
- /// <param name="commandName">name of the command or alias</param>
- /// <param name="services">service provider</param>
- /// <returns>true if success, false if command not found</returns>
- public bool DisplayHelp(string commandName, IServiceProvider services)
- {
- Command command = null;
- if (!string.IsNullOrEmpty(commandName))
- {
- command = _rootBuilder.Command.Children.OfType<Command>().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<ITarget>();
- if (!handler.IsValidPlatform(target))
- {
- return false;
- }
- }
- }
- else
- {
- ITarget target = services.GetService<ITarget>();
-
- // 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<Command>())
- {
- 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;
- }
-
- /// <summary>
- /// Enumerates all the command's name and help
- /// </summary>
- public IEnumerable<(string name, string help, IEnumerable<string> aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases));
-
- /// <summary>
- /// Add the commands and aliases attributes found in the type.
- /// </summary>
- /// <param name="type">Command type to search</param>
- /// <param name="factory">function to create command instance</param>
- public void AddCommands(Type type, Func<IServiceProvider, object> 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<object>());
- }
- CreateCommand(baseType, commandAttribute, factory);
- }
- }
-
- // Build parser instance after all the commands and aliases are added
- BuildParser();
- }
-
- private void CreateCommand(Type type, CommandAttribute commandAttribute, Func<IServiceProvider, object> 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()}";
- }
-
- /// <summary>
- /// The normal command handler.
- /// </summary>
- 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<IServiceProvider, object> _factory;
- private readonly MethodInfo _methodInfo;
- private readonly MethodInfo _methodInfoHelp;
-
- public CommandHandler(
- CommandAttribute commandAttribute,
- IEnumerable<(PropertyInfo, Argument)> arguments,
- IEnumerable<(PropertyInfo, Option)> properties,
- Type type,
- Func<IServiceProvider, object> factory)
- {
- _commandAttribute = commandAttribute;
- _arguments = arguments;
- _properties = properties;
- _factory = factory;
-
- _methodInfo = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute<CommandInvokeAttribute>() != null).SingleOrDefault() ??
- throw new ArgumentException($"No command invoke method found in {type}");
-
- _methodInfoHelp = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute<HelpInvokeAttribute>() != null).SingleOrDefault();
- }
-
- Task<int> ICommandHandler.InvokeAsync(InvocationContext context)
- {
- return Task.FromException<int>(new NotImplementedException());
- }
-
- /// <summary>
- /// Returns the command name
- /// </summary>
- internal string Name => _commandAttribute.Name;
-
- /// <summary>
- /// Returns the command's help text
- /// </summary>
- internal string Help => _commandAttribute.Help;
-
- /// <summary>
- /// Returns the list of the command's aliases.
- /// </summary>
- internal IEnumerable<string> Aliases => _commandAttribute.Aliases;
-
- /// <summary>
- /// Returns true if the command should be added.
- /// </summary>
- 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;
- }
-
- /// <summary>
- /// Execute the command synchronously.
- /// </summary>
- /// <param name="context">invocation context</param>
- /// <param name="services">service provider</param>
- internal void Invoke(InvocationContext context, IServiceProvider services) => Invoke(_methodInfo, context, context.Parser, services);
-
- /// <summary>
- /// Executes the command's help invoke function if exists
- /// </summary>
- /// <param name="parser">parser instance</param>
- /// <param name="services">service provider</param>
- /// <returns>true help called, false no help function</returns>
- 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<string> array = null;
- if (argument.Property.PropertyType.IsArray && argument.Property.PropertyType.GetElementType() == typeof(string))
- {
- array = new List<string>();
- if (value is IEnumerable<string> 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<string> 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<string> 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;
- }
- }
-
- /// <summary>
- /// Local help builder that allows commands to provide more detailed help
- /// text via the "InvokeHelp" function.
- /// </summary>
- 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);
- }
- }
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- 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<IConsoleService>();
- 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<string> _write;
-
- public StandardStreamWriter(Action<string> write) => _write = write;
-
- void IStandardStreamWriter.Write(string value) => _write(value);
- }
-
- #endregion
- }
- }
-}
+++ /dev/null
-// 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");
- }
- }
- }
-}
+++ /dev/null
-// 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<string> m_callback;
- readonly StringBuilder m_text = new StringBuilder();
-
- public CharToLineConverter(Action<string> 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();
- }
- }
-}
+++ /dev/null
-// 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<StringBuilder> 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;
-
- /// <summary>
- /// Create an instance of the console provider
- /// </summary>
- /// <param name="errorColor">error color (default red)</param>
- /// <param name="warningColor">warning color (default yellow)</param>
- public ConsoleProvider(ConsoleColor errorColor = ConsoleColor.Red, ConsoleColor warningColor = ConsoleColor.Yellow)
- {
- m_history = new List<StringBuilder>();
- 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);
- }
-
- /// <summary>
- /// Start input processing and command dispatching
- /// </summary>
- /// <param name="dispatchCommand">Called to dispatch a command on ENTER</param>
- public void Start(Action<string, CancellationToken> 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, "<END_COMMAND_OUTPUT>");
- }
-
- // 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, "<END_COMMAND_OUTPUT>");
- }
- else {
- WriteLine(OutputType.Normal, "<END_COMMAND_ERROR>");
- }
- }
- }
- }
- }
-
- public bool Shutdown { get { return m_shutdown; } }
-
- /// <summary>
- /// Stop input processing/dispatching
- /// </summary>
- 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);
- }
-
- /// <summary>
- /// Returns the current command history for serialization.
- /// </summary>
- public IEnumerable<string> GetCommandHistory()
- {
- return m_history.Select((sb) => sb.ToString());
- }
-
- /// <summary>
- /// Adds the command history.
- /// </summary>
- /// <param name="commandHistory">command history strings to add</param>
- public void AddCommandHistory(IEnumerable<string> commandHistory)
- {
- m_history.AddRange(commandHistory.Select((s) => new StringBuilder(s)));
- m_selectedHistory = m_history.Count;
- }
-
- /// <summary>
- /// Change the command prompt
- /// </summary>
- /// <param name="prompt">new prompt</param>
- public void SetPrompt(string prompt)
- {
- m_prompt = prompt;
- RefreshLine();
- }
-
- /// <summary>
- /// Writes a message with a new line to console.
- /// </summary>
- public void WriteLine(string format, params object[] parameters)
- {
- WriteLine(OutputType.Normal, format, parameters);
- }
-
- /// <summary>
- /// Writes a message with a new line to console.
- /// </summary>
- public void WriteLine(OutputType type, string format, params object[] parameters)
- {
- WriteOutput(type, string.Format(format, parameters) + Environment.NewLine);
- }
-
- /// <summary>
- /// Write text on the console screen
- /// </summary>
- /// <param name="type">output type</param>
- /// <param name="message">text</param>
- /// <exception cref="OperationCanceledException">ctrl-c interrupted the command</exception>
- 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;
- }
- }
-
- /// <summary>
- /// Clear the console screen
- /// </summary>
- public void ClearScreen()
- {
- Console.Clear();
- PrintActiveLine();
- }
-
- /// <summary>
- /// Write a line to the console.
- /// </summary>
- /// <param name="text">line of text</param>
- /// <param name="color">color of the text</param>
- 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();
- }
-
- /// <summary>
- /// This is the ctrl-c/ctrl-break handler
- /// </summary>
- 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<string, CancellationToken> 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<string, CancellationToken> 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
- }
-}
+++ /dev/null
-// 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();
- }
- }
-}
+++ /dev/null
-// 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
-{
- /// <summary>
- /// The type of output.
- /// </summary>
- public enum OutputType
- {
- Normal = 1,
- Error = 2,
- Warning = 3,
- }
-}
--- /dev/null
+// 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<StringBuilder> 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;
+
+ /// <summary>
+ /// Create an instance of the console provider
+ /// </summary>
+ /// <param name="errorColor">error color (default red)</param>
+ /// <param name="warningColor">warning color (default yellow)</param>
+ public ConsoleService(ConsoleColor errorColor = ConsoleColor.Red, ConsoleColor warningColor = ConsoleColor.Yellow)
+ {
+ m_history = new List<StringBuilder>();
+ 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);
+ }
+
+ /// <summary>
+ /// Start input processing and command dispatching
+ /// </summary>
+ /// <param name="dispatchCommand">Called to dispatch a command on ENTER</param>
+ public void Start(Action<string, CancellationToken> 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, "<END_COMMAND_OUTPUT>");
+ }
+
+ // 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, "<END_COMMAND_OUTPUT>");
+ }
+ else {
+ WriteLine(OutputType.Normal, "<END_COMMAND_ERROR>");
+ }
+ }
+ }
+ }
+ }
+
+ public bool Shutdown { get { return m_shutdown; } }
+
+ /// <summary>
+ /// Stop input processing/dispatching
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// Returns the current command history for serialization.
+ /// </summary>
+ public IEnumerable<string> GetCommandHistory()
+ {
+ return m_history.Select((sb) => sb.ToString());
+ }
+
+ /// <summary>
+ /// Adds the command history.
+ /// </summary>
+ /// <param name="commandHistory">command history strings to add</param>
+ public void AddCommandHistory(IEnumerable<string> commandHistory)
+ {
+ m_history.AddRange(commandHistory.Select((s) => new StringBuilder(s)));
+ m_selectedHistory = m_history.Count;
+ }
+
+ /// <summary>
+ /// Change the command prompt
+ /// </summary>
+ /// <param name="prompt">new prompt</param>
+ public void SetPrompt(string prompt)
+ {
+ m_prompt = prompt;
+ RefreshLine();
+ }
+
+ /// <summary>
+ /// Writes a message with a new line to console.
+ /// </summary>
+ public void WriteLine(string format, params object[] parameters)
+ {
+ WriteLine(OutputType.Normal, format, parameters);
+ }
+
+ /// <summary>
+ /// Writes a message with a new line to console.
+ /// </summary>
+ public void WriteLine(OutputType type, string format, params object[] parameters)
+ {
+ WriteOutput(type, string.Format(format, parameters) + Environment.NewLine);
+ }
+
+ /// <summary>
+ /// Write text on the console screen
+ /// </summary>
+ /// <param name="type">output type</param>
+ /// <param name="message">text</param>
+ /// <exception cref="OperationCanceledException">ctrl-c interrupted the command</exception>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Clear the console screen
+ /// </summary>
+ public void ClearScreen()
+ {
+ Console.Clear();
+ PrintActiveLine();
+ }
+
+ /// <summary>
+ /// Write a line to the console.
+ /// </summary>
+ /// <param name="text">line of text</param>
+ /// <param name="color">color of the text</param>
+ 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();
+ }
+
+ /// <summary>
+ /// This is the ctrl-c/ctrl-break handler
+ /// </summary>
+ 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<string, CancellationToken> 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<string, CancellationToken> 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
+ }
+}
--- /dev/null
+// 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();
+ }
+ }
+}
--- /dev/null
+// 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");
+ }
+ }
+ }
+}
<IsShippingPackage>false</IsShippingPackage>
</PropertyGroup>
- <ItemGroup>
- <PackageReference Include="System.CommandLine" Version="$(SystemCommandLineVersion)" />
- </ItemGroup>
-
<ItemGroup>
<ProjectReference Include="..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
</ItemGroup>
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// The type of output.
+ /// </summary>
+ public enum OutputType
+ {
+ Normal = 1,
+ Error = 2,
+ Warning = 3,
+ }
+}
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;
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;
{
_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<IHost>(this);
- _serviceProvider.AddService<ICommandService>(_commandProcessor);
+ _serviceProvider.AddService<ICommandService>(_commandService);
_serviceProvider.AddService<ISymbolService>(_symbolService);
_hostWrapper = new HostWrapper(this, () => _targetWrapper);
});
// Add each extension command to the native debugger
- foreach ((string name, string help, IEnumerable<string> aliases) in _commandProcessor.Commands)
+ foreach ((string name, string help, IEnumerable<string> aliases) in _commandService.Commands)
{
hr = DebuggerServices.AddCommand(name, help, aliases);
if (hr != HResult.S_OK)
}
try
{
- return _commandProcessor.Execute(commandLine, _contextService.Services);
+ return _commandService.Execute(commandLine, _contextService.Services);
}
catch (Exception ex)
{
{
try
{
- if (!_commandProcessor.DisplayHelp(command, _contextService.Services))
+ if (!_commandService.DisplayHelp(command, _contextService.Services))
{
return HResult.E_INVALIDARG;
}
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.DebugServices.Implementation\Microsoft.Diagnostics.DebugServices.Implementation.csproj" />
- <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Repl\Microsoft.Diagnostics.Repl.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.ExtensionCommands\Microsoft.Diagnostics.ExtensionCommands.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\SOS.Hosting\SOS.Hosting.csproj" />
</ItemGroup>
// 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;
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<IHost>(this);
_serviceProvider.AddService<IConsoleService>(_consoleProvider);
- _serviceProvider.AddService<ICommandService>(_commandProcessor);
+ _serviceProvider.AddService<ICommandService>(_commandService);
_serviceProvider.AddService<ISymbolService>(_symbolService);
_serviceProvider.AddService<IContextService>(_contextService);
_serviceProvider.AddServiceFactory<SOSLibrary>(() => SOSLibrary.Create(this));
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<int> Analyze(FileInfo dump_path, string[] command)
{
foreach (string cmd in command)
{
- _commandProcessor.Execute(cmd, _contextService.Services);
+ _commandService.Execute(cmd, _contextService.Services);
if (_consoleProvider.Shutdown) {
break;
}
_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);
});
}
}
}
if (assembly is not null)
{
- _commandProcessor.AddCommands(assembly);
+ _commandService.AddCommands(assembly);
_consoleProvider.WriteLine($"Extension loaded {extensionPath}");
}
}