Sicherung
This commit is contained in:
27
Kalk/Consolus/ConsoleChar.cs
Normal file
27
Kalk/Consolus/ConsoleChar.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Kalk/Consolus/ConsoleHelper.cs
Normal file
49
Kalk/Consolus/ConsoleHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
217
Kalk/Consolus/ConsoleRenderer.cs
Normal file
217
Kalk/Consolus/ConsoleRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
750
Kalk/Consolus/ConsoleRepl.cs
Normal file
750
Kalk/Consolus/ConsoleRepl.cs
Normal 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,
|
||||
}
|
||||
|
||||
}
|
||||
228
Kalk/Consolus/ConsoleStyle.cs
Normal file
228
Kalk/Consolus/ConsoleStyle.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
716
Kalk/Consolus/ConsoleText.cs
Normal file
716
Kalk/Consolus/ConsoleText.cs
Normal 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 0x40–0x5F: (ASCII @A–Z[\]^_)
|
||||
var c = item.Value;
|
||||
|
||||
if (_escapeState >= EscapeState.EscapeCsiParameterBytes)
|
||||
{
|
||||
// CSI: ESC [ followed by
|
||||
// - by any number (including none) of "parameter bytes", char in the 0x30–0x3F: (ASCII 0–9:;<=>?)
|
||||
// - then by any number of "intermediate bytes" in the range 0x20–0x2F (ASCII space and !"#$%&'()*+,-./),
|
||||
// - then finally by a single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~)
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Kalk/Consolus/ConsoleTextWriter.cs
Normal file
86
Kalk/Consolus/ConsoleTextWriter.cs
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Kalk/Consolus/Consolus.csproj
Normal file
10
Kalk/Consolus/Consolus.csproj
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="_CheckForGenerateAppxPackageOnBuild" />
|
||||
|
||||
</Project>
|
||||
71
Kalk/Consolus/WindowsHelper.cs
Normal file
71
Kalk/Consolus/WindowsHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user