mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 09:06:41 +02:00
Add snapcraft & chocolatey manifests and move code to src
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Timers;
|
||||
|
||||
using YesNt.Interpreter.Runtime;
|
||||
|
||||
namespace YesNt.CodeEditor;
|
||||
|
||||
internal class TextEditor
|
||||
{
|
||||
private readonly InputHandler inputHandler;
|
||||
private readonly SyntaxHighlighter syntaxHighlighter;
|
||||
private readonly List<string> debugOutput = [];
|
||||
|
||||
private readonly Point oldSize = new Point(0, 0);
|
||||
public YesNtInterpreter YesNtInterpreter { get; } = new();
|
||||
public int LineOffset { get; set; } = 0;
|
||||
public List<string> Lines { get; } = [];
|
||||
public Point CursorPosition { get; } = new(0, 0);
|
||||
public Mode EditMode { get; set; } = Mode.Command;
|
||||
public string CurrentPath { get; set; } = string.Empty;
|
||||
public bool IsStepDebugMode { get; set; }
|
||||
|
||||
public TextEditor(string path) : this()
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
_ = Load(path);
|
||||
}
|
||||
}
|
||||
|
||||
public TextEditor()
|
||||
{
|
||||
YesNtInterpreter.OnDebugOutput += YesNtInterpreter_OnDebugOutput;
|
||||
YesNtInterpreter.OnLineExecuted += YesNtInterpreter_OnLineExecuted;
|
||||
syntaxHighlighter = new(YesNtInterpreter.StatementInformation);
|
||||
inputHandler = new InputHandler(this);
|
||||
Console.CancelKeyPress += Console_CancelKeyPress;
|
||||
|
||||
Timer timer = new Timer(100);
|
||||
timer.Elapsed += (s, e) =>
|
||||
{
|
||||
if (SizeChanged() && EditMode != Mode.Debug)
|
||||
{
|
||||
Display(true);
|
||||
|
||||
if (EditMode == Mode.Command)
|
||||
{
|
||||
InputHandler.WriteStatus(string.Empty);
|
||||
Console.SetCursorPosition(3, Console.WindowHeight - 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
Console.Clear();
|
||||
|
||||
Display(true);
|
||||
do
|
||||
{
|
||||
Display(false);
|
||||
} while (inputHandler.HandleInput());
|
||||
|
||||
Console.Clear();
|
||||
}
|
||||
|
||||
public void Display(bool drawAll)
|
||||
{
|
||||
Console.CursorVisible = false;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
Console.BackgroundColor = ConsoleColor.Black;
|
||||
|
||||
Console.SetCursorPosition(0, 0);
|
||||
|
||||
if (SizeChanged())
|
||||
{
|
||||
drawAll = true;
|
||||
InputHandler.WriteStatus(string.Empty);
|
||||
}
|
||||
|
||||
if (LineOffset < 0 || CursorPosition.Y < 0 || CursorPosition.Y < 0)
|
||||
{
|
||||
LineOffset = 0;
|
||||
CursorPosition.Y = 0;
|
||||
CursorPosition.X = 0;
|
||||
}
|
||||
|
||||
for (int i = LineOffset; i < Console.WindowHeight + LineOffset - 2; i++)
|
||||
{
|
||||
Console.SetCursorPosition(0, i - LineOffset);
|
||||
|
||||
string lineCountString = $"{i + 1}".PadRight(GetSpacing(), ' ') + "| ";
|
||||
if (i < Lines.Count)
|
||||
{
|
||||
if (CursorPosition.Y == i || drawAll)
|
||||
{
|
||||
Console.Write(lineCountString);
|
||||
string printLine = $"{Lines[i][..Math.Min(Lines[i].Length, Console.WindowWidth)]}".TrimEnd();
|
||||
syntaxHighlighter.Write(printLine);
|
||||
Console.Write(new string(' ', Math.Max(Console.WindowWidth - lineCountString.Length - printLine.Length, 0)));
|
||||
}
|
||||
}
|
||||
else if (drawAll || CursorPosition.Y == i)
|
||||
{
|
||||
Console.Write(lineCountString + new string(' ', Console.WindowWidth - lineCountString.Length));
|
||||
}
|
||||
}
|
||||
|
||||
Console.SetCursorPosition(0, Console.WindowHeight - 3);
|
||||
Console.Write(new string('-', Console.WindowWidth));
|
||||
Console.SetCursorPosition(0, Console.WindowHeight - 2);
|
||||
Console.Write(">>>" + new string(' ', Console.WindowWidth - 3));
|
||||
|
||||
Console.SetCursorPosition(Math.Min(CursorPosition.X + GetSpacing() + 2, Console.WindowWidth - 1), Math.Min(CursorPosition.Y - LineOffset, Console.WindowHeight - 4));
|
||||
|
||||
Console.CursorVisible = true;
|
||||
}
|
||||
|
||||
public bool Load(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(path)))
|
||||
{
|
||||
path = Path.ChangeExtension(path, "ynt");
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
InputHandler.WriteStatus("File does not exist!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Lines.Clear();
|
||||
Lines.AddRange(File.ReadAllLines(path));
|
||||
CurrentPath = path;
|
||||
InputHandler.WriteStatus("File Loaded!");
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Save(string input, bool loadIfExists)
|
||||
{
|
||||
string path;
|
||||
if (input.Split(' ').Length == 2)
|
||||
{
|
||||
path = input.Split(' ')[1];
|
||||
|
||||
if (CurrentPath.Trim() != path.Trim() && loadIfExists)
|
||||
{
|
||||
return Load(path);
|
||||
}
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(path)))
|
||||
{
|
||||
path = Path.ChangeExtension(path, "ynt");
|
||||
}
|
||||
CurrentPath = path;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(CurrentPath))
|
||||
{
|
||||
path = CurrentPath;
|
||||
}
|
||||
else if (input.Split(' ').Length > 2)
|
||||
{
|
||||
InputHandler.WriteStatus("Invalid arguments!");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
InputHandler.WriteStatus("File path is empty! (Save the file before you can use this command)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(path)))
|
||||
{
|
||||
path = Path.ChangeExtension(path, "ynt");
|
||||
}
|
||||
|
||||
while (Lines.Count > 0 && string.IsNullOrWhiteSpace(Lines[^1]))
|
||||
{
|
||||
Lines.RemoveAt(Lines.Count - 1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllLines(path, Lines);
|
||||
InputHandler.WriteStatus("File Saved!");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
InputHandler.WriteStatus(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetSpacing()
|
||||
{
|
||||
int padding = (Console.WindowHeight + LineOffset - 3).ToString().Length;
|
||||
padding = Math.Max(padding, Lines.Count.ToString().Length);
|
||||
padding += 1;
|
||||
return padding;
|
||||
}
|
||||
|
||||
public void FormatLines()
|
||||
{
|
||||
const int indentationSize = 4;
|
||||
List<string> blockStack = [];
|
||||
|
||||
for (int i = 0; i < Lines.Count; i++)
|
||||
{
|
||||
string trimmed = Lines[i].Trim(' ');
|
||||
if (string.IsNullOrWhiteSpace(trimmed))
|
||||
{
|
||||
Lines[i] = string.Empty;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.StartsWith('#'))
|
||||
{
|
||||
Lines[i] = trimmed;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed == "else:")
|
||||
{
|
||||
for (int j = blockStack.Count - 1; j >= 0; j--)
|
||||
{
|
||||
if (blockStack[j] is "if" or "else")
|
||||
{
|
||||
blockStack.RemoveAt(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool isTerminatingStatement = trimmed == "exit"
|
||||
|| trimmed.StartsWith("throw ", StringComparison.Ordinal)
|
||||
|| trimmed.StartsWith("error ", StringComparison.Ordinal);
|
||||
bool closesFunctionBlock = isTerminatingStatement && blockStack.Count > 0 && blockStack[^1] == "func";
|
||||
|
||||
if (trimmed == "end_if")
|
||||
{
|
||||
for (int j = blockStack.Count - 1; j >= 0; j--)
|
||||
{
|
||||
if (blockStack[j] is "if" or "else")
|
||||
{
|
||||
blockStack.RemoveAt(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (trimmed == "end_while")
|
||||
{
|
||||
for (int j = blockStack.Count - 1; j >= 0; j--)
|
||||
{
|
||||
if (blockStack[j] == "while")
|
||||
{
|
||||
blockStack.RemoveAt(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (trimmed == "return")
|
||||
{
|
||||
for (int j = blockStack.Count - 1; j >= 0; j--)
|
||||
{
|
||||
if (blockStack[j] == "func")
|
||||
{
|
||||
blockStack.RemoveAt(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int lineIndentation = closesFunctionBlock ? Math.Max(0, blockStack.Count - 1) : blockStack.Count;
|
||||
Lines[i] = new string(' ', lineIndentation * indentationSize) + trimmed;
|
||||
|
||||
if (!isTerminatingStatement && (
|
||||
(trimmed.StartsWith("if ", StringComparison.Ordinal) && trimmed.EndsWith(':'))
|
||||
|| (trimmed.StartsWith("while ", StringComparison.Ordinal) && trimmed.EndsWith(':'))
|
||||
|| (trimmed.StartsWith("func ", StringComparison.Ordinal) && trimmed.EndsWith(':'))
|
||||
|| trimmed == "else:"))
|
||||
{
|
||||
if (trimmed.StartsWith("if ", StringComparison.Ordinal))
|
||||
{
|
||||
blockStack.Add("if");
|
||||
}
|
||||
else if (trimmed.StartsWith("while ", StringComparison.Ordinal))
|
||||
{
|
||||
blockStack.Add("while");
|
||||
}
|
||||
else if (trimmed.StartsWith("func ", StringComparison.Ordinal))
|
||||
{
|
||||
blockStack.Add("func");
|
||||
}
|
||||
else
|
||||
{
|
||||
blockStack.Add("else");
|
||||
}
|
||||
}
|
||||
|
||||
if (isTerminatingStatement)
|
||||
{
|
||||
while (blockStack.Count > 0 && blockStack[^1] != "func")
|
||||
{
|
||||
blockStack.RemoveAt(blockStack.Count - 1);
|
||||
}
|
||||
|
||||
if (closesFunctionBlock && blockStack.Count > 0 && blockStack[^1] == "func")
|
||||
{
|
||||
blockStack.RemoveAt(blockStack.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ToLiteral(string input)
|
||||
{
|
||||
return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(input, false);
|
||||
}
|
||||
|
||||
private void YesNtInterpreter_OnDebugOutput(string output)
|
||||
{
|
||||
debugOutput.Add(output);
|
||||
}
|
||||
|
||||
private void YesNtInterpreter_OnLineExecuted(DebugEventArgs e)
|
||||
{
|
||||
lock (Console.Out)
|
||||
{
|
||||
if (e is not null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
|
||||
string sharedString = (Console.CursorLeft != 0) ? Environment.NewLine : string.Empty;
|
||||
sharedString += (e.IsTask ? $"[Task: {e.TaskId}]" : string.Empty) + $"[{e.LineNumber}]";
|
||||
|
||||
if (e.OriginalLine == e.CurrentLine)
|
||||
{
|
||||
Console.WriteLine($"{sharedString}[{ToLiteral(e.CurrentLine)}] ==>");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{sharedString}[{ToLiteral(e.OriginalLine)}] => [{ToLiteral(e.CurrentLine)}] ==>");
|
||||
}
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
string[] outputs = debugOutput.ToArray();
|
||||
debugOutput.Clear();
|
||||
|
||||
foreach (string output in outputs)
|
||||
{
|
||||
Console.Write(output);
|
||||
}
|
||||
|
||||
if (IsStepDebugMode && e is not null && !e.IsTask)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine("[Step] Press any key for next line (Ctrl+C to stop)...");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
_ = Console.ReadKey(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
switch (EditMode)
|
||||
{
|
||||
case Mode.Debug:
|
||||
YesNtInterpreter.Stop();
|
||||
EditMode = Mode.Command;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool SizeChanged()
|
||||
{
|
||||
if (oldSize.X != Console.WindowWidth || oldSize.Y != Console.WindowHeight)
|
||||
{
|
||||
oldSize.X = Console.WindowWidth;
|
||||
oldSize.Y = Console.WindowHeight;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Point(int x, int y)
|
||||
{
|
||||
public int X { get; set; } = x;
|
||||
public int Y { get; set; } = y;
|
||||
}
|
||||
|
||||
internal enum Mode
|
||||
{
|
||||
Edit,
|
||||
Command,
|
||||
Debug
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Critical Code Smell", "S3998:Threads should not lock on objects with weak identity", Justification = "<Pending>", Scope = "member", Target = "~M:YesNt.CodeEditor.TextEditor.YesNtInterpreter_OnLineExecuted(YesNt.Interpreter.Runtime.DebugEventArgs)")]
|
||||
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace YesNt.CodeEditor;
|
||||
|
||||
internal class InputHandler(TextEditor textEditor)
|
||||
{
|
||||
private readonly TextEditor textEditor = textEditor;
|
||||
|
||||
public bool HandleInput()
|
||||
{
|
||||
while (Console.KeyAvailable)
|
||||
{
|
||||
_ = Console.ReadKey(true);
|
||||
}
|
||||
if (textEditor.EditMode == Mode.Edit)
|
||||
{
|
||||
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
|
||||
|
||||
if ((ConsoleModifiers.Alt & keyInfo.Modifiers) == ConsoleModifiers.Alt)
|
||||
{
|
||||
switch (keyInfo.Key)
|
||||
{
|
||||
case ConsoleKey.C:
|
||||
textEditor.EditMode = Mode.Command;
|
||||
return true;
|
||||
|
||||
case ConsoleKey.B:
|
||||
int position = textEditor.Lines.Count;
|
||||
textEditor.CursorPosition.Y = position - 1;
|
||||
textEditor.LineOffset = Math.Max(position - Console.WindowHeight + 3, 0);
|
||||
|
||||
textEditor.Display(true);
|
||||
return true;
|
||||
|
||||
case ConsoleKey.T:
|
||||
textEditor.CursorPosition.Y = 0;
|
||||
textEditor.LineOffset = 0;
|
||||
|
||||
textEditor.Display(true);
|
||||
return true;
|
||||
|
||||
case ConsoleKey.S:
|
||||
textEditor.CursorPosition.X = 0;
|
||||
return true;
|
||||
|
||||
case ConsoleKey.E:
|
||||
textEditor.CursorPosition.X = textEditor.Lines.Count > textEditor.CursorPosition.Y ? textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length : 0;
|
||||
return true;
|
||||
|
||||
case ConsoleKey.R:
|
||||
ExecuteWithDebugScreen("run", false, false);
|
||||
textEditor.Display(true);
|
||||
return true;
|
||||
|
||||
case ConsoleKey.D:
|
||||
ExecuteWithDebugScreen("debug", true, false);
|
||||
textEditor.Display(true);
|
||||
return true;
|
||||
|
||||
case ConsoleKey.F:
|
||||
textEditor.FormatLines();
|
||||
textEditor.Display(true);
|
||||
WriteStatus("Formatted!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (keyInfo.Key == ConsoleKey.DownArrow)
|
||||
{
|
||||
textEditor.CursorPosition.Y++;
|
||||
if (textEditor.CursorPosition.Y - textEditor.LineOffset >= Console.WindowHeight - 3)
|
||||
{
|
||||
textEditor.LineOffset++;
|
||||
textEditor.Display(true);
|
||||
}
|
||||
}
|
||||
else if (keyInfo.Key == ConsoleKey.UpArrow)
|
||||
{
|
||||
if (textEditor.CursorPosition.Y > 0)
|
||||
{
|
||||
textEditor.CursorPosition.Y--;
|
||||
if (textEditor.CursorPosition.Y - textEditor.LineOffset < 0 && textEditor.LineOffset > 0)
|
||||
{
|
||||
textEditor.LineOffset--;
|
||||
textEditor.Display(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (keyInfo.Key == ConsoleKey.LeftArrow)
|
||||
{
|
||||
if (textEditor.CursorPosition.X > 0)
|
||||
{
|
||||
textEditor.CursorPosition.X--;
|
||||
}
|
||||
}
|
||||
else if (keyInfo.Key == ConsoleKey.RightArrow)
|
||||
{
|
||||
if (textEditor.CursorPosition.X + textEditor.GetSpacing() + 3 < Console.WindowWidth)
|
||||
{
|
||||
textEditor.CursorPosition.X++;
|
||||
}
|
||||
}
|
||||
else if (keyInfo.Key == ConsoleKey.Enter)
|
||||
{
|
||||
while (textEditor.Lines.Count <= textEditor.CursorPosition.Y)
|
||||
{
|
||||
textEditor.Lines.Add("");
|
||||
}
|
||||
textEditor.Lines.Insert(textEditor.CursorPosition.Y, "");
|
||||
|
||||
string line = textEditor.Lines[textEditor.CursorPosition.Y + 1];
|
||||
|
||||
textEditor.Lines[textEditor.CursorPosition.Y] = line[..Math.Min(textEditor.CursorPosition.X, line.Length)];
|
||||
textEditor.Lines[textEditor.CursorPosition.Y + 1] = line[Math.Min(textEditor.CursorPosition.X, line.Length)..];
|
||||
|
||||
textEditor.CursorPosition.X = 0;
|
||||
textEditor.CursorPosition.Y++;
|
||||
|
||||
if (textEditor.CursorPosition.Y - textEditor.LineOffset >= Console.WindowHeight - 3)
|
||||
{
|
||||
textEditor.LineOffset++;
|
||||
}
|
||||
textEditor.Display(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (textEditor.Lines.Count <= textEditor.CursorPosition.Y)
|
||||
{
|
||||
textEditor.Lines.Add("");
|
||||
}
|
||||
|
||||
StringBuilder lineBuilder = new StringBuilder(textEditor.Lines[textEditor.CursorPosition.Y]);
|
||||
while (lineBuilder.Length <= textEditor.CursorPosition.X)
|
||||
{
|
||||
_ = lineBuilder.Append(' ');
|
||||
}
|
||||
|
||||
textEditor.Lines[textEditor.CursorPosition.Y] = lineBuilder.ToString();
|
||||
|
||||
if (keyInfo.Key is ConsoleKey.Backspace or ConsoleKey.Delete)
|
||||
{
|
||||
if (keyInfo.Key == ConsoleKey.Delete)
|
||||
{
|
||||
textEditor.CursorPosition.X++;
|
||||
}
|
||||
|
||||
if (textEditor.CursorPosition.X > 0)
|
||||
{
|
||||
if (textEditor.Lines[textEditor.CursorPosition.Y][textEditor.CursorPosition.X - 1] == ' ' && textEditor.CursorPosition.X > textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length)
|
||||
{
|
||||
textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd();
|
||||
textEditor.CursorPosition.X = textEditor.Lines[textEditor.CursorPosition.Y].Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].Remove(textEditor.CursorPosition.X - 1, 1);
|
||||
textEditor.CursorPosition.X--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (textEditor.CursorPosition.Y > 0)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(textEditor.Lines[textEditor.CursorPosition.Y - 1]))
|
||||
{
|
||||
textEditor.Lines.RemoveAt(--textEditor.CursorPosition.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
textEditor.CursorPosition.Y--;
|
||||
textEditor.CursorPosition.X = textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length;
|
||||
textEditor.Lines[textEditor.CursorPosition.Y] += textEditor.Lines[textEditor.CursorPosition.Y + 1];
|
||||
textEditor.Lines.RemoveAt(textEditor.CursorPosition.Y + 1);
|
||||
}
|
||||
if (textEditor.CursorPosition.Y - textEditor.LineOffset < 0 && textEditor.LineOffset > 0)
|
||||
{
|
||||
textEditor.LineOffset--;
|
||||
}
|
||||
textEditor.Display(true);
|
||||
}
|
||||
}
|
||||
|
||||
while (textEditor.Lines.Count > 0 && string.IsNullOrWhiteSpace(textEditor.Lines[^1]))
|
||||
{
|
||||
textEditor.Lines.RemoveAt(textEditor.Lines.Count - 1);
|
||||
}
|
||||
}
|
||||
else if (textEditor.CursorPosition.X + textEditor.GetSpacing() + 3 < Console.WindowWidth)
|
||||
{
|
||||
char input = keyInfo.KeyChar;
|
||||
if (!char.IsControl(input))
|
||||
{
|
||||
textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].Insert(textEditor.CursorPosition.X, input.ToString());
|
||||
textEditor.CursorPosition.X++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (textEditor.EditMode == Mode.Command)
|
||||
{
|
||||
Console.SetCursorPosition(3, Console.WindowHeight - 2);
|
||||
|
||||
string input = Console.ReadLine() ?? string.Empty;
|
||||
string command = input.Split(' ')[0].Trim();
|
||||
string path;
|
||||
|
||||
Console.CursorVisible = false;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "edit":
|
||||
WriteStatus(string.Empty);
|
||||
textEditor.EditMode = Mode.Edit;
|
||||
break;
|
||||
|
||||
case "line":
|
||||
WriteStatus(string.Empty);
|
||||
|
||||
bool success = false;
|
||||
int lineNumber = 0;
|
||||
if (input.Split(' ').Length == 2)
|
||||
{
|
||||
success = int.TryParse(input.Split(' ')[1], out lineNumber);
|
||||
}
|
||||
|
||||
if (success && lineNumber > 0)
|
||||
{
|
||||
textEditor.CursorPosition.Y = lineNumber - 1;
|
||||
textEditor.CursorPosition.X = 0;
|
||||
textEditor.LineOffset = lineNumber - 1;
|
||||
textEditor.EditMode = Mode.Edit;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteStatus("Invalid line number!");
|
||||
}
|
||||
break;
|
||||
|
||||
case "save":
|
||||
_ = textEditor.Save(input, false);
|
||||
break;
|
||||
|
||||
case "run":
|
||||
ExecuteWithDebugScreen(input, false, false);
|
||||
break;
|
||||
|
||||
case "debug":
|
||||
if (TryParseDebugCommand(input, out bool stepMode, out string parsedPath))
|
||||
{
|
||||
string saveInput = string.IsNullOrWhiteSpace(parsedPath) ? "debug" : $"debug {parsedPath}";
|
||||
ExecuteWithDebugScreen(saveInput, true, stepMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteStatus("Invalid arguments!");
|
||||
}
|
||||
break;
|
||||
|
||||
case "load":
|
||||
if (input.Split(' ').Length == 2)
|
||||
{
|
||||
path = input.Split(' ')[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteStatus("Invalid arguments!");
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_ = textEditor.Load(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteStatus(ex.Message);
|
||||
}
|
||||
textEditor.LineOffset = 0;
|
||||
textEditor.CursorPosition.X = 0;
|
||||
textEditor.CursorPosition.Y = 0;
|
||||
break;
|
||||
|
||||
case "new":
|
||||
|
||||
textEditor.LineOffset = 0;
|
||||
textEditor.CursorPosition.X = 0;
|
||||
textEditor.CursorPosition.Y = 0;
|
||||
textEditor.CurrentPath = string.Empty;
|
||||
textEditor.Lines.Clear();
|
||||
WriteStatus(string.Empty);
|
||||
break;
|
||||
|
||||
case "format":
|
||||
textEditor.FormatLines();
|
||||
WriteStatus("Formatted!");
|
||||
break;
|
||||
|
||||
case "exit":
|
||||
return false;
|
||||
|
||||
default:
|
||||
WriteStatus("Command not found!");
|
||||
break;
|
||||
}
|
||||
textEditor.Display(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void WriteStatus(string input)
|
||||
{
|
||||
Console.SetCursorPosition(0, Console.WindowHeight - 1);
|
||||
Console.Write(input + new string(' ', Console.WindowWidth - input.Length - 1));
|
||||
}
|
||||
|
||||
private static bool TryParseDebugCommand(string input, out bool stepMode, out string path)
|
||||
{
|
||||
stepMode = false;
|
||||
path = string.Empty;
|
||||
|
||||
string[] parts = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 0 || !parts[0].Equals("debug", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 1; i < parts.Length; i++)
|
||||
{
|
||||
string token = parts[i];
|
||||
if (token.Equals("step", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (stepMode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stepMode = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
path = token;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ExecuteWithDebugScreen(string saveInput, bool debugMode, bool stepMode)
|
||||
{
|
||||
string[] parts = saveInput.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
bool hasPathArgument = parts.Length > 1;
|
||||
bool canRunUnsavedBuffer = !hasPathArgument && string.IsNullOrWhiteSpace(textEditor.CurrentPath);
|
||||
|
||||
bool canExecute = canRunUnsavedBuffer || textEditor.Save(saveInput, true);
|
||||
if (!canExecute)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mode previousMode = textEditor.EditMode;
|
||||
|
||||
textEditor.EditMode = Mode.Debug;
|
||||
textEditor.IsStepDebugMode = stepMode;
|
||||
Console.Clear();
|
||||
Console.CursorVisible = true;
|
||||
|
||||
if (canRunUnsavedBuffer)
|
||||
{
|
||||
textEditor.YesNtInterpreter.Execute([.. textEditor.Lines], debugMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath, debugMode);
|
||||
}
|
||||
|
||||
while (Console.KeyAvailable)
|
||||
{
|
||||
_ = Console.ReadKey(true);
|
||||
}
|
||||
_ = Console.ReadKey();
|
||||
WriteStatus(string.Empty);
|
||||
textEditor.IsStepDebugMode = false;
|
||||
textEditor.EditMode = previousMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
using YesNt.CodeEditor;
|
||||
|
||||
TextEditor textEditor = args.Length > 0 ? new TextEditor(args[0]) : new TextEditor();
|
||||
textEditor.Run();
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"YesNt.CodeEditor": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"WSL": {
|
||||
"commandName": "WSL2",
|
||||
"environmentVariables": {},
|
||||
"distributionName": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.CodeEditor;
|
||||
|
||||
internal partial class SyntaxHighlighter(ReadOnlyCollection<StatementInformation> statementInformation)
|
||||
{
|
||||
private readonly ReadOnlyCollection<StatementInformation> statementInformation = statementInformation;
|
||||
private readonly string[] replacementValues = StringExtensions.ReplacementRules.Values.ToArray();
|
||||
|
||||
public static string Base64Encode(string plainText)
|
||||
{
|
||||
byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
|
||||
return System.Convert.ToBase64String(plainTextBytes);
|
||||
}
|
||||
|
||||
public static string Base64Decode(string base64EncodedData)
|
||||
{
|
||||
byte[] base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
|
||||
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
|
||||
}
|
||||
|
||||
public void Write(string input)
|
||||
{
|
||||
input = input.Replace("\0", string.Empty);
|
||||
|
||||
if (input.TrimStart(' ').StartsWith('#'))
|
||||
{
|
||||
input = AddColorInformation(input, input, ConsoleColor.Gray, SearchMode.Exact);
|
||||
}
|
||||
else
|
||||
{
|
||||
MatchCollection matches = StringRegex().Matches(input);
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
input = AddColorInformation(input, matches[i].Value, ConsoleColor.DarkYellow, SearchMode.Contains);
|
||||
}
|
||||
|
||||
matches = VariableRegex().Matches(input);
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
input = AddColorInformation(input, matches[i].Value, ConsoleColor.Cyan, SearchMode.Contains);
|
||||
}
|
||||
|
||||
for (int i = 0; i < replacementValues.Length; i++)
|
||||
{
|
||||
input = AddColorInformation(input, replacementValues[i], ConsoleColor.Blue, SearchMode.Contains);
|
||||
}
|
||||
|
||||
foreach (StatementInformation statement in statementInformation)
|
||||
{
|
||||
if (statement.IgnoreSyntaxHighlighting)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string name = statement.SpaceAround switch
|
||||
{
|
||||
SpaceAround.StartEnd => $" {statement.Name.Trim()} ",
|
||||
SpaceAround.Start => $" {statement.Name.Trim()}",
|
||||
SpaceAround.End => $"{statement.Name.Trim()} ",
|
||||
_ => statement.Name.Trim()
|
||||
};
|
||||
input = input.TrimEnd(' ');
|
||||
string inputTrim = input.Trim(' ');
|
||||
if (statement.SearchMode == SearchMode.StartOfLine && inputTrim.StartsWith(name))
|
||||
{
|
||||
if (statement.Separator is not null && input.Contains(statement.Separator))
|
||||
{
|
||||
input = AddColorInformation(input, statement.Separator, statement.Color, SearchMode.StartOfLine);
|
||||
}
|
||||
else if (statement.Separator is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
input = AddColorInformation(input, name, statement.Color, statement.SearchMode);
|
||||
}
|
||||
else if (statement.SearchMode == SearchMode.Contains && input.Contains(name))
|
||||
{
|
||||
if (statement.Separator is not null && input.Contains(statement.Separator))
|
||||
{
|
||||
input = AddColorInformation(input, statement.Separator, statement.Color, SearchMode.StartOfLine);
|
||||
}
|
||||
else if (statement.Separator is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
input = AddColorInformation(input, input.Substring(input.IndexOf(name), name.Length), statement.Color, statement.SearchMode);
|
||||
}
|
||||
else if (statement.SearchMode == SearchMode.EndOfLine && input.EndsWith(name))
|
||||
{
|
||||
if (statement.Separator is not null && input.Contains(statement.Separator))
|
||||
{
|
||||
input = AddColorInformation(input, statement.Separator, statement.Color, SearchMode.StartOfLine);
|
||||
}
|
||||
else if (statement.Separator is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
input = AddColorInformation(input, input[^name.Length..], statement.Color, statement.SearchMode);
|
||||
}
|
||||
else if (statement.SearchMode == SearchMode.Exact && inputTrim.Equals(name))
|
||||
{
|
||||
if (statement.Separator is not null && input.Contains(statement.Separator))
|
||||
{
|
||||
input = AddColorInformation(input, statement.Separator, statement.Color, SearchMode.StartOfLine);
|
||||
}
|
||||
else if (statement.Separator is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
input = AddColorInformation(input, input, statement.Color, statement.SearchMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string part in input.Split("\0"))
|
||||
{
|
||||
ConsoleColor consoleColor;
|
||||
string messagePart = part;
|
||||
|
||||
string stringColor = StringColorRegex().Match(messagePart).Value;
|
||||
bool succ = int.TryParse(stringColor, out int colorIndex);
|
||||
if (succ && colorIndex >= 0 && colorIndex < 16)
|
||||
{
|
||||
messagePart = Base64Decode(messagePart.Replace("\x01" + stringColor + "\x01", string.Empty));
|
||||
consoleColor = (ConsoleColor)colorIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
consoleColor = ConsoleColor.White;
|
||||
}
|
||||
|
||||
if (consoleColor == Console.BackgroundColor)
|
||||
{
|
||||
consoleColor = ConsoleColor.White;
|
||||
}
|
||||
Console.ForegroundColor = consoleColor;
|
||||
Console.Write(messagePart);
|
||||
}
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
private static string AddColorInformation(string originalString, string value, ConsoleColor color, SearchMode searchMode)
|
||||
{
|
||||
int spacesAtEnd = value.WhiteSpaceAtEnd();
|
||||
|
||||
string base64Value = Base64Encode(value.TrimEnd());
|
||||
|
||||
string result = searchMode switch
|
||||
{
|
||||
SearchMode.StartOfLine => originalString.ReplaceFirstOccurrence(value, $"\0\x01{(int)color}\x01{base64Value}\0" + new string(' ', spacesAtEnd)),
|
||||
SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\x01{(int)color}\x01{base64Value}\0" + new string(' ', spacesAtEnd)),
|
||||
_ => originalString.Replace(value, $"\0\x01{(int)color}\x01{base64Value}\0" + new string(' ', spacesAtEnd))
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
// This regex matches variables in the format ${variableName}, where variableName consists of alphanumeric characters.
|
||||
[GeneratedRegex("\\$\\{[a-zA-Z0-9]+\\}")]
|
||||
private static partial Regex VariableRegex();
|
||||
|
||||
// This regex matches string literals, taking into account escaped quotes and backslashes.
|
||||
[GeneratedRegex(@"(?<!\\)(?:\\\\{2})*""(?:\\.|[^""\\])*""")]
|
||||
private static partial Regex StringRegex();
|
||||
|
||||
// This regex matches the color information embedded in the string, which is in the format \x01{colorIndex}\x01{base64EncodedValue}.
|
||||
[GeneratedRegex(@"(?<=(\x01))(.*)(?=\x01)")]
|
||||
private static partial Regex StringColorRegex();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<PublishAot>True</PublishAot>
|
||||
<AssemblyName>yesntcode</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\YesNt.Interpreter\YesNt.Interpreter.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user