Sicherung

This commit is contained in:
Maier Stephan SI
2023-01-20 16:09:00 +01:00
parent e5257d8413
commit b684704bf8
139 changed files with 95678 additions and 499 deletions

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
namespace Consolus
{
public struct ConsoleChar
{
public ConsoleChar(char value)
{
Value = value;
StyleMarkers = null;
}
public List<ConsoleStyleMarker> StyleMarkers;
public char Value;
public static implicit operator ConsoleChar(char c)
{
return new ConsoleChar(c);
}
public override string ToString()
{
return Value.ToString();
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Consolus
{
public class ConsoleHelper
{
public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static readonly Lazy<bool> _isConsoleSupportingEscapeSequences = new Lazy<bool>(IsConsoleSupportingEscapeSequencesInternal);
private static readonly Lazy<bool> _hasInteractiveConsole = new Lazy<bool>(HasInteractiveConsoleInternal);
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public static bool SupportEscapeSequences => _isConsoleSupportingEscapeSequences.Value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public static bool HasInteractiveConsole => _hasInteractiveConsole.Value;
private static bool IsConsoleSupportingEscapeSequencesInternal()
{
if (Console.IsOutputRedirected || Console.IsErrorRedirected || !HasInteractiveConsole)
{
return false;
}
if (IsWindows)
{
WindowsHelper.EnableAnsiEscapeOnWindows();
}
var left = Console.CursorLeft;
var top = Console.CursorTop;
ConsoleStyle.Reset.Render(Console.Out);
bool support = true;
if (left != Console.CursorLeft || top != Console.CursorTop)
{
support = false;
// Remove previous sequence
Console.SetCursorPosition(left, top);
Console.Write(" "); // Clear \x1b[0m = 4 characters
Console.SetCursorPosition(left, top);
}
return support;
}
private static bool HasInteractiveConsoleInternal() => IsWindows ? WindowsHelper.HasConsoleWindows() : Environment.UserInteractive;
}
}

View File

@@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
namespace Consolus
{
public class ConsoleRenderer
{
private readonly ConsoleTextWriter _consoleWriter;
private int _previousDisplayLength;
private int _cursorIndex;
private int _beforeEditLineSize;
public ConsoleRenderer()
{
SupportEscapeSequences = ConsoleHelper.SupportEscapeSequences;
_consoleWriter = new ConsoleTextWriter(Console.Out);
Prompt = new ConsoleText();
EditLine = new ConsoleText();
AfterEditLine = new ConsoleText();
SelectionIndex = -1;
EnableCursorChanged = true;
}
public bool SupportEscapeSequences { get; set; }
public ConsoleTextWriter ConsoleWriter => _consoleWriter;
public int LineTop
{
get
{
var currentLine = (_beforeEditLineSize + CursorIndex) / Console.BufferWidth;
return Console.CursorTop - currentLine;
}
}
public bool IsClean => _previousDisplayLength == 0;
public bool EnableCursorChanged { get; set; }
public int CursorIndex
{
get => _cursorIndex;
set
{
if (value < 0 || value > EditLine.Count) throw new ArgumentOutOfRangeException(nameof(value), $"Value must be >= 0 and <= {EditLine.Count}");
var lineTop = LineTop;
bool changed = _cursorIndex != value;
_cursorIndex = value;
UpdateCursorPosition(lineTop);
if (changed)
{
NotifyCursorChanged();
}
}
}
public Action CursorChanged { get; set; }
private void NotifyCursorChanged()
{
var cursorChanged = CursorChanged;
if (EnableCursorChanged) CursorChanged?.Invoke();
}
public bool HasSelection => SelectionIndex >= 0 && CursorIndex != SelectionIndex;
public int SelectionStartIndex => HasSelection ? SelectionIndex < CursorIndex ? SelectionIndex : CursorIndex : -1;
public int SelectionEndIndex => HasSelection ? SelectionIndex < CursorIndex ? CursorIndex : SelectionIndex : -1;
public int SelectionIndex { get; private set; }
public ConsoleText Prompt { get; }
public ConsoleText EditLine { get; }
public ConsoleText AfterEditLine { get; }
public Action BeforeRender { get; set; }
public void BeginSelection()
{
if (SelectionIndex >= 0) return;
SelectionIndex = CursorIndex;
}
public void EndSelection()
{
if (SelectionIndex < 0) return;
SelectionIndex = -1;
Render();
}
public void RemoveSelection()
{
if (!HasSelection) return;
var start = SelectionStartIndex;
var count = SelectionEndIndex - start;
for (int i = 0; i < count; i++)
{
if (start >= EditLine.Count) break;
EditLine.RemoveAt(start);
}
var cursorIndex = start;
SelectionIndex = -1;
Render(cursorIndex);
}
public void Reset()
{
AfterEditLine.Clear();
EditLine.Clear();
_cursorIndex = 0;
_beforeEditLineSize = 0;
_previousDisplayLength = 0;
}
public void Render(int? newCursorIndex = null, bool reset = false)
{
// Invoke before rendering to update the highlighting
BeforeRender?.Invoke();
Console.CursorVisible = false;
Console.SetCursorPosition(0, LineTop);
if (SupportEscapeSequences)
{
ConsoleStyle.Reset.Render(_consoleWriter);
}
else
{
Console.ResetColor();
}
Prompt.Render(_consoleWriter);
EditLine.SelectionStart = SelectionStartIndex;
EditLine.SelectionEnd = SelectionEndIndex;
EditLine.Render(_consoleWriter, SupportEscapeSequences);
AfterEditLine.Render(_consoleWriter);
// Fill remaining space with space
var newLength = _consoleWriter.VisibleCharacterCount;
var visualLength = newLength;
if (newLength < _previousDisplayLength)
{
if (SupportEscapeSequences)
{
ConsoleStyle.Reset.Render(_consoleWriter);
}
for (int i = newLength; i < _previousDisplayLength; i++)
{
_consoleWriter.Write(' ');
}
visualLength = _previousDisplayLength;
}
if (SupportEscapeSequences)
{
ConsoleStyle.Reset.Render(_consoleWriter);
}
_consoleWriter.Commit();
if (!SupportEscapeSequences)
{
Console.ResetColor();
}
_previousDisplayLength = newLength;
if (!reset)
{
// Calculate the current line based on the visual
var lineTop = Console.CursorTop - (visualLength - 1) / Console.BufferWidth;
_beforeEditLineSize = EditLine.VisibleCharacterStart;
if (newCursorIndex.HasValue)
{
bool changed = _cursorIndex != newCursorIndex.Value;
_cursorIndex = newCursorIndex.Value;
if (changed)
{
NotifyCursorChanged();
}
}
if (_cursorIndex > EditLine.Count) _cursorIndex = EditLine.Count;
UpdateCursorPosition(lineTop);
}
else
{
Reset();
}
Console.CursorVisible = true;
}
private void UpdateCursorPosition(int lineTop)
{
var position = _beforeEditLineSize + _cursorIndex;
var deltaX = position % Console.BufferWidth;
var deltaY = position / Console.BufferWidth;
Console.SetCursorPosition(deltaX, lineTop + deltaY);
}
}
}

View File

@@ -0,0 +1,750 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace Consolus
{
public class ConsoleRepl : ConsoleRenderer
{
// http://ascii-table.com/ansi-escape-sequences-vt-100.php
private int _stackIndex;
private readonly BlockingCollection<ConsoleKeyInfo> _keys;
private Thread _thread;
public static bool IsSelf()
{
if (ConsoleHelper.IsWindows)
{
return WindowsHelper.IsSelfConsoleWindows();
}
return false;
}
public ConsoleRepl()
{
HasInteractiveConsole = ConsoleHelper.HasInteractiveConsole;
Prompt.Append(">>> ");
_stackIndex = -1;
History = new List<string>();
ExitOnNextEval = false;
PendingTextToEnter = new Queue<string>();
_keys = new BlockingCollection<ConsoleKeyInfo>();
if (HasInteractiveConsole)
{
Console.TreatControlCAsInput = true;
Console.CursorVisible = true;
}
}
public bool HasInteractiveConsole { get; }
public bool ExitOnNextEval { get; set; }
public Func<CancellationTokenSource> GetCancellationTokenSource { get; set; }
public PreProcessKeyDelegate TryPreProcessKey { get; set; }
public delegate bool PreProcessKeyDelegate(ConsoleKeyInfo key, ref int cursorIndex);
public Action<ConsoleKeyInfo> PostProcessKey { get; set; }
public List<string> History { get; }
public Action<string> SetClipboardTextImpl { get; set; }
public Func<string> GetClipboardTextImpl { get; set; }
public string LocalClipboard { get; set; }
public Func<string, bool, bool> OnTextValidatingEnter { get; set; }
public Action<string> OnTextValidatedEnter { get; set; }
private Queue<string> PendingTextToEnter { get; }
public bool Evaluating { get; private set; }
public void Begin()
{
CursorIndex = 0;
Render();
}
public void End()
{
CursorIndex = EditLine.Count;
Render();
}
public void EnqueuePendingTextToEnter(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
PendingTextToEnter.Enqueue(text);
}
public void UpdateSelection()
{
if (SelectionIndex >= 0)
{
Render();
}
}
private static UnicodeCategory GetCharCategory(char c)
{
if (c == '_' || (c >= '0' && c <= '9')) return UnicodeCategory.LowercaseLetter;
c = char.ToLowerInvariant(c);
return char.GetUnicodeCategory(c);
}
public void MoveLeft(bool word = false)
{
var cursorIndex = CursorIndex;
if (cursorIndex > 0)
{
if (word)
{
UnicodeCategory? category = null;
// Remove any space before
while (cursorIndex > 0)
{
cursorIndex--;
var newCategory = GetCharCategory(EditLine[cursorIndex].Value);
if (newCategory != UnicodeCategory.SpaceSeparator)
{
category = newCategory;
break;
}
}
while (cursorIndex > 0)
{
cursorIndex--;
var newCategory = GetCharCategory(EditLine[cursorIndex].Value);
if (category.HasValue)
{
if (newCategory != category.Value)
{
cursorIndex++;
break;
}
}
else
{
category = newCategory;
}
}
}
else
{
cursorIndex--;
}
}
CursorIndex = cursorIndex;
Render();
}
private int FindNextWordRight(int cursorIndex)
{
var category = GetCharCategory(EditLine[cursorIndex].Value);
while (cursorIndex < EditLine.Count)
{
var newCategory = GetCharCategory(EditLine[cursorIndex].Value);
if (newCategory != category)
{
break;
}
cursorIndex++;
}
while (cursorIndex < EditLine.Count)
{
var newCategory = GetCharCategory(EditLine[cursorIndex].Value);
if (newCategory != UnicodeCategory.SpaceSeparator)
{
break;
}
cursorIndex++;
}
return cursorIndex;
}
public void MoveRight(bool word = false)
{
var cursorIndex = CursorIndex;
if (cursorIndex < EditLine.Count)
{
if (word)
{
cursorIndex = FindNextWordRight(cursorIndex);
}
else
{
cursorIndex++;
}
}
CursorIndex = cursorIndex;
Render();
}
private void CopySelectionToClipboard()
{
if (!HasSelection) return;
var text = new StringBuilder();
var from = SelectionIndex < CursorIndex ? SelectionIndex : CursorIndex;
var to = SelectionIndex < CursorIndex ? CursorIndex - 1 : SelectionIndex - 1;
for (int i = from; i <= to; i++)
{
text.Append(EditLine[i].Value);
}
bool useLocalClipboard = true;
var textToClip = text.ToString();
if (SetClipboardTextImpl != null)
{
try
{
SetClipboardTextImpl(textToClip);
useLocalClipboard = false;
}
catch
{
// ignore
}
}
if (useLocalClipboard)
{
LocalClipboard = textToClip;
}
}
public string GetClipboardText()
{
if (LocalClipboard != null) return LocalClipboard;
if (GetClipboardTextImpl != null)
{
try
{
return GetClipboardTextImpl();
}
catch
{
// ignore
}
}
return null;
}
private void Backspace(bool word)
{
if (HasSelection)
{
RemoveSelection();
return;
}
var cursorIndex = CursorIndex;
if (cursorIndex == 0) return;
if (word)
{
MoveLeft(true);
var newCursorIndex = CursorIndex;
var length = cursorIndex - CursorIndex;
EditLine.RemoveRangeAt(newCursorIndex, length);
cursorIndex = newCursorIndex;
}
else
{
cursorIndex--;
EditLine.RemoveAt(cursorIndex);
}
Render(cursorIndex);
}
private void Delete(bool word)
{
if (HasSelection)
{
RemoveSelection();
return;
}
var cursorIndex = CursorIndex;
if (cursorIndex < EditLine.Count)
{
if (word)
{
var count = FindNextWordRight(cursorIndex) - cursorIndex;
EditLine.RemoveRangeAt(cursorIndex, count);
}
else
{
EditLine.RemoveAt(cursorIndex);
}
Render();
}
}
public void SetLine(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
EndSelection();
// Don't update if it is already empty
if (EditLine.Count == 0 && string.IsNullOrEmpty(text)) return;
EditLine.ReplaceBy(text);
Render(EditLine.Count);
}
public void Write(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
Write(text, 0, text.Length);
}
public void Write(string text, int index, int length)
{
if (text == null) throw new ArgumentNullException(nameof(text));
var cursorIndex = CursorIndex + length;
EditLine.InsertRange(CursorIndex, text, index, length);
Render(cursorIndex);
}
public void Write(ConsoleStyle style)
{
var cursorIndex = CursorIndex;
EditLine.EnableStyleAt(cursorIndex, style);
Render(cursorIndex);
}
public void Write(char c)
{
if (SelectionIndex >= 0)
{
RemoveSelection();
}
EndSelection();
var cursorIndex = CursorIndex;
EditLine.Insert(cursorIndex, c);
cursorIndex++;
Render(cursorIndex);
}
private bool Enter(bool force)
{
if (EnterInternal(force))
{
while (PendingTextToEnter.Count > 0)
{
var newTextToEnter = PendingTextToEnter.Dequeue();
EditLine.Clear();
EditLine.Append(newTextToEnter);
if (!EnterInternal(force)) return false;
}
return true;
}
return false;
}
private bool EnterInternal(bool hasControl)
{
if (HasSelection)
{
EndSelection();
}
End();
var text = EditLine.Count == 0 ? string.Empty : EditLine.ToString();
// Try to validate the string
if (OnTextValidatingEnter != null)
{
bool isValid = false;
try
{
Evaluating = true;
isValid = OnTextValidatingEnter(text, hasControl);
}
finally
{
Evaluating = false;
}
if (!isValid)
{
Render();
return false;
}
}
bool isNotEmpty = !IsClean || EditLine.Count > 0 || AfterEditLine.Count > 0;
if (isNotEmpty)
{
Render(reset: true);
}
// Propagate enter validation
OnTextValidatedEnter?.Invoke(text);
if (!string.IsNullOrEmpty(text))
{
History.Add(text);
}
_stackIndex = -1;
if (!ExitOnNextEval)
{
Render(0);
}
return true;
}
public void Clear()
{
Reset();
Console.Clear();
}
private void Exit()
{
End();
Render(reset: true);
Console.Write($"{ConsoleStyle.BrightRed}^Z");
Console.ResetColor();
if (!ConsoleHelper.IsWindows)
{
Console.WriteLine();
}
ExitOnNextEval = true;
}
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public void Run()
{
if (IsWindows)
{
// Clear any previous running thread
if (_thread != null)
{
try
{
_thread.Abort();
}
catch
{
// ignore
}
_thread = null;
}
_thread = new Thread(ThreadReadKeys) {IsBackground = true, Name = "Consolus.ThreadReadKeys"};
_thread.Start();
}
_stackIndex = -1;
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
Render();
while (!ExitOnNextEval)
{
try
{
ConsoleKeyInfo key;
key = IsWindows ? _keys.Take() : Console.ReadKey(true);
ProcessKey(key);
}
catch (Exception ex)
{
AfterEditLine.Clear();
AfterEditLine.Append("\n");
AfterEditLine.Begin(ConsoleStyle.Red);
AfterEditLine.Append(ex.Message);
Render(); // re-display the current line with the exception
// Display the next line
//Render();
}
}
}
private void ThreadReadKeys()
{
while (!ExitOnNextEval)
{
var key = Console.ReadKey(true);
if (Evaluating && (key.Modifiers & ConsoleModifiers.Control) != 0 && key.Key == ConsoleKey.C)
{
GetCancellationTokenSource()?.Cancel();
}
else
{
_keys.Add(key);
}
}
}
private void PasteClipboard()
{
var clipboard = GetClipboardText();
if (clipboard != null)
{
clipboard = clipboard.TrimEnd();
int previousIndex = 0;
while (true)
{
int matchIndex = clipboard.IndexOf('\n', previousIndex);
int index = matchIndex;
bool exit = false;
if (index < 0)
{
index = clipboard.Length;
exit = true;
}
while (index > 0 && index < clipboard.Length && clipboard[index - 1] == '\r')
{
index--;
}
Write(clipboard, previousIndex, index - previousIndex);
if (exit)
{
break;
}
else
{
previousIndex = matchIndex + 1;
// Otherwise we have a new line
Enter(true);
}
}
}
}
protected virtual void ProcessKey(ConsoleKeyInfo key)
{
_isStandardAction = false;
_hasShift = (key.Modifiers & ConsoleModifiers.Shift) != 0;
// Only support selection if we have support for escape sequences
if (SupportEscapeSequences && _hasShift)
{
BeginSelection();
}
// Try to pre-process key
var cursorIndex = CursorIndex;
if (TryPreProcessKey != null && TryPreProcessKey(key, ref cursorIndex))
{
if (!_isStandardAction)
{
if (SelectionIndex >= 0)
{
RemoveSelection();
}
EndSelection();
Render(cursorIndex);
}
}
else if (key.KeyChar >= ' ')
{
Write(key.KeyChar);
}
// Remove selection if shift is no longer selected
if (!_hasShift)
{
EndSelection();
}
// Post-process key
if (PostProcessKey != null)
{
PostProcessKey(key);
}
}
private void DebugCursorPosition(string text = null)
{
Console.Title = $"x:{Console.CursorLeft} y:{Console.CursorTop} (Size w:{Console.BufferWidth} h:{Console.BufferHeight}){(text == null ? string.Empty: " " + text)}";
}
private void GoHistory(bool next)
{
var newStackIndex = _stackIndex + (next ? -1 : 1);
if (newStackIndex < 0 || newStackIndex >= History.Count)
{
if (newStackIndex < 0)
{
SetLine(string.Empty);
_stackIndex = -1;
}
}
else
{
_stackIndex = newStackIndex;
var index = (History.Count - 1) - _stackIndex;
SetLine(History[index]);
}
}
private bool _hasShift;
private bool _isStandardAction;
public void Action(ConsoleAction action)
{
_isStandardAction = true;
switch (action)
{
case ConsoleAction.Exit:
Exit();
break;
case ConsoleAction.CursorLeft:
MoveLeft(false);
break;
case ConsoleAction.CursorRight:
MoveRight(false);
break;
case ConsoleAction.CursorLeftWord:
MoveLeft(true);
break;
case ConsoleAction.CursorRightWord:
MoveRight(true);
break;
case ConsoleAction.CursorStartOfLine:
Begin();
break;
case ConsoleAction.CursorEndOfLine:
End();
break;
case ConsoleAction.HistoryPrevious:
GoHistory(false);
break;
case ConsoleAction.HistoryNext:
GoHistory(true);
break;
case ConsoleAction.DeleteCharacterLeft:
Backspace(false);
_stackIndex = -1;
_hasShift = false;
break;
case ConsoleAction.DeleteCharacterLeftAndCopy:
break;
case ConsoleAction.DeleteCharacterRight:
Delete(false);
_stackIndex = -1;
_hasShift = false;
break;
case ConsoleAction.DeleteCharacterRightAndCopy:
break;
case ConsoleAction.DeleteWordLeft:
Backspace(true);
_stackIndex = -1;
_hasShift = false;
break;
case ConsoleAction.DeleteWordRight:
Delete(true);
_stackIndex = -1;
_hasShift = false;
break;
case ConsoleAction.Completion:
break;
case ConsoleAction.DeleteTextRightAndCopy:
break;
case ConsoleAction.DeleteWordRightAndCopy:
break;
case ConsoleAction.DeleteWordLeftAndCopy:
break;
case ConsoleAction.CopySelection:
CopySelectionToClipboard();
break;
case ConsoleAction.CutSelection:
CopySelectionToClipboard();
RemoveSelection();
break;
case ConsoleAction.PasteClipboard:
PasteClipboard();
break;
case ConsoleAction.ValidateLine:
Enter(false);
break;
case ConsoleAction.ForceValidateLine:
Enter(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(action), action, $"Invalid action {action}");
}
}
}
public enum ConsoleAction
{
Exit,
CursorLeft,
CursorRight,
CursorLeftWord,
CursorRightWord,
CursorStartOfLine,
CursorEndOfLine,
HistoryPrevious,
HistoryNext,
DeleteCharacterLeft,
DeleteCharacterLeftAndCopy,
DeleteCharacterRight,
DeleteCharacterRightAndCopy,
DeleteWordLeft,
DeleteWordRight,
Completion,
DeleteTextRightAndCopy,
DeleteWordRightAndCopy,
DeleteWordLeftAndCopy,
CopySelection,
CutSelection,
PasteClipboard,
ValidateLine,
ForceValidateLine,
}
}

View File

@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Consolus
{
public enum ConsoleStyleKind
{
Color,
Format,
Reset,
}
public readonly struct ConsoleStyle : IEquatable<ConsoleStyle>
{
/// https://en.wikipedia.org/wiki/ANSI_escape_code
public static readonly ConsoleStyle Black = new ConsoleStyle("\u001b[30m");
public static readonly ConsoleStyle Red = new ConsoleStyle("\u001b[31m");
public static readonly ConsoleStyle Green = new ConsoleStyle("\u001b[32m");
public static readonly ConsoleStyle Yellow = new ConsoleStyle("\u001b[33m");
public static readonly ConsoleStyle Blue = new ConsoleStyle("\u001b[34m");
public static readonly ConsoleStyle Magenta = new ConsoleStyle("\u001b[35m");
public static readonly ConsoleStyle Cyan = new ConsoleStyle("\u001b[36m");
public static readonly ConsoleStyle White = new ConsoleStyle("\u001b[37m");
public static readonly ConsoleStyle BrightBlack = new ConsoleStyle("\u001b[30;1m");
public static readonly ConsoleStyle BrightRed = new ConsoleStyle("\u001b[31;1m");
public static readonly ConsoleStyle BrightGreen = new ConsoleStyle("\u001b[32;1m");
public static readonly ConsoleStyle BrightYellow = new ConsoleStyle("\u001b[33;1m");
public static readonly ConsoleStyle BrightBlue = new ConsoleStyle("\u001b[34;1m");
public static readonly ConsoleStyle BrightMagenta = new ConsoleStyle("\u001b[35;1m");
public static readonly ConsoleStyle BrightCyan = new ConsoleStyle("\u001b[36;1m");
public static readonly ConsoleStyle BrightWhite = new ConsoleStyle("\u001b[37;1m");
public static readonly ConsoleStyle BackgroundBlack = new ConsoleStyle("\u001b[40m");
public static readonly ConsoleStyle BackgroundRed = new ConsoleStyle("\u001b[41m");
public static readonly ConsoleStyle BackgroundGreen = new ConsoleStyle("\u001b[42m");
public static readonly ConsoleStyle BackgroundYellow = new ConsoleStyle("\u001b[43m");
public static readonly ConsoleStyle BackgroundBlue = new ConsoleStyle("\u001b[44m");
public static readonly ConsoleStyle BackgroundMagenta = new ConsoleStyle("\u001b[45m");
public static readonly ConsoleStyle BackgroundCyan = new ConsoleStyle("\u001b[46m");
public static readonly ConsoleStyle BackgroundWhite = new ConsoleStyle("\u001b[47m");
public static readonly ConsoleStyle BackgroundBrightBlack = new ConsoleStyle("\u001b[40;1m");
public static readonly ConsoleStyle BackgroundBrightRed = new ConsoleStyle("\u001b[41;1m");
public static readonly ConsoleStyle BackgroundBrightGreen = new ConsoleStyle("\u001b[42;1m");
public static readonly ConsoleStyle BackgroundBrightYellow = new ConsoleStyle("\u001b[43;1m");
public static readonly ConsoleStyle BackgroundBrightBlue = new ConsoleStyle("\u001b[44;1m");
public static readonly ConsoleStyle BackgroundBrightMagenta = new ConsoleStyle("\u001b[45;1m");
public static readonly ConsoleStyle BackgroundBrightCyan = new ConsoleStyle("\u001b[46;1m");
public static readonly ConsoleStyle BackgroundBrightWhite = new ConsoleStyle("\u001b[47;1m");
public static readonly ConsoleStyle Bold = new ConsoleStyle("\u001b[1m", ConsoleStyleKind.Format);
public static readonly ConsoleStyle Underline = new ConsoleStyle("\u001b[4m", ConsoleStyleKind.Format);
public static readonly ConsoleStyle Reversed = new ConsoleStyle("\u001b[7m", ConsoleStyleKind.Format);
public static readonly ConsoleStyle Reset = new ConsoleStyle("\u001b[0m", ConsoleStyleKind.Reset);
private static readonly Dictionary<string, ConsoleStyle> MapKnownEscapeToConsoleStyle = new Dictionary<string, ConsoleStyle>(StringComparer.OrdinalIgnoreCase);
static ConsoleStyle()
{
MapKnownEscapeToConsoleStyle.Add(Black.EscapeSequence, Black);
MapKnownEscapeToConsoleStyle.Add(Red.EscapeSequence, Red);
MapKnownEscapeToConsoleStyle.Add(Green.EscapeSequence, Green);
MapKnownEscapeToConsoleStyle.Add(Yellow.EscapeSequence, Yellow);
MapKnownEscapeToConsoleStyle.Add(Blue.EscapeSequence, Blue);
MapKnownEscapeToConsoleStyle.Add(Magenta.EscapeSequence, Magenta);
MapKnownEscapeToConsoleStyle.Add(Cyan.EscapeSequence, Cyan);
MapKnownEscapeToConsoleStyle.Add(White.EscapeSequence, White);
MapKnownEscapeToConsoleStyle.Add(BrightBlack.EscapeSequence, BrightBlack);
MapKnownEscapeToConsoleStyle.Add(BrightRed.EscapeSequence, BrightRed);
MapKnownEscapeToConsoleStyle.Add(BrightGreen.EscapeSequence, BrightGreen);
MapKnownEscapeToConsoleStyle.Add(BrightYellow.EscapeSequence, BrightYellow);
MapKnownEscapeToConsoleStyle.Add(BrightBlue.EscapeSequence, BrightBlue);
MapKnownEscapeToConsoleStyle.Add(BrightMagenta.EscapeSequence, BrightMagenta);
MapKnownEscapeToConsoleStyle.Add(BrightCyan.EscapeSequence, BrightCyan);
MapKnownEscapeToConsoleStyle.Add(BrightWhite.EscapeSequence, BrightWhite);
MapKnownEscapeToConsoleStyle.Add(BackgroundBlack.EscapeSequence, BackgroundBlack);
MapKnownEscapeToConsoleStyle.Add(BackgroundRed.EscapeSequence, BackgroundRed);
MapKnownEscapeToConsoleStyle.Add(BackgroundGreen.EscapeSequence, BackgroundGreen);
MapKnownEscapeToConsoleStyle.Add(BackgroundYellow.EscapeSequence, BackgroundYellow);
MapKnownEscapeToConsoleStyle.Add(BackgroundBlue.EscapeSequence, BackgroundBlue);
MapKnownEscapeToConsoleStyle.Add(BackgroundMagenta.EscapeSequence, BackgroundMagenta);
MapKnownEscapeToConsoleStyle.Add(BackgroundCyan.EscapeSequence, BackgroundCyan);
MapKnownEscapeToConsoleStyle.Add(BackgroundWhite.EscapeSequence, BackgroundWhite);
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightBlack.EscapeSequence, BackgroundBrightBlack);
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightRed.EscapeSequence, BackgroundBrightRed);
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightGreen.EscapeSequence, BackgroundBrightGreen);
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightYellow.EscapeSequence, BackgroundBrightYellow);
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightBlue.EscapeSequence, BackgroundBrightBlue);
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightMagenta.EscapeSequence, BackgroundBrightMagenta);
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightCyan.EscapeSequence, BackgroundBrightCyan);
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightWhite.EscapeSequence, BackgroundBrightWhite);
MapKnownEscapeToConsoleStyle.Add(Bold.EscapeSequence, Bold);
MapKnownEscapeToConsoleStyle.Add(Underline.EscapeSequence, Underline);
MapKnownEscapeToConsoleStyle.Add(Reversed.EscapeSequence, Reversed);
MapKnownEscapeToConsoleStyle.Add(Reset.EscapeSequence, Reset);
}
public ConsoleStyle(string escapeSequence) : this()
{
EscapeSequence = escapeSequence;
Kind = ConsoleStyleKind.Color;
}
public ConsoleStyle(string escapeSequence, ConsoleStyleKind kind) : this()
{
EscapeSequence = escapeSequence;
Kind = kind;
}
private ConsoleStyle(string escapeSequence, ConsoleStyleKind kind, bool isInline)
{
EscapeSequence = escapeSequence;
Kind = kind;
IsInline = isInline;
}
public static ConsoleStyle Inline(string escape, ConsoleStyleKind kind = ConsoleStyleKind.Color)
{
if (escape == null) throw new ArgumentNullException(nameof(escape));
if (kind == ConsoleStyleKind.Color)
{
if (MapKnownEscapeToConsoleStyle.TryGetValue(escape, out var style))
{
kind = style.Kind;
}
}
return new ConsoleStyle(escape, kind, true);
}
public static ConsoleStyle Rgb(int r, int g, int b)
{
return new ConsoleStyle($"\x1b[38;2;{r};{g};{b}m", ConsoleStyleKind.Color);
}
public static ConsoleStyle BackgroundRgb(int r, int g, int b)
{
return new ConsoleStyle($"\x1b[48;2;{r};{g};{b}m", ConsoleStyleKind.Color);
}
public readonly string EscapeSequence;
public readonly ConsoleStyleKind Kind;
public readonly bool IsInline;
public void Render(TextWriter writer)
{
writer.Write(EscapeSequence);
}
#if NETSTANDARD2_0
public static implicit operator ConsoleStyle(ConsoleColor color)
{
switch (color)
{
case ConsoleColor.Black:
return Black;
case ConsoleColor.Blue:
return BrightBlue;
case ConsoleColor.Cyan:
return BrightCyan;
case ConsoleColor.DarkBlue:
return Blue;
case ConsoleColor.DarkCyan:
return Cyan;
case ConsoleColor.DarkGray:
return BrightBlack;
case ConsoleColor.DarkGreen:
return Green;
case ConsoleColor.DarkMagenta:
return Magenta;
case ConsoleColor.DarkRed:
return Red;
case ConsoleColor.DarkYellow:
return Yellow;
case ConsoleColor.Gray:
return BrightBlack;
case ConsoleColor.Green:
return BrightGreen;
case ConsoleColor.Magenta:
return BrightMagenta;
case ConsoleColor.Red:
return BrightRed;
case ConsoleColor.White:
return BrightWhite;
case ConsoleColor.Yellow:
return BrightYellow;
default:
throw new ArgumentOutOfRangeException(nameof(color), color, null);
}
}
#endif
public bool Equals(ConsoleStyle other)
{
return EscapeSequence == other.EscapeSequence && Kind == other.Kind && IsInline == other.IsInline;
}
public override bool Equals(object obj)
{
return obj is ConsoleStyle other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
return ((((EscapeSequence?.GetHashCode() ?? 0) * 397) ^ (int) Kind) * 397) & IsInline.GetHashCode();
}
}
public static bool operator ==(ConsoleStyle left, ConsoleStyle right)
{
return left.Equals(right);
}
public static bool operator !=(ConsoleStyle left, ConsoleStyle right)
{
return !left.Equals(right);
}
public override string ToString()
{
return EscapeSequence;
}
}
}

View File

@@ -0,0 +1,716 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Consolus
{
using static ConsoleStyle;
public struct ConsoleStyleMarker
{
public ConsoleStyleMarker(ConsoleStyle style, bool enabled)
{
Style = style;
Enabled = enabled;
}
public readonly ConsoleStyle Style;
public readonly bool Enabled;
public static implicit operator ConsoleStyleMarker(ConsoleStyle style)
{
return new ConsoleStyleMarker(style, true);
}
}
public class ConsoleText : IList<ConsoleChar>
{
private readonly List<ConsoleChar> _chars;
private readonly List<ConsoleStyleMarker> _leadingStyles;
private readonly List<ConsoleStyleMarker> _trailingStyles;
private bool _changedCalled;
private readonly StringBuilder _currentEscape;
private EscapeState _escapeState;
public ConsoleText()
{
_chars = new List<ConsoleChar>();
_leadingStyles = new List<ConsoleStyleMarker>();
_trailingStyles = new List<ConsoleStyleMarker>();
SelectionEnd = -1;
_currentEscape = new StringBuilder();
}
public ConsoleText(string text) : this()
{
Append(text);
}
public Action Changed { get; set; }
public int VisibleCharacterStart { get; internal set; }
public int VisibleCharacterEnd { get; internal set; }
public int SelectionStart { get; set; }
public int SelectionEnd { get; set; }
public void Add(ConsoleChar item)
{
Insert(Count, item);
}
public void Clear()
{
ResetAndSet(null);
NotifyChanged();
}
private void ResetAndSet(string text)
{
_leadingStyles.Clear();
_chars.Clear();
_trailingStyles.Clear();
SelectionStart = 0;
SelectionEnd = -1;
if (text != null)
{
foreach (var c in text)
{
_chars.Add(c);
}
}
}
public void ReplaceBy(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
ResetAndSet(text);
NotifyChanged();
}
public void ClearSelection()
{
SelectionStart = 0;
SelectionEnd = -1;
}
public bool HasSelection => SelectionStart >= 0 && SelectionEnd <= Count && SelectionStart <= SelectionEnd;
public void ClearStyles()
{
_leadingStyles.Clear();
_trailingStyles.Clear();
for (var i = 0; i < _chars.Count; i++)
{
var consoleChar = _chars[i];
if (consoleChar.StyleMarkers != null)
{
consoleChar.StyleMarkers.Clear();
_chars[i] = consoleChar;
}
}
}
public bool ClearStyle(ConsoleStyle style)
{
var removed = RemoveStyle(style, _leadingStyles);
removed = RemoveStyle(style, _trailingStyles) || removed;
for (var i = 0; i < _chars.Count; i++)
{
var consoleChar = _chars[i];
if (consoleChar.StyleMarkers != null)
{
removed = RemoveStyle(style, consoleChar.StyleMarkers) || removed;
}
}
return removed;
}
private static bool RemoveStyle(ConsoleStyle style, List<ConsoleStyleMarker> markers)
{
bool styleRemoved = false;
if (markers == null) return false;
for (var i = markers.Count - 1; i >= 0; i--)
{
var consoleStyleMarker = markers[i];
if (consoleStyleMarker.Style == style)
{
markers.RemoveAt(i);
styleRemoved = true;
}
}
return styleRemoved;
}
public bool Contains(ConsoleChar item)
{
return _chars.Contains(item);
}
public void CopyTo(ConsoleChar[] array, int arrayIndex)
{
_chars.CopyTo(array, arrayIndex);
}
public bool Remove(ConsoleChar item)
{
return _chars.Remove(item);
}
public int Count => _chars.Count;
public bool IsReadOnly => false;
public int IndexOf(ConsoleChar item)
{
return _chars.IndexOf(item);
}
public void Insert(int index, ConsoleChar item)
{
InsertInternal(index, item);
NotifyChanged();
}
public void Insert(int index, string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
for (int i = 0; i < text.Length; i++)
{
InsertInternal(index + i, text[i]);
}
NotifyChanged();
}
private enum EscapeState
{
None,
Escape,
EscapeCsiParameterBytes,
EscapeCsiIntermediateBytes,
EscapeCsiFinalByte,
}
private void InsertInternal(int index, ConsoleChar item)
{
if (item.Value == '\x1b')
{
_currentEscape.Append(item.Value);
_escapeState = EscapeState.Escape;
return;
}
if (_escapeState != EscapeState.None)
{
bool isCharInvalidValid = false;
// All sequences start with followed by a char in the range 0x400x5F: (ASCII @AZ[\]^_)
var c = item.Value;
if (_escapeState >= EscapeState.EscapeCsiParameterBytes)
{
// CSI: ESC [ followed by
// - by any number (including none) of "parameter bytes", char in the 0x300x3F: (ASCII 09:;<=>?)
// - then by any number of "intermediate bytes" in the range 0x200x2F (ASCII space and !"#$%&'()*+,-./),
// - then finally by a single "final byte" in the range 0x400x7E (ASCII @AZ[\]^_`az{|}~)
switch (_escapeState)
{
case EscapeState.EscapeCsiParameterBytes:
if (c >= 0x30 && c <= 0x3F)
{
_currentEscape.Append(c);
}
else
{
goto case EscapeState.EscapeCsiIntermediateBytes;
}
break;
case EscapeState.EscapeCsiIntermediateBytes:
if (c >= 0x20 && c <= 0x2F)
{
_escapeState = EscapeState.EscapeCsiIntermediateBytes;
_currentEscape.Append(c);
}
else
{
goto case EscapeState.EscapeCsiFinalByte;
}
break;
case EscapeState.EscapeCsiFinalByte:
if (c >= 0x40 && c <= 0x7E)
{
_currentEscape.Append(c);
}
else
{
isCharInvalidValid = true;
}
var styleAsText = _currentEscape.ToString();
_currentEscape.Length = 0;
_escapeState = EscapeState.None;
InsertInternal(index, new ConsoleStyleMarker(Inline(styleAsText), true));
break;
}
}
else
{
if (_currentEscape.Length == 1)
{
_currentEscape.Append(c);
if (c == '[')
{
_escapeState = EscapeState.EscapeCsiParameterBytes;
}
else
{
var styleAsText = _currentEscape.ToString();
_currentEscape.Length = 0;
_escapeState = EscapeState.None;
InsertInternal(index, new ConsoleStyleMarker(Inline(styleAsText), true));
}
}
}
if (!isCharInvalidValid)
{
return;
}
// otherwise the character hasn't been consumed, so we propagate it as a real char.
}
// Copy any leading/trailing escapes
bool isFirstInsert = index == 0 && Count == 0;
bool isLastInsert = Count > 0 && index == Count;
List<ConsoleStyleMarker> copyFrom = isFirstInsert ? _leadingStyles : isLastInsert ? _trailingStyles : null;
if (copyFrom != null && copyFrom.Count > 0)
{
var escapes = item.StyleMarkers;
if (escapes == null)
{
escapes = new List<ConsoleStyleMarker>();
item.StyleMarkers = escapes;
}
for (int i = 0; i < copyFrom.Count; i++)
{
escapes.Insert(i, copyFrom[i]);
}
copyFrom.Clear();
}
_chars.Insert(index, item);
}
public void InsertRange(int index, string text, int textIndex, int length)
{
if (text == null) throw new ArgumentNullException(nameof(text));
var cursorIndex = index;
var end = textIndex + length;
for (int i = textIndex; i < end; i++)
{
var c = text[i];
InsertInternal(cursorIndex, c);
cursorIndex++;
}
NotifyChanged();
}
private void NotifyChanged()
{
var changed = Changed;
// Avoid recursive change
if (changed != null && !_changedCalled)
{
_changedCalled = true;
try
{
changed();
}
finally
{
_changedCalled = false;
}
}
}
public void RemoveAt(int index)
{
_chars.RemoveAt(index);
NotifyChanged();
}
public void RemoveRangeAt(int index, int length)
{
for (int i = 0; i < length; i++)
{
_chars.RemoveAt(index);
}
NotifyChanged();
}
public ConsoleChar this[int index]
{
get => _chars[index];
set => _chars[index] = value;
}
public void Add(ConsoleStyle style)
{
Add(style, true);
}
public void Add(ConsoleStyle style, bool enabled)
{
if (enabled) EnableStyleAt(Count, style);
else DisableStyleAt(Count, style);
}
public ConsoleText Append(char c)
{
Add(c);
return this;
}
public ConsoleText Begin(ConsoleStyle style)
{
Add(style);
return this;
}
public ConsoleText End(ConsoleStyle style)
{
Add(style, false);
return this;
}
public ConsoleText AppendLine(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
AddInternal(text);
Add('\n');
return this;
}
public ConsoleText AppendLine()
{
Add('\n');
return this;
}
public void AddRange(ConsoleText text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
foreach(var c in text._leadingStyles)
{
InsertInternal(Count, c);
}
foreach(var c in text._chars)
{
InsertInternal(Count, c);
}
foreach (var c in text._trailingStyles)
{
InsertInternal(Count, c);
}
}
public ConsoleText Append(ConsoleStyle style, bool enabled)
{
Add(style, enabled);
return this;
}
public ConsoleText Append(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
AddInternal(text);
NotifyChanged();
return this;
}
private void AddInternal(string text)
{
foreach (var c in text)
{
InsertInternal(Count, c);
}
}
public void EnableStyleAt(int index, ConsoleStyle style)
{
InsertInternal(index, style);
}
public void DisableStyleAt(int index, ConsoleStyle style)
{
InsertInternal(index, new ConsoleStyleMarker(style, false));
}
private void InsertInternal(int index, ConsoleStyleMarker marker)
{
if ((uint)index > (uint)Count) throw new ArgumentOutOfRangeException($"Invalid character index {index} not within range [0, {Count}]");
var isFirst = index == 0 && Count == 0;
var isLast = index == Count;
List<ConsoleStyleMarker> list;
if (isFirst)
{
list = _leadingStyles;
}
else if (isLast)
{
list = _trailingStyles;
}
else
{
var c = this[index];
list = c.StyleMarkers;
if (list == null)
{
list = new List<ConsoleStyleMarker>();
c.StyleMarkers = list;
this[index] = c;
}
}
list.Add(marker);
}
private void RenderLeadingTrailingStyles(TextWriter writer, bool displayStyle, bool leading, RunningStyles runningStyles)
{
var styles = leading ? _leadingStyles : _trailingStyles;
foreach (var consoleStyle in styles)
{
runningStyles.ApplyStyle(consoleStyle);
if (displayStyle)
{
runningStyles.Render(writer);
}
}
}
public void Render(ConsoleTextWriter writer, bool renderEscape = true)
{
VisibleCharacterStart = writer.VisibleCharacterCount;
if (HasSelection)
{
RenderWithSelection(writer, renderEscape);
}
else
{
var styles = new RunningStyles();
if (renderEscape) RenderLeadingTrailingStyles(writer, true, true, styles);
RenderInternal(writer, 0, Count, renderEscape, styles);
if (renderEscape) RenderLeadingTrailingStyles(writer, true, false, styles);
}
VisibleCharacterEnd = writer.VisibleCharacterCount - 1;
}
private void RenderWithSelection(ConsoleTextWriter writer, bool renderEscape = true)
{
if (writer == null) throw new ArgumentNullException(nameof(writer));
// TODO: TLS cache
var pendingStyles = renderEscape ? new RunningStyles() : null;
if (renderEscape)
{
RenderLeadingTrailingStyles(writer, true, true, pendingStyles);
}
// Display text before without selection
RenderInternal(writer, 0, SelectionStart, renderEscape, pendingStyles);
if (renderEscape)
{
// Disable any attribute sequences
Reset.Render(writer);
Reversed.Render(writer);
}
// Render the string with reverse video
RenderInternal(writer, SelectionStart, SelectionEnd, false, pendingStyles);
if (renderEscape)
{
// Disable any attribute sequences
Reset.Render(writer);
pendingStyles.Render(writer);
}
// Display text after without selection
RenderInternal(writer, SelectionEnd, this.Count, renderEscape, pendingStyles);
if (renderEscape) RenderLeadingTrailingStyles(writer, true, false, pendingStyles);
}
private void RenderInternal(ConsoleTextWriter writer, int start, int end, bool displayStyle, RunningStyles runningStyles)
{
for(int i = start; i < end; i++)
{
var c = this[i];
if ((displayStyle || runningStyles != null) && c.StyleMarkers != null)
{
foreach (var esc in c.StyleMarkers)
{
runningStyles.ApplyStyle(esc);
if (displayStyle)
{
runningStyles.Render(writer);
}
}
}
var charValue = c.Value;
// Fill the remaining line with space to clear the space
if (charValue == '\n')
{
var toComplete = Console.BufferWidth - writer.VisibleCharacterCount % Console.BufferWidth;
for (int j = 0; j < toComplete; j++)
{
writer.Write(' ');
}
writer.VisibleCharacterCount += toComplete;
}
if (charValue == '\n' || charValue >= ' ')
{
writer.Write(charValue);
}
if (charValue != '\n' || charValue >= ' ')
{
writer.VisibleCharacterCount++;
}
}
}
public static implicit operator ConsoleText(string text)
{
return new ConsoleText(text);
}
public IEnumerator<ConsoleChar> GetEnumerator()
{
return _chars.GetEnumerator();
}
public override string ToString()
{
var builder = new StringBuilder();
foreach (var c in this)
{
builder.Append(c.Value);
}
return builder.ToString();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) _chars).GetEnumerator();
}
private class RunningStyles : Dictionary<ConsoleStyleKind, List<ConsoleStyle>>
{
public void Render(TextWriter writer)
{
// Disable any attribute sequences
Reset.Render(writer);
foreach (var stylePair in this)
{
var list = stylePair.Value;
if (stylePair.Key == ConsoleStyleKind.Color)
{
if (list.Count > 0)
{
list[list.Count - 1].Render(writer);
}
}
else if (stylePair.Key == ConsoleStyleKind.Format)
{
foreach (var item in list)
{
item.Render(writer);
}
}
}
}
public void ApplyStyle(ConsoleStyleMarker styleMarker)
{
var style = styleMarker.Style;
switch (style.Kind)
{
case ConsoleStyleKind.Color:
case ConsoleStyleKind.Format:
if (!TryGetValue(style.Kind, out var list))
{
list = new List<ConsoleStyle>();
Add(style.Kind, list);
}
if (styleMarker.Enabled)
{
list.Add(style);
}
else
{
for (var i = list.Count - 1; i >= 0; i--)
{
var item = list[i];
if (item == style)
{
list.RemoveAt(i);
break;
}
}
}
break;
case ConsoleStyleKind.Reset:
// An inline reset applies only to inline styles
if (style.IsInline)
{
// Clear only inline styles (and not regular)
foreach(var keyPair in this)
{
var styleList = keyPair.Value;
for(int i = styleList.Count - 1; i >= 0; i--)
{
var styleIn = styleList[i];
if (styleIn.IsInline)
{
styleList.RemoveAt(i);
}
}
}
}
else
{
Clear();
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace Consolus
{
public class ConsoleTextWriter : TextWriter
{
private char[] _buffer;
private int _count;
public ConsoleTextWriter(TextWriter writer)
{
Backend = writer ?? throw new ArgumentNullException(nameof(writer));
_buffer = new char[4096];
}
public override Encoding Encoding => Backend.Encoding;
public TextWriter Backend { get; }
public void Commit()
{
VisibleCharacterCount = 0;
Backend.Write(_buffer, 0, _count);
_count = 0;
}
public override void Write(char[] buffer, int index, int count)
{
for (int i = 0; i < count; i++)
{
Write(buffer[index + i]);
}
}
public override void Write(string value)
{
if (value == null) return;
for (int i = 0; i < value.Length; i++)
{
Write(value[i]);
}
}
public int VisibleCharacterCount { get; internal set; }
public override void Write(char value)
{
WriteInternal(value);
}
private void EnsureCapacity(int min)
{
if (_buffer.Length < min)
{
int newCapacity = _buffer.Length * 2;
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newCapacity > int.MaxValue) newCapacity = int.MaxValue;
if (newCapacity < min) newCapacity = min;
var newBuffer = new char[newCapacity];
if (_count > 0)
{
Array.Copy(_buffer, 0,newBuffer, 0, _count);
}
_buffer = newBuffer;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteInternal(char value)
{
var newCount = _count + 1;
if (_buffer.Length < newCount)
{
EnsureCapacity(newCount);
}
_buffer[_count] = value;
_count++;
}
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<Target Name="_CheckForGenerateAppxPackageOnBuild" />
</Project>

View File

@@ -0,0 +1,71 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Consolus
{
internal static class WindowsHelper
{
private const int STD_OUTPUT_HANDLE = -11;
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
public static void EnableAnsiEscapeOnWindows()
{
var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (iStdOut != IntPtr.Zero && GetConsoleMode(iStdOut, out uint outConsoleMode))
{
SetConsoleMode(iStdOut, outConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
[DllImport("kernel32")]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32")]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
[DllImport("kernel32")]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32")]
private static extern IntPtr GetConsoleWindow();
[DllImport("kernel32")]
private static extern IntPtr GetCurrentProcessId();
[DllImport("user32")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, ref IntPtr ProcessId);
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool HasConsoleWindows()
{
try
{
return GetConsoleWindow() != IntPtr.Zero;
}
catch
{
return Environment.UserInteractive;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool IsSelfConsoleWindows()
{
try
{
IntPtr hConsole = GetConsoleWindow();
IntPtr hProcessId = IntPtr.Zero;
GetWindowThreadProcessId(hConsole, ref hProcessId);
var processId = GetCurrentProcessId();
return processId == hProcessId;
}
catch
{
return true;
}
}
}
}