diff --git a/.gitignore b/.gitignore
index c66d6df..0db9923 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ obj/
riderModule.iml
/_ReSharper.Caches/
NeonShell.sln.DotSettings.user
-publish
\ No newline at end of file
+publish
+global.json
\ No newline at end of file
diff --git a/Commands/CdCommand.cs b/Commands/CdCommand.cs
index 6769ff7..46f567a 100644
--- a/Commands/CdCommand.cs
+++ b/Commands/CdCommand.cs
@@ -30,4 +30,4 @@ public void Execute(ShellContext context, string[] args)
AnsiConsole.MarkupLine($"[[[red]-[/]]] - No such directory: {fullPath}");
}
}
-}
\ No newline at end of file
+}
diff --git a/Program.cs b/Program.cs
index dfd0017..fa9c71a 100644
--- a/Program.cs
+++ b/Program.cs
@@ -2,9 +2,8 @@
using Spectre.Console;
using NShell.Shell;
using NShell.Shell.Commands;
-using NShell.Shell.History;
-using NShell.Shell.Keyboard;
using NShell.Shell.Plugins;
+using NShell.Shell.Readline;
using static NShell.Animation.GlitchOutput;
public class Program
@@ -37,7 +36,6 @@ public static async Task Main(string[] args)
AnsiConsole.Markup("[bold cyan][[*]] - Booting NShell...[/]\n");
ShellContext context = new();
- HistoryManager history = new();
PluginLoader plugins = new();
AnsiConsole.Markup("[bold cyan][[*]] - Loading command(s)...[/]\n");
CommandParser parser = new();
@@ -45,13 +43,33 @@ public static async Task Main(string[] args)
plugins.LoadPlugins();
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
- history.Save();
+ ReadLine.History.Save();
};
await GlitchedPrint("[+] - System Online", TimeSpan.FromMilliseconds(20));
- Console.WriteLine();
+ string inputBuffer;
- KeyboardHandler.Handler(history, context, parser);
-
+ while (true)
+ {
+ Environment.SetEnvironmentVariable("LS_COLORS", context.GetLsColors());
+ context.SetTheme(context.CurrentTheme);
+ AnsiConsole.Markup(context.GetPrompt());
+ ReadLine.History.ResetIndex();
+ ReadLine.Handler.UpdateInitCursorPos(Console.CursorLeft);
+
+ inputBuffer = ReadLine.GetText();
+
+ if (string.IsNullOrWhiteSpace(inputBuffer)) continue;
+ ReadLine.History.Add(inputBuffer);
+
+ try
+ {
+ parser.TryExecute(inputBuffer, context);
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.MarkupLine($"[[[red]-[/]]] - Shell crash: [yellow]{ex.Message}[/]");
+ }
+ }
}
}
diff --git a/Shell/Readline/KeyAction.cs b/Shell/Readline/KeyAction.cs
new file mode 100644
index 0000000..ba5427c
--- /dev/null
+++ b/Shell/Readline/KeyAction.cs
@@ -0,0 +1,126 @@
+using System.Diagnostics;
+
+namespace NShell.Shell.Readline;
+
+///
+/// KeyHandler is a class that handle all about key input.
+/// define different key handling methods
+///
+public partial class KeyHandler
+{
+ public void HandleNormalChar()
+ {
+ _inputBuffer.Insert(_currentCursorPos4Input, _currentKey.KeyChar);
+ _inputLength++;
+ Console.Write((string?)_inputBuffer.ToString(_currentCursorPos4Input, _inputLength - _currentCursorPos4Input));
+ _currentCursorPos4Input++;
+ _currentCursorPos4Console++;
+ Console.SetCursorPosition(_currentCursorPos4Console, Console.CursorTop);
+ }
+
+ public void HandleBackspaceChar()
+ {
+ if (IsStartOfLine())
+ return;
+ _inputBuffer = _inputBuffer.Remove(_currentCursorPos4Input - 1, 1);
+ _inputLength--;
+ _currentCursorPos4Input--;
+ _currentCursorPos4Console--;
+ Console.SetCursorPosition(_currentCursorPos4Console, Console.CursorTop);
+ Console.Write(_inputBuffer.ToString(_currentCursorPos4Input, _inputLength - _currentCursorPos4Input) + " ");
+ Console.SetCursorPosition(_currentCursorPos4Console, Console.CursorTop);
+ }
+
+ public void HandleDeleteChar()
+ {
+ if (IsEndOfLine())
+ return;
+ _inputBuffer.Remove(_currentCursorPos4Input, 1);
+ _inputLength--;
+ Console.Write(_inputBuffer.ToString(_currentCursorPos4Input, _inputLength - _currentCursorPos4Input) + " ");
+ Console.SetCursorPosition(_currentCursorPos4Console, Console.CursorTop);
+ }
+
+ public void HandleTab()
+ {
+ // TODO: Implement tab completion
+ }
+
+ public void PreviousHistory()
+ {
+ var prev = _history.GetPrevious();
+ if (prev != null)
+ {
+ Console.Write(new string('\b', _inputLength) + new string(' ', _inputLength) + new string('\b', _inputLength));
+ _inputBuffer.Clear();
+ _inputLength = prev.Length;
+ _inputBuffer.Append(prev);
+ Console.Write((object?)_inputBuffer);
+ _currentCursorPos4Input = _inputLength;
+ _currentCursorPos4Console = _initCursorPos4Console + _inputLength;
+ Console.SetCursorPosition(_currentCursorPos4Console, Console.CursorTop);
+ }
+ }
+
+ public void NextHistory()
+ {
+ var next = _history.GetNext();
+ if (next != null)
+ {
+ Console.Write(new string('\b', _inputLength) + new string(' ', _inputLength) + new string('\b', _inputLength));
+ _inputBuffer.Clear();
+ _inputLength = next.Length;
+ _inputBuffer.Append(next);
+ Console.Write((object?)_inputBuffer);
+ _currentCursorPos4Input = _inputLength;
+ _currentCursorPos4Console = _initCursorPos4Console + _inputLength;
+ Console.SetCursorPosition(_currentCursorPos4Console, Console.CursorTop);
+ }
+ }
+
+ private void MoveCursorToEnd()
+ {
+ _currentCursorPos4Input = _inputLength;
+ _currentCursorPos4Console = _initCursorPos4Console + _inputLength;
+ Console.SetCursorPosition(_currentCursorPos4Console,Console.CursorTop);
+ }
+
+ private void MoveCursorToStart()
+ {
+ _currentCursorPos4Input = 0;
+ _currentCursorPos4Console = _initCursorPos4Console;
+ Console.SetCursorPosition(_currentCursorPos4Console, Console.CursorTop);
+ }
+
+ private void MoveCursorWordLeft()
+ {
+ Debug.Write("MoveCursorWordLeft");
+ }
+
+ private void MoveCursorWordRight()
+ {
+ Debug.Write("MoveCursorWordRight");
+ }
+
+ private void MoveCursorLeft()
+ {
+ if (_currentCursorPos4Input == 0)
+ return;
+ _currentCursorPos4Console--;
+ _currentCursorPos4Input--;
+ Console.SetCursorPosition(_currentCursorPos4Console,Console.CursorTop);
+ }
+
+ private void MoveCursorRight()
+ {
+ if (_currentCursorPos4Input == _inputLength)
+ return;
+ _currentCursorPos4Console++;
+ _currentCursorPos4Input++;
+ Console.SetCursorPosition(_currentCursorPos4Console,Console.CursorTop);
+ }
+
+ private bool IsEndOfLine() => _currentCursorPos4Input == _inputLength;
+
+ private bool IsStartOfLine() => _currentCursorPos4Input == 0;
+}
diff --git a/Shell/Readline/KeyHandler.cs b/Shell/Readline/KeyHandler.cs
new file mode 100644
index 0000000..3a15b02
--- /dev/null
+++ b/Shell/Readline/KeyHandler.cs
@@ -0,0 +1,115 @@
+using System.Text;
+using NShell.Shell.History;
+using Spectre.Console;
+
+namespace NShell.Shell.Readline;
+
+///
+/// KeyHandler deals with keybindings and inputs.
+/// defining key behaviors, managing input buffers, and cursor position
+///
+public partial class KeyHandler
+{
+ // a dictionary that stores key bindings
+ private Dictionary _keyBindings;
+ // cursor position
+ private int _currentCursorPos4Console;
+ private int _currentCursorPos4Input;
+ private int _initCursorPos4Console;
+ // length of the string entered
+ private int _inputLength;
+ private ConsoleKeyInfo _currentKey;
+ private StringBuilder _inputBuffer;
+ private readonly HistoryManager _history;
+ public string InputBuffer { get => _inputBuffer.ToString(); }
+
+ public void LoadKeyBindings()
+ {
+ foreach (var binding in _keyBindings)
+ {
+ AnsiConsole.MarkupLine($"[green]+[/] - Loaded key binding: [yellow]{binding.Key}[/] -> [blue]{binding.Value}[/]");
+ }
+ }
+
+ // it can add custom key bindings to facilitate subsequent expansion
+ public void AddCustomKeyBinding(string key, Action handler)
+ {
+ if (_keyBindings.ContainsKey(key))
+ {
+ _keyBindings.Remove(key);
+ }
+ _keyBindings.Add(key, handler);
+ }
+
+ // it can remove custom key bindings
+ public void RemoveCustomKeyBinding(string key)
+ {
+ if (_keyBindings.ContainsKey(key))
+ {
+ _keyBindings.Remove(key);
+ }
+ }
+
+ public KeyHandler(HistoryManager history)
+ {
+ _history = history;
+ _inputBuffer = new();
+ _keyBindings = new();
+ _initCursorPos4Console = Console.CursorLeft;
+ _currentCursorPos4Console = _initCursorPos4Console;
+ _currentCursorPos4Input = 0;
+ _inputLength = 0;
+
+ _keyBindings["Backspace"] = HandleBackspaceChar;
+ _keyBindings["Delete"] = HandleDeleteChar;
+ _keyBindings["Home"] = MoveCursorToStart;
+ _keyBindings["ControlA"] = MoveCursorToStart;
+ _keyBindings["End"] = MoveCursorToEnd;
+ _keyBindings["ControlE"] = MoveCursorToEnd;
+ _keyBindings["LeftArrow"] = MoveCursorLeft;
+ _keyBindings["RightArrow"] = MoveCursorRight;
+ _keyBindings["UpArrow"] = PreviousHistory;
+ _keyBindings["DownArrow"] = NextHistory;
+
+ _keyBindings["Tab"] = HandleTab;
+ _keyBindings["ControlLeftArrow"] = MoveCursorWordLeft;
+ _keyBindings["ControlRightArrow"] = MoveCursorWordRight;
+ }
+
+ private string BuildKeyInput()
+ {
+ string result = "";
+ if (_currentKey.Modifiers.HasFlag(ConsoleModifiers.Control)) result += "Control";
+ if (_currentKey.Modifiers.HasFlag(ConsoleModifiers.Alt)) result += "Alt";
+ if (_currentKey.Modifiers.HasFlag(ConsoleModifiers.Shift)) result += "Shift";
+ result += _currentKey.Key.ToString();
+ return result;
+ }
+
+ // TODO: need to improve when input complex key (like Control+A, Alt+Shift+A)
+ public void HandleInput(ConsoleKeyInfo keyInfo)
+ {
+ _currentKey = keyInfo;
+ _keyBindings.TryGetValue(BuildKeyInput(),out var handleAction);
+ handleAction??=HandleNormalChar;
+ handleAction.Invoke();
+ }
+
+ // clear the input buffer and reset the cursor position
+ public void ResetInput()
+ {
+ _inputBuffer.Clear();
+ _currentCursorPos4Console = _initCursorPos4Console;
+ _currentCursorPos4Input = 0;
+ _inputLength = 0;
+ }
+
+ // update the initial cursor position when the prompt is being updated
+ public void UpdateInitCursorPos(int initCursorPos)
+ {
+ _initCursorPos4Console = initCursorPos;
+ _currentCursorPos4Console = _initCursorPos4Console;
+ _currentCursorPos4Input = 0;
+ _inputLength = 0;
+ }
+}
diff --git a/Shell/Readline/ReadLine.cs b/Shell/Readline/ReadLine.cs
new file mode 100644
index 0000000..8ef84ff
--- /dev/null
+++ b/Shell/Readline/ReadLine.cs
@@ -0,0 +1,33 @@
+using NShell.Shell.History;
+
+namespace NShell.Shell.Readline;
+
+///
+/// ReadLine is a simple readline library
+/// base on the tonerdo/readline project
+///
+public class ReadLine
+{
+ public static KeyHandler Handler { get; }
+ public static HistoryManager History { get; }
+
+ static ReadLine()
+ {
+ History = new HistoryManager();
+ Handler = new KeyHandler(History);
+ }
+
+ public static string GetText()
+ {
+ ConsoleKeyInfo key = Console.ReadKey(intercept: true);
+ while (key.Key != ConsoleKey.Enter)
+ {
+ Handler.HandleInput(key);
+ key = Console.ReadKey(intercept: true);
+ }
+ string input = Handler.InputBuffer;
+ Handler.ResetInput();
+ Console.WriteLine();
+ return input;
+ }
+}
diff --git a/Shell/ShellContext.cs b/Shell/ShellContext.cs
index 1eaabe1..6392fec 100644
--- a/Shell/ShellContext.cs
+++ b/Shell/ShellContext.cs
@@ -122,7 +122,5 @@ public bool SetTheme(string themeName)
}
return true;
}
-
-
}
}