EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-sos", "src\Tools\dotnet-sos\dotnet-sos.csproj", "{41351955-16D5-48D7-AF4C-AF25F5FB2E78}"
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}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Checked|Any CPU = Checked|Any CPU
{41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|ARM.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|x64.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|x86.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|ARM.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|x64.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|x86.Build.0 = Debug|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|ARM.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|ARM.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|ARM64.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|x64.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|x64.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|x86.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.Release|x86.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+ {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
{20EBC3C4-917C-402D-B778-9A6E3742BF5A} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
{1F012743-941B-4915-8C55-02097894CF3F} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
{41351955-16D5-48D7-AF4C-AF25F5FB2E78} = {B62728C8-1267-4043-B46F-5537BBAEC692}
+ {90CF2633-58F0-44EE-943B-D70207455F20} = {19FAB78C-3351-4911-8F0C-8C6056401740}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json;
https://dotnet.myget.org/F/symstore/api/v3/index.json;
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
- https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json
+ https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json;
+ https://dotnet.myget.org/F/system-commandline/api/v3/index.json
</RestoreSources>
</PropertyGroup>
</Project>
--- /dev/null
+// --------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// --------------------------------------------------------------------
+using System;
+
+namespace Microsoft.Diagnostic.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>
+ /// 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
+ {
+ }
+}
--- /dev/null
+// --------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// --------------------------------------------------------------------
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostic.Repl
+{
+ /// <summary>
+ /// The common command context
+ /// </summary>
+ public abstract class CommandBase
+ {
+ public const string EntryPointName = nameof(InvokeAsync);
+
+ /// <summary>
+ /// Parser invocation context. Contains the ParseResult, CommandResult, etc.
+ /// </summary>
+ public InvocationContext InvocationContext { get; set; }
+
+ /// <summary>
+ /// Console instance
+ /// </summary>
+ public IConsole Console { get { return InvocationContext.Console; } }
+
+ /// <summary>
+ /// The AliasExpansion value from the CommandAttribute or null if none.
+ /// </summary>
+ public string AliasExpansion { get; set; }
+
+ /// <summary>
+ /// Execute the command
+ /// </summary>
+ public abstract Task InvokeAsync();
+
+ /// <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
--- /dev/null
+// --------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// --------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.Builder;
+using System.CommandLine.Invocation;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostic.Repl
+{
+ public class CommandProcessor
+ {
+ private readonly Parser _parser;
+ private readonly Command _rootCommand;
+
+ /// <summary>
+ /// Domain specific context passed to commands
+ /// </summary>
+ public object CommandContext { get; set; }
+
+ /// <summary>
+ /// Create an instance of the command processor;
+ /// </summary>
+ /// <param name="assemblies">The list of assemblies to look for commands</param>
+ public CommandProcessor(IEnumerable<Assembly> assemblies)
+ {
+ var rootBuilder = new CommandLineBuilder();
+ rootBuilder.UseHelp()
+ .UseParseDirective()
+ .UseSuggestDirective()
+ .UseParseErrorReporting()
+ .UseExceptionHandler();
+ BuildCommands(rootBuilder, assemblies);
+ _rootCommand = rootBuilder.Command;
+ _parser = rootBuilder.Build();
+ }
+
+ /// <summary>
+ /// Parse the command line.
+ /// </summary>
+ /// <param name="commandLine">command line txt</param>
+ /// <param name="console">option console</param>
+ /// <returns>exit code</returns>
+ public Task<int> Parse(string commandLine, IConsole console = null)
+ {
+ ParseResult result = _parser.Parse(commandLine);
+ return _parser.InvokeAsync(result, console);
+ }
+
+ /// <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)
+ {
+ foreach (Type type in assemblies.SelectMany((assembly) => assembly.GetExportedTypes()))
+ {
+ Command command = null;
+
+ var commandAttributes = (CommandAttribute[])type.GetCustomAttributes(typeof(CommandAttribute), inherit: false);
+ foreach (CommandAttribute commandAttribute in commandAttributes)
+ {
+ // If there is a previous command and the current command doesn't have help or alias expansion, use "simple" aliasing
+ if (command != null && commandAttribute.Help == null && commandAttribute.AliasExpansion == null) {
+ command.AddAlias(commandAttribute.Name);
+ continue;
+ }
+ command = new Command(commandAttribute.Name, commandAttribute.Help);
+ var builder = new CommandLineBuilder(command);
+ builder.UseHelp();
+
+ 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}");
+ }
+ command.Argument = new Argument {
+ Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
+ Description = argumentAttribute.Help,
+ ArgumentType = property.PropertyType,
+ Arity = new ArgumentArity(0, int.MaxValue)
+ };
+ 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));
+ }
+ }
+ }
+
+ command.Handler = new Handler(this, commandAttribute.AliasExpansion, argument, properties, type);
+ rootBuilder.AddCommand(command);
+ }
+ }
+ }
+
+ 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;
+
+ 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.GetMethod(CommandBase.EntryPointName, new Type[] { typeof(IHelpBuilder) }) ?? type.GetMethod(CommandBase.EntryPointName) ??
+ throw new ArgumentException($"{CommandBase.EntryPointName} method not found in {type}");
+ }
+
+ public Task<int> InvokeAsync(InvocationContext context)
+ {
+ try
+ {
+ // Assumes zero parameter constructor
+ object instance = _constructor.Invoke(new object[0]);
+ SetProperties(context, instance);
+
+ var methodBinder = new MethodBinder(_methodInfo, () => instance);
+ return methodBinder.InvokeAsync(context);
+ }
+ catch (TargetInvocationException ex)
+ {
+ throw ex.InnerException;
+ }
+ }
+
+ private void SetProperties(InvocationContext context, object instance)
+ {
+ IEnumerable<OptionResult> optionResults = context.ParseResult.CommandResult.Children.OfType<OptionResult>();
+
+ foreach (var 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 (propertyType == typeof(InvocationContext)) {
+ value = context;
+ }
+ else if (propertyType == typeof(IConsole)) {
+ value = context.Console;
+ }
+ else if (propertyType == typeof(CommandProcessor)) {
+ value = _commandProcessor;
+ }
+ else if (propertyType == _commandProcessor.CommandContext?.GetType()) {
+ value = _commandProcessor.CommandContext;
+ }
+ else if (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 (_argument != null)
+ {
+ object value = context.ParseResult.CommandResult.GetValueOrDefault();
+ _argument.SetValue(instance, value);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// --------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// --------------------------------------------------------------------
+
+using System;
+using System.Text;
+
+namespace Microsoft.Diagnostic.Repl
+{
+ public sealed class CharToLineConverter
+ {
+ readonly Action<string> m_callback;
+ readonly StringBuilder m_text = new StringBuilder();
+
+ public CharToLineConverter(Action<string> callback)
+ {
+ m_callback = callback;
+ }
+
+ public void Input(byte[] buffer, int offset, int count)
+ {
+ for (int i = 0; i < count; i++) {
+ char c = (char)buffer[offset + i];
+ if (c == '\r') {
+ continue;
+ }
+ if (c == '\n') {
+ Flush();
+ }
+ else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) {
+ m_text.Append(c);
+ }
+ }
+ }
+
+ public void Input(string text)
+ {
+ foreach (char c in text) {
+ if (c == '\r') {
+ continue;
+ }
+ if (c == '\n') {
+ Flush();
+ }
+ else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) {
+ m_text.Append(c);
+ }
+ }
+ }
+
+ public void Flush()
+ {
+ m_callback(m_text.ToString());
+ m_text.Clear();
+ }
+ }
+}
--- /dev/null
+// --------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// --------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostic.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_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;
+ RefreshLine();
+
+ // Start keyboard processing
+ while (!m_shutdown) {
+ ConsoleKeyInfo keyInfo = Console.ReadKey(true);
+ await ProcessKeyInfo(keyInfo, 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) {
+ 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_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_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))
+ {
+ 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
+ }
+}
--- /dev/null
+// --------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// --------------------------------------------------------------------
+
+namespace Microsoft.Diagnostic.Repl
+{
+ /// <summary>
+ /// The type of output.
+ /// </summary>
+ public enum OutputType
+ {
+ Normal = 1,
+ Error = 2,
+ Warning = 3,
+ }
+}
--- /dev/null
+<!-- 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>
+ <DebugType>embedded</DebugType>
+ <DebugSymbols>true</DebugSymbols>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="System.CommandLine.Experimental" Version="0.1.0-alpha-63807-01" />
+ </ItemGroup>
+
+</Project>