Standardize on spelling (Diagnostic -> Diagnostics) - rename files (#358)
authorAndrew Au <andrewau@microsoft.com>
Sat, 22 Jun 2019 06:01:50 +0000 (23:01 -0700)
committerGitHub <noreply@github.com>
Sat, 22 Jun 2019 06:01:50 +0000 (23:01 -0700)
22 files changed:
diagnostics.sln
src/Microsoft.Diagnostic.Repl/Command/Attributes.cs [deleted file]
src/Microsoft.Diagnostic.Repl/Command/CommandBase.cs [deleted file]
src/Microsoft.Diagnostic.Repl/Command/CommandProcessor.cs [deleted file]
src/Microsoft.Diagnostic.Repl/Console/CharToLineConverter.cs [deleted file]
src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs [deleted file]
src/Microsoft.Diagnostic.Repl/Console/OutputType.cs [deleted file]
src/Microsoft.Diagnostic.Repl/Microsoft.Diagnostic.Repl.csproj [deleted file]
src/Microsoft.Diagnostics.Repl/Command/Attributes.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.Repl/Command/CommandBase.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.Repl/Console/CharToLineConverter.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.Repl/Console/ConsoleProvider.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.Repl/Console/OutputType.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.Repl/Microsoft.Diagnostics.Repl.csproj [new file with mode: 0644]
src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticHelpers.cs [deleted file]
src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticsHelpers.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticMessageType.cs [deleted file]
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticsMessageType.cs [new file with mode: 0644]
src/Tools/dotnet-counters/dotnet-counters.csproj
src/Tools/dotnet-dump/Commands/SOSCommandForWindows.cs
src/Tools/dotnet-dump/dotnet-dump.csproj

index 94d825e023e0f5f6b13cc7858f2f6025f6d406d6..442e06fb97838ca46e5a43fd5cf2ad2058d168e2 100644 (file)
@@ -37,7 +37,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-sos", "src\Tools\dot
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.Hosting", "src\SOS\SOS.Hosting\SOS.Hosting.csproj", "{ED27F39F-DF5C-4E22-87E0-EC5B5873B503}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostic.Repl", "src\Microsoft.Diagnostic.Repl\Microsoft.Diagnostic.Repl.csproj", "{90CF2633-58F0-44EE-943B-D70207455F20}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Repl", "src\Microsoft.Diagnostics.Repl\Microsoft.Diagnostics.Repl.csproj", "{90CF2633-58F0-44EE-943B-D70207455F20}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-counters", "src\Tools\dotnet-counters\dotnet-counters.csproj", "{2A9B5988-982F-4E26-9E44-D38AC5978C30}"
 EndProject
diff --git a/src/Microsoft.Diagnostic.Repl/Command/Attributes.cs b/src/Microsoft.Diagnostic.Repl/Command/Attributes.cs
deleted file mode 100644 (file)
index b902cd3..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-// --------------------------------------------------------------------
-// 
-// Copyright (c) Microsoft Corporation.  All rights reserved.
-// 
-// --------------------------------------------------------------------
-using System;
-
-namespace Microsoft.Diagnostics.Repl
-{
-    /// <summary>
-    /// Base command option attribute.
-    /// </summary>
-    public class BaseAttribute : Attribute
-    {
-        /// <summary>
-        /// Name of the command
-        /// </summary>
-        public string Name;
-
-        /// <summary>
-        /// Displayed in the help for the command
-        /// </summary>
-        public string Help;
-    }
-
-    /// <summary>
-    /// Marks the class as a Command.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-    public class CommandAttribute : BaseAttribute
-    {
-        /// <summary>
-        /// Sets the value of the CommandBase.AliasExpansion when the command is executed.
-        /// </summary>
-        public string AliasExpansion;
-    }
-
-    /// <summary>
-    /// Adds an alias to the previous command attribute
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-    public class CommandAliasAttribute : BaseAttribute
-    {
-    }
-
-    /// <summary>
-    /// Marks the property as a Option.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Property)]
-    public class OptionAttribute : BaseAttribute
-    {
-    }
-
-    /// <summary>
-    /// Adds an alias to the Option. Help is ignored.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
-    public class OptionAliasAttribute : BaseAttribute
-    {
-    }
-
-    /// <summary>
-    /// Marks the property the command Argument.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Property)]
-    public class ArgumentAttribute : BaseAttribute
-    {
-    }
-
-    /// <summary>
-    /// Marks the function to invoke to execute the command.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Method)]
-    public class CommandInvokeAttribute : Attribute
-    {
-    }
-
-    /// <summary>
-    /// Marks the function to invoke to display alternate help for command.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Method)]
-    public class HelpInvokeAttribute : Attribute
-    {
-    }
-}
diff --git a/src/Microsoft.Diagnostic.Repl/Command/CommandBase.cs b/src/Microsoft.Diagnostic.Repl/Command/CommandBase.cs
deleted file mode 100644 (file)
index 73d4580..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-// --------------------------------------------------------------------
-// 
-// Copyright (c) Microsoft Corporation.  All rights reserved.
-// 
-// --------------------------------------------------------------------
-using System;
-using System.CommandLine;
-using System.CommandLine.Invocation;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Repl
-{
-    /// <summary>
-    /// The common command context
-    /// </summary>
-    public abstract class CommandBase
-    {
-        /// <summary>
-        /// Parser invocation context. Contains the ParseResult, CommandResult, etc. Is null when 
-        /// InvokeAdditionalHelp is called.
-        /// </summary>
-        public InvocationContext InvocationContext { get; set; }
-
-        /// <summary>
-        /// Console instance
-        /// </summary>
-        public IConsole Console { get; set; }
-
-        /// <summary>
-        /// The AliasExpansion value from the CommandAttribute or null if none.
-        /// </summary>
-        public string AliasExpansion { get; set; }
-
-        /// <summary>
-        /// Execute the command
-        /// </summary>
-        [CommandInvoke]
-        public abstract void Invoke();
-
-        /// <summary>
-        /// Display text
-        /// </summary>
-        /// <param name="message">text message</param>
-        protected void WriteLine(string message)
-        {
-            Console.Out.WriteLine(message);
-        }
-
-        /// <summary>
-        /// Display formatted text
-        /// </summary>
-        /// <param name="format">format string</param>
-        /// <param name="args">arguments</param>
-        protected void WriteLine(string format, params object[] args)
-        {
-            Console.Out.WriteLine(string.Format(format, args));
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostic.Repl/Command/CommandProcessor.cs b/src/Microsoft.Diagnostic.Repl/Command/CommandProcessor.cs
deleted file mode 100644 (file)
index c36655c..0000000
+++ /dev/null
@@ -1,377 +0,0 @@
-// --------------------------------------------------------------------
-//
-// Copyright (c) Microsoft Corporation.  All rights reserved.
-//
-// --------------------------------------------------------------------
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.CommandLine;
-using System.CommandLine.Binding;
-using System.CommandLine.Builder;
-using System.CommandLine.Invocation;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Repl
-{
-    public class CommandProcessor
-    {
-        private readonly Parser _parser;
-        private readonly Command _rootCommand;
-        private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
-        private readonly Dictionary<string, Handler> _commandHandlers = new Dictionary<string, Handler>();
-
-        /// <summary>
-        /// Create an instance of the command processor;
-        /// </summary>
-        /// <param name="console">console instance to use for commands</param>
-        /// <param name="assemblies">Optional list of assemblies to look for commands</param>
-        /// <param name="types">Optional list of types to look for commands</param>
-        public CommandProcessor(IConsole console, IEnumerable<Assembly> assemblies = null, IEnumerable<Type> types = null)
-        {
-            Debug.Assert(console != null);
-            Debug.Assert(assemblies != null);
-            _services.Add(typeof(CommandProcessor), this);
-            _services.Add(typeof(IConsole), console);
-            _services.Add(typeof(IHelpBuilder), new LocalHelpBuilder(this));
-            var rootBuilder = new CommandLineBuilder(new Command(">"));
-            rootBuilder.UseHelp()
-                       .UseHelpBuilder((bindingContext) => GetService<IHelpBuilder>())
-                       .UseParseDirective()
-                       .UseSuggestDirective()
-                       .UseParseErrorReporting()
-                       .UseExceptionHandler();
-            if (assemblies != null) {
-                BuildCommands(rootBuilder, assemblies);
-            }
-            if (types != null) {
-                BuildCommands(rootBuilder, types);
-            }
-            _rootCommand = rootBuilder.Command;
-            _parser = rootBuilder.Build();
-        }
-
-        /// <summary>
-        /// Adds a service or context to inject into an command.
-        /// </summary>
-        /// <typeparam name="T">type of service</typeparam>
-        /// <param name="instance">service instance</param>
-        public void AddService<T>(T instance)
-        {
-            AddService(typeof(T), instance);
-        }
-
-        /// <summary>
-        /// Adds a service or context to inject into an command.
-        /// </summary>
-        /// <param name="type">service type</param>
-        /// <param name="instance">service instance</param>
-        public void AddService(Type type, object instance)
-        {
-            _services.Add(type, instance);
-        }
-
-        /// <summary>
-        /// Parse the command line.
-        /// </summary>
-        /// <param name="commandLine">command line txt</param>
-        /// <returns>exit code</returns>
-        public Task<int> Parse(string commandLine)
-        {
-            ParseResult result = _parser.Parse(commandLine);
-            return _parser.InvokeAsync(result, GetService<IConsole>());
-        }
-
-        /// <summary>
-        /// Display all the help or a specific command's help.
-        /// </summary>
-        /// <param name="name">command name or null</param>
-        /// <returns>command instance or null if not found</returns>
-        public Command GetCommand(string name)
-        {
-            if (string.IsNullOrEmpty(name)) {
-                return _rootCommand;
-            }
-            else {
-                return _rootCommand.Children.OfType<Command>().FirstOrDefault((cmd) => name == cmd.Name || cmd.Aliases.Any((alias) => name == alias));
-            }
-        }
-
-        private void BuildCommands(CommandLineBuilder rootBuilder, IEnumerable<Assembly> assemblies)
-        {
-            BuildCommands(rootBuilder, assemblies.SelectMany((assembly) => assembly.GetExportedTypes()));
-        }
-
-        private void BuildCommands(CommandLineBuilder rootBuilder, IEnumerable<Type> types)
-        {
-            foreach (Type type in types)
-            {
-                for (Type baseType = type; baseType != null; baseType = baseType.BaseType)
-                {
-                    if (baseType == typeof(CommandBase)) {
-                        break;
-                    }
-                    BuildCommands(rootBuilder, baseType);
-                }
-            }
-        }
-
-        private void BuildCommands(CommandLineBuilder rootBuilder, Type type)
-        {
-            Command command = null;
-
-            var baseAttributes = (BaseAttribute[])type.GetCustomAttributes(typeof(BaseAttribute), inherit: false);
-            foreach (BaseAttribute baseAttribute in baseAttributes)
-            {
-                if (baseAttribute is CommandAttribute commandAttribute)
-                {
-                    command = new Command(commandAttribute.Name, commandAttribute.Help);
-                    var properties = new List<(PropertyInfo, Option)>();
-                    PropertyInfo argument = null;
-
-                    foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
-                    {
-                        var argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
-                        if (argumentAttribute != null)
-                        {
-                            if (argument != null) {
-                                throw new ArgumentException($"More than one ArgumentAttribute in command class: {type.Name}");
-                            }
-                            IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
-
-                            command.Argument = new Argument {
-                                Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
-                                Description = argumentAttribute.Help,
-                                ArgumentType = property.PropertyType,
-                                Arity = arity
-                            };
-                            argument = property;
-                        }
-                        else
-                        {
-                            var optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
-                            if (optionAttribute != null)
-                            {
-                                var option = new Option(optionAttribute.Name ?? BuildAlias(property.Name), optionAttribute.Help, new Argument { ArgumentType = property.PropertyType });
-                                command.AddOption(option);
-                                properties.Add((property, option));
-
-                                foreach (var optionAliasAttribute in (OptionAliasAttribute[])property.GetCustomAttributes(typeof(OptionAliasAttribute), inherit: false))
-                                {
-                                    option.AddAlias(optionAliasAttribute.Name);
-                                }
-                            }
-                            else
-                            {
-                                // If not an option, add as just a settable properties
-                                properties.Add((property, null));
-                            }
-                        }
-                    }
-
-                    var handler = new Handler(this, commandAttribute.AliasExpansion, argument, properties, type);
-                    _commandHandlers.Add(command.Name, handler);
-                    command.Handler = handler;
-
-                    rootBuilder.AddCommand(command);
-                }
-
-                if (baseAttribute is CommandAliasAttribute commandAliasAttribute)
-                {
-                    if (command == null) {
-                        throw new ArgumentException($"No previous CommandAttribute for this CommandAliasAttribute: {type.Name}");
-                    }
-                    command.AddAlias(commandAliasAttribute.Name);
-                }
-            }
-        }
-
-        private T GetService<T>()
-        {
-            _services.TryGetValue(typeof(T), out object service);
-            Debug.Assert(service != null);
-            return (T)service;
-        }
-
-        private static string BuildAlias(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()}";
-        }
-
-        class Handler : ICommandHandler
-        {
-            private readonly CommandProcessor _commandProcessor;
-            private readonly string _aliasExpansion;
-            private readonly PropertyInfo _argument;
-            private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties;
-
-            private readonly ConstructorInfo _constructor;
-            private readonly MethodInfo _methodInfo;
-            private readonly MethodInfo _methodInfoHelp;
-
-            public Handler(CommandProcessor commandProcessor, string aliasExpansion, PropertyInfo argument, IEnumerable<(PropertyInfo, Option)> properties, Type type)
-            {
-                _commandProcessor = commandProcessor;
-                _aliasExpansion = aliasExpansion;
-                _argument = argument;
-                _properties = properties;
-
-                _constructor = type.GetConstructors().SingleOrDefault((info) => info.GetParameters().Length == 0) ?? 
-                    throw new ArgumentException($"No eligible constructor found in {type}");
-
-                _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)
-            {
-                try
-                {
-                    Invoke(_methodInfo, context);
-                }
-                catch (Exception ex)
-                {
-                    return Task.FromException<int>(ex);
-                }
-                return Task.FromResult(context.ResultCode);
-            }
-
-            /// <summary>
-            /// Executes the command's help invoke function if exists
-            /// </summary>
-            /// <returns>true help called, false no help function</returns>
-            internal bool InvokeHelp()
-            {
-                if (_methodInfoHelp == null)
-                {
-                    return false;
-                }
-                // The InvocationContext is null so the options and arguments in the 
-                // command instance created don't get 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);
-                return true;
-            }
-
-            private void Invoke(MethodInfo methodInfo, InvocationContext context)
-            {
-                try
-                {
-                    // Assumes zero parameter constructor
-                    object instance = _constructor.Invoke(new object[0]);
-                    SetProperties(context, instance);
-
-                    object[] arguments = BuildArguments(methodInfo, context);
-                    methodInfo.Invoke(instance, arguments);
-                }
-                catch (TargetInvocationException ex)
-                {
-                    throw ex.InnerException;
-                }
-            }
-
-            private void SetProperties(InvocationContext context, object instance)
-            {
-                IEnumerable<OptionResult> optionResults = context?.ParseResult.CommandResult.Children.OfType<OptionResult>();
-
-                foreach ((PropertyInfo Property, Option Option) property in _properties)
-                {
-                    object value = property.Property.GetValue(instance);
-
-                    if (property.Property.Name == nameof(CommandBase.AliasExpansion)) {
-                        value = _aliasExpansion;
-                    }
-                    else 
-                    {
-                        Type propertyType = property.Property.PropertyType;
-                        if (TryGetService(propertyType, context, out object service)) {
-                            value = service;
-                        }
-                        else if (context != null && property.Option != null)
-                        {
-                            OptionResult optionResult = optionResults.Where((result) => result.Option == property.Option).SingleOrDefault();
-                            if (optionResult != null) {
-                                value = optionResult.GetValueOrDefault();
-                            }
-                        }
-                    }
-
-                    property.Property.SetValue(instance, value);
-                }
-
-                if (context != null && _argument != null)
-                {
-                    object value = null;
-                    ArgumentResult result = context.ParseResult.CommandResult.ArgumentResult;
-                    switch (result)
-                    {
-                        case SuccessfulArgumentResult successful:
-                            value = successful.Value;
-                            break;
-                        case FailedArgumentResult failed:
-                            throw new InvalidOperationException(failed.ErrorMessage);
-                    }
-                    _argument.SetValue(instance, value);
-                }
-            }
-
-            private object[] BuildArguments(MethodBase methodBase, InvocationContext context)
-            {
-                ParameterInfo[] parameters = methodBase.GetParameters();
-                object[] arguments = new object[parameters.Length];
-                for (int i = 0; i < parameters.Length; i++) {
-                    Type parameterType = parameters[i].ParameterType;
-                    // Ignoring false: the parameter will passed as null to allow for "optional"
-                    // services. The invoked method needs to check for possible null parameters.
-                    TryGetService(parameterType, context, out arguments[i]);
-                }
-                return arguments;
-            }
-
-            private bool TryGetService(Type type, InvocationContext context, out object service)
-            {
-                if (type == typeof(InvocationContext)) {
-                    service = context;
-                }
-                else if (!_commandProcessor._services.TryGetValue(type, out service)) {
-                    service = null;
-                    return false;
-                }
-                return true;
-            }
-        }
-
-        class LocalHelpBuilder : IHelpBuilder
-        {
-            private readonly CommandProcessor _commandProcessor;
-
-            public LocalHelpBuilder(CommandProcessor commandProcessor)
-            {
-                _commandProcessor = commandProcessor;
-            }
-
-            void IHelpBuilder.Write(ICommand command)
-            {
-                if (_commandProcessor._commandHandlers.TryGetValue(command.Name, out Handler handler))
-                {
-                    if (handler.InvokeHelp()) {
-                        return;
-                    }
-                }
-                var helpBuilder = new HelpBuilder(_commandProcessor.GetService<IConsole>(), maxWidth: Console.WindowWidth);
-                helpBuilder.Write(command);
-            }
-        }
-    }
-}
diff --git a/src/Microsoft.Diagnostic.Repl/Console/CharToLineConverter.cs b/src/Microsoft.Diagnostic.Repl/Console/CharToLineConverter.cs
deleted file mode 100644 (file)
index ea3f389..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-// --------------------------------------------------------------------
-// 
-// Copyright (c) Microsoft Corporation.  All rights reserved.
-// 
-// --------------------------------------------------------------------
-
-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();
-        }
-    }
-}
diff --git a/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs b/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs
deleted file mode 100644 (file)
index 5cabdfe..0000000
+++ /dev/null
@@ -1,511 +0,0 @@
-// --------------------------------------------------------------------
-// 
-// Copyright (c) Microsoft Corporation.  All rights reserved.
-// 
-// --------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using System.CommandLine;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Repl
-{
-    public sealed class ConsoleProvider : IConsole
-    {
-        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_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_consoleConverter = new CharToLineConverter(text => {
-                NewOutput(text);
-            });
-
-            m_warningConverter = new CharToLineConverter(text => {
-                NewOutput(text, warningColor);
-            });
-
-            m_errorConverter = new CharToLineConverter(text => {
-                NewOutput(text, errorColor);
-            });
-
-            Out = new StandardStreamWriter((text) => WriteOutput(OutputType.Normal, text));
-            Error = new StandardStreamWriter((text) => WriteOutput(OutputType.Error, text));
-
-            // 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 async Task Start(Func<string, CancellationToken, Task> dispatchCommand)
-        {
-            m_lastCommandLine = null;
-            m_shutdown = false;
-            m_interactiveConsole = !Console.IsInputRedirected;
-            RefreshLine();
-
-            // Start keyboard processing
-            while (!m_shutdown) {
-                if (m_interactiveConsole)
-                {
-                    ConsoleKeyInfo keyInfo = Console.ReadKey(true);
-                    await ProcessKeyInfo(keyInfo, dispatchCommand);
-                }
-                else
-                {
-                    // The input has been redirected (i.e. testing or in script)
-                    WriteLine(OutputType.Normal, "<END_COMMAND_OUTPUT>");
-                    string line = Console.ReadLine();
-                    if (string.IsNullOrEmpty(line)) {
-                        continue;
-                    }
-                    await Dispatch(line, dispatchCommand);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Stop input processing/dispatching
-        /// </summary>
-        public void Stop()
-        {
-            ClearLine();
-            m_shutdown = true;
-            Console.CancelKeyPress -= new ConsoleCancelEventHandler(OnCtrlBreakKeyPress);
-        }
-
-        /// <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(OutputType type, string format, params object[] parameters)
-        {
-            WriteOutput(type, string.Format(format + Environment.NewLine, parameters));
-        }
-
-        /// <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);
-            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);
-            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 async Task ProcessKeyInfo(ConsoleKeyInfo keyInfo, Func<string, CancellationToken, Task> 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;
-
-                    await 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 async Task Dispatch(string newCommand, Func<string, CancellationToken, Task> dispatchCommand)
-        {
-            CommandStarting();
-            m_interruptExecutingCommand = new CancellationTokenSource();
-            try
-            {
-                newCommand = newCommand.Trim();
-                if (string.IsNullOrEmpty(newCommand) && m_lastCommandLine != null) {
-                    newCommand = m_lastCommandLine;
-                }
-                try
-                {
-                    WriteLine(OutputType.Normal, "{0}{1}", m_prompt, newCommand);
-                    await 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);
-                    m_lastCommandLine = null;
-                }
-            }
-            finally
-            {
-                m_interruptExecutingCommand = null;
-                CommandFinished();
-            }
-        }
-
-        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 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
-    }
-}
diff --git a/src/Microsoft.Diagnostic.Repl/Console/OutputType.cs b/src/Microsoft.Diagnostic.Repl/Console/OutputType.cs
deleted file mode 100644 (file)
index 26b65aa..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-// --------------------------------------------------------------------
-//
-// Copyright (c) Microsoft Corporation.  All rights reserved.
-//
-// --------------------------------------------------------------------
-
-namespace Microsoft.Diagnostics.Repl
-{
-    /// <summary>
-    /// The type of output. 
-    /// </summary>
-    public enum OutputType
-    {
-        Normal      = 1,
-        Error       = 2,
-        Warning     = 3,
-    }
-}
diff --git a/src/Microsoft.Diagnostic.Repl/Microsoft.Diagnostic.Repl.csproj b/src/Microsoft.Diagnostic.Repl/Microsoft.Diagnostic.Repl.csproj
deleted file mode 100644 (file)
index caeb36f..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
-<Project Sdk="Microsoft.NET.Sdk">
-  <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <NoWarn>;1591;1701</NoWarn>
-    <Description>Diagnostic utility functions and helpers</Description>
-    <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
-    <GenerateDocumentationFile>true</GenerateDocumentationFile>
-  </PropertyGroup>
-  
-  <ItemGroup>
-    <PackageReference Include="System.CommandLine.Experimental" Version="$(SystemCommandLineExperimentalVersion)" />
-  </ItemGroup>
-  
-</Project>
diff --git a/src/Microsoft.Diagnostics.Repl/Command/Attributes.cs b/src/Microsoft.Diagnostics.Repl/Command/Attributes.cs
new file mode 100644 (file)
index 0000000..b902cd3
--- /dev/null
@@ -0,0 +1,85 @@
+// --------------------------------------------------------------------
+// 
+// Copyright (c) Microsoft Corporation.  All rights reserved.
+// 
+// --------------------------------------------------------------------
+using System;
+
+namespace Microsoft.Diagnostics.Repl
+{
+    /// <summary>
+    /// Base command option attribute.
+    /// </summary>
+    public class BaseAttribute : Attribute
+    {
+        /// <summary>
+        /// Name of the command
+        /// </summary>
+        public string Name;
+
+        /// <summary>
+        /// Displayed in the help for the command
+        /// </summary>
+        public string Help;
+    }
+
+    /// <summary>
+    /// Marks the class as a Command.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+    public class CommandAttribute : BaseAttribute
+    {
+        /// <summary>
+        /// Sets the value of the CommandBase.AliasExpansion when the command is executed.
+        /// </summary>
+        public string AliasExpansion;
+    }
+
+    /// <summary>
+    /// Adds an alias to the previous command attribute
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+    public class CommandAliasAttribute : BaseAttribute
+    {
+    }
+
+    /// <summary>
+    /// Marks the property as a Option.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property)]
+    public class OptionAttribute : BaseAttribute
+    {
+    }
+
+    /// <summary>
+    /// Adds an alias to the Option. Help is ignored.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
+    public class OptionAliasAttribute : BaseAttribute
+    {
+    }
+
+    /// <summary>
+    /// Marks the property the command Argument.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property)]
+    public class ArgumentAttribute : BaseAttribute
+    {
+    }
+
+    /// <summary>
+    /// Marks the function to invoke to execute the command.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method)]
+    public class CommandInvokeAttribute : Attribute
+    {
+    }
+
+    /// <summary>
+    /// Marks the function to invoke to display alternate help for command.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method)]
+    public class HelpInvokeAttribute : Attribute
+    {
+    }
+}
diff --git a/src/Microsoft.Diagnostics.Repl/Command/CommandBase.cs b/src/Microsoft.Diagnostics.Repl/Command/CommandBase.cs
new file mode 100644 (file)
index 0000000..73d4580
--- /dev/null
@@ -0,0 +1,59 @@
+// --------------------------------------------------------------------
+// 
+// Copyright (c) Microsoft Corporation.  All rights reserved.
+// 
+// --------------------------------------------------------------------
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Repl
+{
+    /// <summary>
+    /// The common command context
+    /// </summary>
+    public abstract class CommandBase
+    {
+        /// <summary>
+        /// Parser invocation context. Contains the ParseResult, CommandResult, etc. Is null when 
+        /// InvokeAdditionalHelp is called.
+        /// </summary>
+        public InvocationContext InvocationContext { get; set; }
+
+        /// <summary>
+        /// Console instance
+        /// </summary>
+        public IConsole Console { get; set; }
+
+        /// <summary>
+        /// The AliasExpansion value from the CommandAttribute or null if none.
+        /// </summary>
+        public string AliasExpansion { get; set; }
+
+        /// <summary>
+        /// Execute the command
+        /// </summary>
+        [CommandInvoke]
+        public abstract void Invoke();
+
+        /// <summary>
+        /// Display text
+        /// </summary>
+        /// <param name="message">text message</param>
+        protected void WriteLine(string message)
+        {
+            Console.Out.WriteLine(message);
+        }
+
+        /// <summary>
+        /// Display formatted text
+        /// </summary>
+        /// <param name="format">format string</param>
+        /// <param name="args">arguments</param>
+        protected void WriteLine(string format, params object[] args)
+        {
+            Console.Out.WriteLine(string.Format(format, args));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs b/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs
new file mode 100644 (file)
index 0000000..c36655c
--- /dev/null
@@ -0,0 +1,377 @@
+// --------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation.  All rights reserved.
+//
+// --------------------------------------------------------------------
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.Binding;
+using System.CommandLine.Builder;
+using System.CommandLine.Invocation;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Repl
+{
+    public class CommandProcessor
+    {
+        private readonly Parser _parser;
+        private readonly Command _rootCommand;
+        private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
+        private readonly Dictionary<string, Handler> _commandHandlers = new Dictionary<string, Handler>();
+
+        /// <summary>
+        /// Create an instance of the command processor;
+        /// </summary>
+        /// <param name="console">console instance to use for commands</param>
+        /// <param name="assemblies">Optional list of assemblies to look for commands</param>
+        /// <param name="types">Optional list of types to look for commands</param>
+        public CommandProcessor(IConsole console, IEnumerable<Assembly> assemblies = null, IEnumerable<Type> types = null)
+        {
+            Debug.Assert(console != null);
+            Debug.Assert(assemblies != null);
+            _services.Add(typeof(CommandProcessor), this);
+            _services.Add(typeof(IConsole), console);
+            _services.Add(typeof(IHelpBuilder), new LocalHelpBuilder(this));
+            var rootBuilder = new CommandLineBuilder(new Command(">"));
+            rootBuilder.UseHelp()
+                       .UseHelpBuilder((bindingContext) => GetService<IHelpBuilder>())
+                       .UseParseDirective()
+                       .UseSuggestDirective()
+                       .UseParseErrorReporting()
+                       .UseExceptionHandler();
+            if (assemblies != null) {
+                BuildCommands(rootBuilder, assemblies);
+            }
+            if (types != null) {
+                BuildCommands(rootBuilder, types);
+            }
+            _rootCommand = rootBuilder.Command;
+            _parser = rootBuilder.Build();
+        }
+
+        /// <summary>
+        /// Adds a service or context to inject into an command.
+        /// </summary>
+        /// <typeparam name="T">type of service</typeparam>
+        /// <param name="instance">service instance</param>
+        public void AddService<T>(T instance)
+        {
+            AddService(typeof(T), instance);
+        }
+
+        /// <summary>
+        /// Adds a service or context to inject into an command.
+        /// </summary>
+        /// <param name="type">service type</param>
+        /// <param name="instance">service instance</param>
+        public void AddService(Type type, object instance)
+        {
+            _services.Add(type, instance);
+        }
+
+        /// <summary>
+        /// Parse the command line.
+        /// </summary>
+        /// <param name="commandLine">command line txt</param>
+        /// <returns>exit code</returns>
+        public Task<int> Parse(string commandLine)
+        {
+            ParseResult result = _parser.Parse(commandLine);
+            return _parser.InvokeAsync(result, GetService<IConsole>());
+        }
+
+        /// <summary>
+        /// Display all the help or a specific command's help.
+        /// </summary>
+        /// <param name="name">command name or null</param>
+        /// <returns>command instance or null if not found</returns>
+        public Command GetCommand(string name)
+        {
+            if (string.IsNullOrEmpty(name)) {
+                return _rootCommand;
+            }
+            else {
+                return _rootCommand.Children.OfType<Command>().FirstOrDefault((cmd) => name == cmd.Name || cmd.Aliases.Any((alias) => name == alias));
+            }
+        }
+
+        private void BuildCommands(CommandLineBuilder rootBuilder, IEnumerable<Assembly> assemblies)
+        {
+            BuildCommands(rootBuilder, assemblies.SelectMany((assembly) => assembly.GetExportedTypes()));
+        }
+
+        private void BuildCommands(CommandLineBuilder rootBuilder, IEnumerable<Type> types)
+        {
+            foreach (Type type in types)
+            {
+                for (Type baseType = type; baseType != null; baseType = baseType.BaseType)
+                {
+                    if (baseType == typeof(CommandBase)) {
+                        break;
+                    }
+                    BuildCommands(rootBuilder, baseType);
+                }
+            }
+        }
+
+        private void BuildCommands(CommandLineBuilder rootBuilder, Type type)
+        {
+            Command command = null;
+
+            var baseAttributes = (BaseAttribute[])type.GetCustomAttributes(typeof(BaseAttribute), inherit: false);
+            foreach (BaseAttribute baseAttribute in baseAttributes)
+            {
+                if (baseAttribute is CommandAttribute commandAttribute)
+                {
+                    command = new Command(commandAttribute.Name, commandAttribute.Help);
+                    var properties = new List<(PropertyInfo, Option)>();
+                    PropertyInfo argument = null;
+
+                    foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
+                    {
+                        var argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
+                        if (argumentAttribute != null)
+                        {
+                            if (argument != null) {
+                                throw new ArgumentException($"More than one ArgumentAttribute in command class: {type.Name}");
+                            }
+                            IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
+
+                            command.Argument = new Argument {
+                                Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
+                                Description = argumentAttribute.Help,
+                                ArgumentType = property.PropertyType,
+                                Arity = arity
+                            };
+                            argument = property;
+                        }
+                        else
+                        {
+                            var optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
+                            if (optionAttribute != null)
+                            {
+                                var option = new Option(optionAttribute.Name ?? BuildAlias(property.Name), optionAttribute.Help, new Argument { ArgumentType = property.PropertyType });
+                                command.AddOption(option);
+                                properties.Add((property, option));
+
+                                foreach (var optionAliasAttribute in (OptionAliasAttribute[])property.GetCustomAttributes(typeof(OptionAliasAttribute), inherit: false))
+                                {
+                                    option.AddAlias(optionAliasAttribute.Name);
+                                }
+                            }
+                            else
+                            {
+                                // If not an option, add as just a settable properties
+                                properties.Add((property, null));
+                            }
+                        }
+                    }
+
+                    var handler = new Handler(this, commandAttribute.AliasExpansion, argument, properties, type);
+                    _commandHandlers.Add(command.Name, handler);
+                    command.Handler = handler;
+
+                    rootBuilder.AddCommand(command);
+                }
+
+                if (baseAttribute is CommandAliasAttribute commandAliasAttribute)
+                {
+                    if (command == null) {
+                        throw new ArgumentException($"No previous CommandAttribute for this CommandAliasAttribute: {type.Name}");
+                    }
+                    command.AddAlias(commandAliasAttribute.Name);
+                }
+            }
+        }
+
+        private T GetService<T>()
+        {
+            _services.TryGetValue(typeof(T), out object service);
+            Debug.Assert(service != null);
+            return (T)service;
+        }
+
+        private static string BuildAlias(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()}";
+        }
+
+        class Handler : ICommandHandler
+        {
+            private readonly CommandProcessor _commandProcessor;
+            private readonly string _aliasExpansion;
+            private readonly PropertyInfo _argument;
+            private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties;
+
+            private readonly ConstructorInfo _constructor;
+            private readonly MethodInfo _methodInfo;
+            private readonly MethodInfo _methodInfoHelp;
+
+            public Handler(CommandProcessor commandProcessor, string aliasExpansion, PropertyInfo argument, IEnumerable<(PropertyInfo, Option)> properties, Type type)
+            {
+                _commandProcessor = commandProcessor;
+                _aliasExpansion = aliasExpansion;
+                _argument = argument;
+                _properties = properties;
+
+                _constructor = type.GetConstructors().SingleOrDefault((info) => info.GetParameters().Length == 0) ?? 
+                    throw new ArgumentException($"No eligible constructor found in {type}");
+
+                _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)
+            {
+                try
+                {
+                    Invoke(_methodInfo, context);
+                }
+                catch (Exception ex)
+                {
+                    return Task.FromException<int>(ex);
+                }
+                return Task.FromResult(context.ResultCode);
+            }
+
+            /// <summary>
+            /// Executes the command's help invoke function if exists
+            /// </summary>
+            /// <returns>true help called, false no help function</returns>
+            internal bool InvokeHelp()
+            {
+                if (_methodInfoHelp == null)
+                {
+                    return false;
+                }
+                // The InvocationContext is null so the options and arguments in the 
+                // command instance created don't get 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);
+                return true;
+            }
+
+            private void Invoke(MethodInfo methodInfo, InvocationContext context)
+            {
+                try
+                {
+                    // Assumes zero parameter constructor
+                    object instance = _constructor.Invoke(new object[0]);
+                    SetProperties(context, instance);
+
+                    object[] arguments = BuildArguments(methodInfo, context);
+                    methodInfo.Invoke(instance, arguments);
+                }
+                catch (TargetInvocationException ex)
+                {
+                    throw ex.InnerException;
+                }
+            }
+
+            private void SetProperties(InvocationContext context, object instance)
+            {
+                IEnumerable<OptionResult> optionResults = context?.ParseResult.CommandResult.Children.OfType<OptionResult>();
+
+                foreach ((PropertyInfo Property, Option Option) property in _properties)
+                {
+                    object value = property.Property.GetValue(instance);
+
+                    if (property.Property.Name == nameof(CommandBase.AliasExpansion)) {
+                        value = _aliasExpansion;
+                    }
+                    else 
+                    {
+                        Type propertyType = property.Property.PropertyType;
+                        if (TryGetService(propertyType, context, out object service)) {
+                            value = service;
+                        }
+                        else if (context != null && property.Option != null)
+                        {
+                            OptionResult optionResult = optionResults.Where((result) => result.Option == property.Option).SingleOrDefault();
+                            if (optionResult != null) {
+                                value = optionResult.GetValueOrDefault();
+                            }
+                        }
+                    }
+
+                    property.Property.SetValue(instance, value);
+                }
+
+                if (context != null && _argument != null)
+                {
+                    object value = null;
+                    ArgumentResult result = context.ParseResult.CommandResult.ArgumentResult;
+                    switch (result)
+                    {
+                        case SuccessfulArgumentResult successful:
+                            value = successful.Value;
+                            break;
+                        case FailedArgumentResult failed:
+                            throw new InvalidOperationException(failed.ErrorMessage);
+                    }
+                    _argument.SetValue(instance, value);
+                }
+            }
+
+            private object[] BuildArguments(MethodBase methodBase, InvocationContext context)
+            {
+                ParameterInfo[] parameters = methodBase.GetParameters();
+                object[] arguments = new object[parameters.Length];
+                for (int i = 0; i < parameters.Length; i++) {
+                    Type parameterType = parameters[i].ParameterType;
+                    // Ignoring false: the parameter will passed as null to allow for "optional"
+                    // services. The invoked method needs to check for possible null parameters.
+                    TryGetService(parameterType, context, out arguments[i]);
+                }
+                return arguments;
+            }
+
+            private bool TryGetService(Type type, InvocationContext context, out object service)
+            {
+                if (type == typeof(InvocationContext)) {
+                    service = context;
+                }
+                else if (!_commandProcessor._services.TryGetValue(type, out service)) {
+                    service = null;
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        class LocalHelpBuilder : IHelpBuilder
+        {
+            private readonly CommandProcessor _commandProcessor;
+
+            public LocalHelpBuilder(CommandProcessor commandProcessor)
+            {
+                _commandProcessor = commandProcessor;
+            }
+
+            void IHelpBuilder.Write(ICommand command)
+            {
+                if (_commandProcessor._commandHandlers.TryGetValue(command.Name, out Handler handler))
+                {
+                    if (handler.InvokeHelp()) {
+                        return;
+                    }
+                }
+                var helpBuilder = new HelpBuilder(_commandProcessor.GetService<IConsole>(), maxWidth: Console.WindowWidth);
+                helpBuilder.Write(command);
+            }
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.Repl/Console/CharToLineConverter.cs b/src/Microsoft.Diagnostics.Repl/Console/CharToLineConverter.cs
new file mode 100644 (file)
index 0000000..ea3f389
--- /dev/null
@@ -0,0 +1,59 @@
+// --------------------------------------------------------------------
+// 
+// Copyright (c) Microsoft Corporation.  All rights reserved.
+// 
+// --------------------------------------------------------------------
+
+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();
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.Repl/Console/ConsoleProvider.cs b/src/Microsoft.Diagnostics.Repl/Console/ConsoleProvider.cs
new file mode 100644 (file)
index 0000000..5cabdfe
--- /dev/null
@@ -0,0 +1,511 @@
+// --------------------------------------------------------------------
+// 
+// Copyright (c) Microsoft Corporation.  All rights reserved.
+// 
+// --------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Repl
+{
+    public sealed class ConsoleProvider : IConsole
+    {
+        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_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_consoleConverter = new CharToLineConverter(text => {
+                NewOutput(text);
+            });
+
+            m_warningConverter = new CharToLineConverter(text => {
+                NewOutput(text, warningColor);
+            });
+
+            m_errorConverter = new CharToLineConverter(text => {
+                NewOutput(text, errorColor);
+            });
+
+            Out = new StandardStreamWriter((text) => WriteOutput(OutputType.Normal, text));
+            Error = new StandardStreamWriter((text) => WriteOutput(OutputType.Error, text));
+
+            // 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 async Task Start(Func<string, CancellationToken, Task> dispatchCommand)
+        {
+            m_lastCommandLine = null;
+            m_shutdown = false;
+            m_interactiveConsole = !Console.IsInputRedirected;
+            RefreshLine();
+
+            // Start keyboard processing
+            while (!m_shutdown) {
+                if (m_interactiveConsole)
+                {
+                    ConsoleKeyInfo keyInfo = Console.ReadKey(true);
+                    await ProcessKeyInfo(keyInfo, dispatchCommand);
+                }
+                else
+                {
+                    // The input has been redirected (i.e. testing or in script)
+                    WriteLine(OutputType.Normal, "<END_COMMAND_OUTPUT>");
+                    string line = Console.ReadLine();
+                    if (string.IsNullOrEmpty(line)) {
+                        continue;
+                    }
+                    await Dispatch(line, dispatchCommand);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Stop input processing/dispatching
+        /// </summary>
+        public void Stop()
+        {
+            ClearLine();
+            m_shutdown = true;
+            Console.CancelKeyPress -= new ConsoleCancelEventHandler(OnCtrlBreakKeyPress);
+        }
+
+        /// <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(OutputType type, string format, params object[] parameters)
+        {
+            WriteOutput(type, string.Format(format + Environment.NewLine, parameters));
+        }
+
+        /// <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);
+            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);
+            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 async Task ProcessKeyInfo(ConsoleKeyInfo keyInfo, Func<string, CancellationToken, Task> 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;
+
+                    await 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 async Task Dispatch(string newCommand, Func<string, CancellationToken, Task> dispatchCommand)
+        {
+            CommandStarting();
+            m_interruptExecutingCommand = new CancellationTokenSource();
+            try
+            {
+                newCommand = newCommand.Trim();
+                if (string.IsNullOrEmpty(newCommand) && m_lastCommandLine != null) {
+                    newCommand = m_lastCommandLine;
+                }
+                try
+                {
+                    WriteLine(OutputType.Normal, "{0}{1}", m_prompt, newCommand);
+                    await 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);
+                    m_lastCommandLine = null;
+                }
+            }
+            finally
+            {
+                m_interruptExecutingCommand = null;
+                CommandFinished();
+            }
+        }
+
+        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 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
+    }
+}
diff --git a/src/Microsoft.Diagnostics.Repl/Console/OutputType.cs b/src/Microsoft.Diagnostics.Repl/Console/OutputType.cs
new file mode 100644 (file)
index 0000000..26b65aa
--- /dev/null
@@ -0,0 +1,18 @@
+// --------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation.  All rights reserved.
+//
+// --------------------------------------------------------------------
+
+namespace Microsoft.Diagnostics.Repl
+{
+    /// <summary>
+    /// The type of output. 
+    /// </summary>
+    public enum OutputType
+    {
+        Normal      = 1,
+        Error       = 2,
+        Warning     = 3,
+    }
+}
diff --git a/src/Microsoft.Diagnostics.Repl/Microsoft.Diagnostics.Repl.csproj b/src/Microsoft.Diagnostics.Repl/Microsoft.Diagnostics.Repl.csproj
new file mode 100644 (file)
index 0000000..caeb36f
--- /dev/null
@@ -0,0 +1,16 @@
+<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <NoWarn>;1591;1701</NoWarn>
+    <Description>Diagnostic utility functions and helpers</Description>
+    <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+  </PropertyGroup>
+  
+  <ItemGroup>
+    <PackageReference Include="System.CommandLine.Experimental" Version="$(SystemCommandLineExperimentalVersion)" />
+  </ItemGroup>
+  
+</Project>
diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticHelpers.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticHelpers.cs
deleted file mode 100644 (file)
index f6709fe..0000000
+++ /dev/null
@@ -1,149 +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.Tools.RuntimeClient.DiagnosticsIpc;
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Diagnostics.Tools.RuntimeClient
-{
-    public static class DiagnosticsHelpers
-    {
-        /// <summary>
-        /// Controls the contents of the dump
-        /// </summary>
-        public enum DumpType : uint
-        {
-            Normal = 1,
-            WithHeap = 2,
-            Triage = 3,
-            Full = 4
-        }
-
-        /// <summary>
-        /// Initiate a core dump in the target process runtime.
-        /// </summary>
-        /// <param name="processId">.NET Core process id</param>
-        /// <param name="dumpName">Path and file name of core dump</param>
-        /// <param name="dumpType">Type of dump</param>
-        /// <param name="diagnostics">If true, log to console the dump generation diagnostics</param>
-        /// <returns>DiagnosticsServerErrorCode</returns>
-        public static int GenerateCoreDump(int processId, string dumpName, DumpType dumpType, bool diagnostics)
-        {
-            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
-                throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}");
-
-            if (string.IsNullOrEmpty(dumpName))
-                throw new ArgumentNullException($"{nameof(dumpName)} required");
-
-
-            var payload = SerializeCoreDump(dumpName, dumpType, diagnostics);
-            var message = new IpcMessage(DiagnosticsServerCommandSet.Dump, (byte)DumpCommandId.GenerateCoreDump, payload);
-
-            var response = IpcClient.SendMessage(processId, message);
-
-            var hr = 0;
-            switch ((DiagnosticsServerCommandId)response.Header.CommandId)
-            {
-                case DiagnosticsServerCommandId.Error:
-                case DiagnosticsServerCommandId.OK:
-                    hr = BitConverter.ToInt32(response.Payload);
-                    break;
-                default:
-                    return -1;
-            }
-
-            return hr;
-        }
-
-        /// <summary>
-        /// Attach a profiler to the target process runtime.
-        /// </summary>
-        /// <param name="processId">.NET Core process id</param>
-        /// <param name="attachTimeout">The timeout (in ms) for the runtime to wait while attempting to attach.</param>
-        /// <param name="profilerGuid">CLSID of the profiler to load</param>
-        /// <param name="profilerPath">Path to the profiler library on disk</param>
-        /// <param name="additionalData">additional data to pass to the profiler on attach</param>
-        /// <returns>HRESULT</returns>
-        public static int AttachProfiler(int processId, uint attachTimeout, Guid profilerGuid, string profilerPath, byte[] additionalData)
-        {
-            if (profilerGuid == null || profilerGuid == Guid.Empty)
-            {
-                throw new ArgumentException($"{nameof(profilerGuid)} must be a valid Guid");
-            }
-
-            if (String.IsNullOrEmpty(profilerPath))
-            {
-                throw new ArgumentException($"{nameof(profilerPath)} must be non-null");
-            }
-
-            var header = new MessageHeader {
-                RequestType = DiagnosticsMessageType.AttachProfiler,
-                Pid = (uint)Process.GetCurrentProcess().Id,
-            };
-
-            byte[] serializedConfiguration = SerializeProfilerAttach(attachTimeout, profilerGuid, profilerPath, additionalData);
-            var message = new IpcMessage(DiagnosticsServerCommandSet.Profiler, (byte)ProfilerCommandId.AttachProfiler, serializedConfiguration);
-
-            var response = IpcClient.SendMessage(processId, message);
-
-            var hr = 0;
-            switch ((DiagnosticsServerCommandId)response.Header.CommandId)
-            {
-                case DiagnosticsServerCommandId.Error:
-                case DiagnosticsServerCommandId.OK:
-                    hr = BitConverter.ToInt32(response.Payload);
-                    break;
-                default:
-                    hr = -1;
-                    break;
-            }
-
-            // TODO: the call to set up the pipe and send the message operates on a different timeout than attachTimeout, which is for the runtime.
-            // We should eventually have a configurable timeout for the message passing, potentially either separately from the 
-            // runtime timeout or respect attachTimeout as one total duration.
-            return hr;
-        }
-
-        private static byte[] SerializeProfilerAttach(uint attachTimeout, Guid profilerGuid, string profilerPath, byte[] additionalData)
-        {
-            using (var stream = new MemoryStream())
-            using (var writer = new BinaryWriter(stream))
-            {
-                writer.Write(attachTimeout);
-                writer.Write(profilerGuid.ToByteArray());
-                writer.WriteString(profilerPath);
-
-                if (additionalData == null)
-                {
-                    writer.Write(0);
-                }
-                else
-                {
-                    writer.Write(additionalData.Length);
-                    writer.Write(additionalData);
-                }
-
-                writer.Flush();
-                return stream.ToArray();
-            }
-        }
-
-        private static byte[] SerializeCoreDump(string dumpName, DumpType dumpType, bool diagnostics)
-        {
-            using (var stream = new MemoryStream())
-            using (var writer = new BinaryWriter(stream))
-            {
-                writer.WriteString(dumpName);
-                writer.Write((uint)dumpType);
-                writer.Write((uint)(diagnostics ? 1 : 0));
-
-                writer.Flush();
-                return stream.ToArray();
-            }
-        }
-    }
-}
diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticsHelpers.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticsHelpers.cs
new file mode 100644 (file)
index 0000000..f6709fe
--- /dev/null
@@ -0,0 +1,149 @@
+// 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.Tools.RuntimeClient.DiagnosticsIpc;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient
+{
+    public static class DiagnosticsHelpers
+    {
+        /// <summary>
+        /// Controls the contents of the dump
+        /// </summary>
+        public enum DumpType : uint
+        {
+            Normal = 1,
+            WithHeap = 2,
+            Triage = 3,
+            Full = 4
+        }
+
+        /// <summary>
+        /// Initiate a core dump in the target process runtime.
+        /// </summary>
+        /// <param name="processId">.NET Core process id</param>
+        /// <param name="dumpName">Path and file name of core dump</param>
+        /// <param name="dumpType">Type of dump</param>
+        /// <param name="diagnostics">If true, log to console the dump generation diagnostics</param>
+        /// <returns>DiagnosticsServerErrorCode</returns>
+        public static int GenerateCoreDump(int processId, string dumpName, DumpType dumpType, bool diagnostics)
+        {
+            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}");
+
+            if (string.IsNullOrEmpty(dumpName))
+                throw new ArgumentNullException($"{nameof(dumpName)} required");
+
+
+            var payload = SerializeCoreDump(dumpName, dumpType, diagnostics);
+            var message = new IpcMessage(DiagnosticsServerCommandSet.Dump, (byte)DumpCommandId.GenerateCoreDump, payload);
+
+            var response = IpcClient.SendMessage(processId, message);
+
+            var hr = 0;
+            switch ((DiagnosticsServerCommandId)response.Header.CommandId)
+            {
+                case DiagnosticsServerCommandId.Error:
+                case DiagnosticsServerCommandId.OK:
+                    hr = BitConverter.ToInt32(response.Payload);
+                    break;
+                default:
+                    return -1;
+            }
+
+            return hr;
+        }
+
+        /// <summary>
+        /// Attach a profiler to the target process runtime.
+        /// </summary>
+        /// <param name="processId">.NET Core process id</param>
+        /// <param name="attachTimeout">The timeout (in ms) for the runtime to wait while attempting to attach.</param>
+        /// <param name="profilerGuid">CLSID of the profiler to load</param>
+        /// <param name="profilerPath">Path to the profiler library on disk</param>
+        /// <param name="additionalData">additional data to pass to the profiler on attach</param>
+        /// <returns>HRESULT</returns>
+        public static int AttachProfiler(int processId, uint attachTimeout, Guid profilerGuid, string profilerPath, byte[] additionalData)
+        {
+            if (profilerGuid == null || profilerGuid == Guid.Empty)
+            {
+                throw new ArgumentException($"{nameof(profilerGuid)} must be a valid Guid");
+            }
+
+            if (String.IsNullOrEmpty(profilerPath))
+            {
+                throw new ArgumentException($"{nameof(profilerPath)} must be non-null");
+            }
+
+            var header = new MessageHeader {
+                RequestType = DiagnosticsMessageType.AttachProfiler,
+                Pid = (uint)Process.GetCurrentProcess().Id,
+            };
+
+            byte[] serializedConfiguration = SerializeProfilerAttach(attachTimeout, profilerGuid, profilerPath, additionalData);
+            var message = new IpcMessage(DiagnosticsServerCommandSet.Profiler, (byte)ProfilerCommandId.AttachProfiler, serializedConfiguration);
+
+            var response = IpcClient.SendMessage(processId, message);
+
+            var hr = 0;
+            switch ((DiagnosticsServerCommandId)response.Header.CommandId)
+            {
+                case DiagnosticsServerCommandId.Error:
+                case DiagnosticsServerCommandId.OK:
+                    hr = BitConverter.ToInt32(response.Payload);
+                    break;
+                default:
+                    hr = -1;
+                    break;
+            }
+
+            // TODO: the call to set up the pipe and send the message operates on a different timeout than attachTimeout, which is for the runtime.
+            // We should eventually have a configurable timeout for the message passing, potentially either separately from the 
+            // runtime timeout or respect attachTimeout as one total duration.
+            return hr;
+        }
+
+        private static byte[] SerializeProfilerAttach(uint attachTimeout, Guid profilerGuid, string profilerPath, byte[] additionalData)
+        {
+            using (var stream = new MemoryStream())
+            using (var writer = new BinaryWriter(stream))
+            {
+                writer.Write(attachTimeout);
+                writer.Write(profilerGuid.ToByteArray());
+                writer.WriteString(profilerPath);
+
+                if (additionalData == null)
+                {
+                    writer.Write(0);
+                }
+                else
+                {
+                    writer.Write(additionalData.Length);
+                    writer.Write(additionalData);
+                }
+
+                writer.Flush();
+                return stream.ToArray();
+            }
+        }
+
+        private static byte[] SerializeCoreDump(string dumpName, DumpType dumpType, bool diagnostics)
+        {
+            using (var stream = new MemoryStream())
+            using (var writer = new BinaryWriter(stream))
+            {
+                writer.WriteString(dumpName);
+                writer.Write((uint)dumpType);
+                writer.Write((uint)(diagnostics ? 1 : 0));
+
+                writer.Flush();
+                return stream.ToArray();
+            }
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticMessageType.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticMessageType.cs
deleted file mode 100644 (file)
index 883ea21..0000000
+++ /dev/null
@@ -1,33 +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.Tools.RuntimeClient
-{
-    /// <summary>
-    /// Different diagnostic message types that are handled by the runtime.
-    /// </summary>
-    public enum DiagnosticsMessageType : uint
-    {
-        /// <summary>
-        /// Initiates core dump generation 
-        /// </summary>
-        GenerateCoreDump = 1,
-        /// <summary>
-        /// Starts an EventPipe session that writes events to a file when the session is stopped or the application exits.
-        /// </summary>
-        StartEventPipeTracing = 1024,
-        /// <summary>
-        /// Stops an EventPipe session.
-        /// </summary>
-        StopEventPipeTracing,
-        /// <summary>
-        /// Starts an EventPipe session that sends events out-of-proc through IPC.
-        /// </summary>
-        CollectEventPipeTracing,
-        /// <summary>
-        /// Attaches a profiler to an existing process
-        /// </summary>
-        AttachProfiler = 2048,
-    }
-}
diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticsMessageType.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticsMessageType.cs
new file mode 100644 (file)
index 0000000..883ea21
--- /dev/null
@@ -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.
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient
+{
+    /// <summary>
+    /// Different diagnostic message types that are handled by the runtime.
+    /// </summary>
+    public enum DiagnosticsMessageType : uint
+    {
+        /// <summary>
+        /// Initiates core dump generation 
+        /// </summary>
+        GenerateCoreDump = 1,
+        /// <summary>
+        /// Starts an EventPipe session that writes events to a file when the session is stopped or the application exits.
+        /// </summary>
+        StartEventPipeTracing = 1024,
+        /// <summary>
+        /// Stops an EventPipe session.
+        /// </summary>
+        StopEventPipeTracing,
+        /// <summary>
+        /// Starts an EventPipe session that sends events out-of-proc through IPC.
+        /// </summary>
+        CollectEventPipeTracing,
+        /// <summary>
+        /// Attaches a profiler to an existing process
+        /// </summary>
+        AttachProfiler = 2048,
+    }
+}
index 2d56b70f6045b0857eb963271f103acd7a0691ce..15f28687540e8d564d08681b2009e022865f4223 100644 (file)
@@ -15,7 +15,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostic.Repl\Microsoft.Diagnostic.Repl.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Repl\Microsoft.Diagnostics.Repl.csproj" />
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Tools.RuntimeClient\Microsoft.Diagnostics.Tools.RuntimeClient.csproj" />
   </ItemGroup>
 
index 72825a45eb7b5efa9cb0cb6a0e94e28f9df3ad34..af38b15cb8549b4e1ece524fde0e93e99f6e1874 100644 (file)
@@ -1,7 +1,7 @@
-using Microsoft.Diagnostic.Repl;
+using Microsoft.Diagnostics.Repl;
 using System.CommandLine;
 
-namespace Microsoft.Diagnostic.Tools.Dump
+namespace Microsoft.Diagnostics.Tools.Dump
 {
     [Command(Name = "dumprcw",              AliasExpansion = "DumpRCW",             Help = "Displays information about a Runtime Callable Wrapper.")]
     [Command(Name = "dumpccw",              AliasExpansion = "DumpCCW",             Help = "Displays information about a COM Callable Wrapper.")]
index 2907b973fc18d22a57277a94c10abed56cd022e8..95d059bd4e42114dfcaa518e12c09ded60b16a41 100644 (file)
@@ -16,7 +16,7 @@
   </ItemGroup>
   
   <ItemGroup>
-    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostic.Repl\Microsoft.Diagnostic.Repl.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Repl\Microsoft.Diagnostics.Repl.csproj" />
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Tools.RuntimeClient\Microsoft.Diagnostics.Tools.RuntimeClient.csproj" />
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\SOS\SOS.Hosting\SOS.Hosting.csproj" />
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\SOS\SOS.NETCore\SOS.NETCore.csproj" />