diff --git a/Editor.cs b/Editor.cs index 8cd4035..1675baa 100644 --- a/Editor.cs +++ b/Editor.cs @@ -108,45 +108,56 @@ namespace CMLeonOS { Console.Clear(); - // 显示标题栏 - Console.WriteLine($"CMLeonOS Editor - {fileName}"); - Console.WriteLine("--------------------------------"); - Console.WriteLine("Commands: Esc = Exit"); - Console.WriteLine(); + Console.BackgroundColor = ConsoleColor.Gray; + Console.ForegroundColor = ConsoleColor.Black; - // 显示文件内容(限制显示行数,避免超出控制台高度) - int maxVisibleLines = 15; // 假设控制台高度为25行,留10行给其他内容 + int consoleWidth = 80; + + string titleLine = $"CMLeonOS Editor - {fileName}".PadRight(consoleWidth); + Console.WriteLine(titleLine); + + string separatorLine = new string('-', consoleWidth); + Console.WriteLine(separatorLine); + + string commandLine = "Commands: Esc = Exit".PadRight(consoleWidth); + Console.WriteLine(commandLine); + + Console.ResetColor(); + + int maxVisibleLines = 15; int startLine = Math.Max(0, currentLine - maxVisibleLines / 2); int endLine = Math.Min(lines.Count, startLine + maxVisibleLines); for (int i = startLine; i < endLine; i++) { string line = lines[i]; - Console.WriteLine($"{i + 1}: {line}"); + Console.WriteLine(line); } - // 显示光标位置 - Console.WriteLine(); - Console.WriteLine($"Cursor: Line {currentLine + 1}, Column {currentColumn + 1}"); - Console.WriteLine($"Lines: {lines.Count}"); + Console.BackgroundColor = ConsoleColor.Gray; + Console.ForegroundColor = ConsoleColor.Black; - // 计算光标位置,确保不超出控制台高度 - int consoleHeight = 25; // 假设控制台高度为25行 - int titleLines = 4; // 标题栏占用的行数 + string cursorLine = $"Cursor: Line {currentLine + 1}, Column {currentColumn + 1}".PadRight(consoleWidth); + Console.WriteLine(cursorLine); + + string linesLine = $"Lines: {lines.Count}".PadRight(consoleWidth); + Console.WriteLine(linesLine); + + Console.ResetColor(); + + int consoleHeight = 25; + int titleLines = 3; int visibleLines = endLine - startLine; int cursorY = titleLines + (currentLine - startLine); - // 确保光标Y位置在有效范围内 - cursorY = Math.Max(titleLines, Math.Min(cursorY, consoleHeight - 5)); // 留5行给状态信息 + cursorY = Math.Max(titleLines, Math.Min(cursorY, consoleHeight - 5)); - // 设置光标位置 try { - Console.SetCursorPosition(currentColumn + 3, cursorY); + Console.SetCursorPosition(currentColumn, cursorY); } catch { - // 如果设置光标位置失败,忽略错误 } } @@ -245,18 +256,14 @@ namespace CMLeonOS // 显示保存确认弹窗 private void ShowSaveConfirmation() { - // 简化版本,避免使用可能在Cosmos中不支持的功能 Console.Clear(); - // 显示灰色背景的确认弹窗 - Console.BackgroundColor = ConsoleColor.DarkGray; - Console.ForegroundColor = ConsoleColor.White; + Console.BackgroundColor = ConsoleColor.Gray; + Console.ForegroundColor = ConsoleColor.Black; - // 获取控制台窗口大小 - int consoleWidth = 80; // 假设控制台宽度为80 - int consoleHeight = 25; // 假设控制台高度为25 + int consoleWidth = 80; + int consoleHeight = 25; - // 弹窗内容 string[] popupLines = { "+----------------------------+", "| Save File? |", @@ -268,22 +275,18 @@ namespace CMLeonOS "+----------------------------+" }; - // 计算弹窗宽度和高度 - int popupWidth = 30; // 弹窗宽度 - int popupHeight = popupLines.Length; // 弹窗高度 + int popupWidth = 30; + int popupHeight = popupLines.Length; - // 计算弹窗在屏幕中的位置(居中) int popupX = (consoleWidth - popupWidth) / 2; int popupY = (consoleHeight - popupHeight) / 2; - // 显示弹窗 for (int i = 0; i < popupLines.Length; i++) { Console.SetCursorPosition(popupX, popupY + i); Console.WriteLine(popupLines[i]); } - // 等待用户输入 while (true) { ConsoleKeyInfo keyInfo; @@ -293,7 +296,6 @@ namespace CMLeonOS } catch { - // 如果ReadKey失败,直接退出 Console.ResetColor(); Console.Clear(); isRunning = false; @@ -302,7 +304,6 @@ namespace CMLeonOS if (keyInfo.Key == ConsoleKey.Y) { - // 保存文件 try { SaveFile(); @@ -318,7 +319,6 @@ namespace CMLeonOS } else if (keyInfo.Key == ConsoleKey.N) { - // 不保存文件 Console.ResetColor(); Console.Clear(); isRunning = false; diff --git a/Nano.cs b/Nano.cs new file mode 100644 index 0000000..362e4e0 --- /dev/null +++ b/Nano.cs @@ -0,0 +1,700 @@ +using System; +using System.Collections.Generic; +using UniLua; + +namespace CMLeonOS +{ + public class Nano + { + private string path; + private bool quit = false; + private bool modified = false; + private string clipboard = string.Empty; + private List lines = new List(); + private int currentLine = 0; + private int linePos = 0; + private int scrollX = 0; + private int scrollY = 0; + private int? updatedLinesStart; + private int? updatedLinesEnd; + private const int TITLEBAR_HEIGHT = 1; + private const int SHORTCUT_BAR_HEIGHT = 1; + private FileSystem fileSystem; + private UserSystem userSystem; + private Shell shell; + + private readonly (string, string)[] SHORTCUTS = new (string, string)[] + { + ("Ctrl+X", "Quit"), + ("Ctrl+S", "Save"), + ("Ctrl+I", "Info"), + ("Ctrl+K", "Cut Line"), + ("Ctrl+V", "Paste"), + ("Ctrl+R", "Run") + }; + + string? pendingNotification; + + public Nano(string value, bool isPath = true, FileSystem fs = null, UserSystem us = null, Shell sh = null) + { + this.fileSystem = fs; + this.userSystem = us; + this.shell = sh; + + if (isPath) + { + this.path = value; + + if (value != null && fileSystem != null) + { + string fullPath = fileSystem.GetFullPath(value); + + if (System.IO.File.Exists(fullPath)) + { + string text = fileSystem.ReadFile(fullPath); + text = text.Replace("\r\n", "\n"); + lines.AddRange(text.Split('\n')); + } + else + { + lines.Add(string.Empty); + } + } + else + { + lines.Add(string.Empty); + } + } + else + { + string text = value; + text = text.Replace("\r\n", "\n"); + lines.AddRange(text.Split('\n')); + } + + updatedLinesStart = 0; + updatedLinesEnd = lines.Count - 1; + } + + private void Render() + { + if (updatedLinesStart != null) + { + Console.BackgroundColor = ConsoleColor.Black; + Console.ForegroundColor = ConsoleColor.White; + + int consoleWidth = 80; + int consoleHeight = 25; + + for (int i = (int)updatedLinesStart; i <= updatedLinesEnd; i++) + { + int y = i - scrollY + TITLEBAR_HEIGHT; + if (y < TITLEBAR_HEIGHT || y >= consoleHeight - SHORTCUT_BAR_HEIGHT) continue; + + Console.SetCursorPosition(0, y); + + if (i >= lines.Count || scrollX >= lines[i].Length) + { + Console.Write(new string(' ', consoleWidth)); + } + else + { + string line = lines[i].Substring(scrollX, Math.Min(consoleWidth, lines[i].Length - scrollX)); + Console.Write(line + new string(' ', Math.Max(0, consoleWidth - line.Length))); + } + } + + updatedLinesStart = null; + updatedLinesEnd = null; + } + + Console.SetCursorPosition(linePos - scrollX, currentLine + TITLEBAR_HEIGHT - scrollY); + } + + // Insert a new line at the cursor. + private void InsertLine() + { + string line = lines[currentLine]; + if (linePos == line.Length) + { + lines.Insert(currentLine + 1, string.Empty); + } + else + { + lines.Insert(currentLine + 1, line.Substring(linePos, line.Length - linePos)); + lines[currentLine] = line.Remove(linePos, line.Length - linePos); + } + updatedLinesStart = currentLine; + updatedLinesEnd = lines.Count - 1; + + currentLine += 1; + linePos = 0; + + modified = true; + } + + // Insert text at the cursor. + private void Insert(string text) + { + for (int i = 0; i < text.Length; i++) + { + lines[currentLine] = lines[currentLine].Insert(linePos, text[i].ToString()); + linePos++; + + updatedLinesStart = currentLine; + updatedLinesEnd = currentLine; + } + modified = true; + } + + // Backspace at the cursor. + private void Backspace() + { + if (linePos == 0) + { + if (currentLine == 0) return; + linePos = lines[currentLine - 1].Length; + lines[currentLine - 1] += lines[currentLine]; + + updatedLinesStart = currentLine - 1; + updatedLinesEnd = lines.Count - 1; + + lines.RemoveAt(currentLine); + currentLine -= 1; + } + else + { + lines[currentLine] = lines[currentLine].Remove(linePos - 1, 1); + linePos--; + + updatedLinesStart = currentLine; + updatedLinesEnd = currentLine; + } + modified = true; + } + + // Move the cursor left. + private void MoveLeft() + { + if (linePos == 0) + { + if (currentLine == 0) return; + currentLine--; + linePos = lines[currentLine].Length; + } + else + { + linePos--; + } + } + + // Move the cursor right. + private void MoveRight() + { + if (linePos == lines[currentLine].Length) + { + if (currentLine + 1 == lines.Count) return; + currentLine++; + linePos = 0; + } + else + { + linePos++; + } + } + + // Move the cursor up. + private void MoveUp() + { + if (currentLine == 0) return; + currentLine--; + linePos = Math.Min(linePos, lines[currentLine].Length); + } + + // Move the cursor down. + private void MoveDown() + { + if (currentLine + 1 == lines.Count) return; + currentLine++; + linePos = Math.Min(linePos, lines[currentLine].Length); + } + + // Jump back to the previous word. + private void JumpToPreviousWord() + { + if (linePos == 0) + { + if (currentLine == 0) return; + currentLine -= 1; + linePos = lines[currentLine].Length; + JumpToPreviousWord(); + } + else + { + int res = lines[currentLine].Substring(0, linePos - 1).LastIndexOf(' '); + linePos = res == -1 ? 0 : res + 1; + } + } + + // Jump forward to the next word. + private void JumpToNextWord() + { + int res = lines[currentLine].IndexOf(' ', linePos + 1); + if (res == -1) + { + if (currentLine == lines.Count - 1) + { + linePos = lines[currentLine].Length; + return; + } + + linePos = 0; + currentLine++; + while (lines[currentLine] == string.Empty) + { + if (currentLine == lines.Count - 1) break; + currentLine++; + } + } + else + { + linePos = res + 1; + } + } + + private void ShowNotification(string text) + { + int consoleWidth = 80; + int consoleHeight = 25; + Console.BackgroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Black; + Console.SetCursorPosition((consoleWidth - text.Length) / 2, consoleHeight - SHORTCUT_BAR_HEIGHT - 1); + Console.Write($" {text} "); + } + + private void RenderPrompt(string question, (string, string)[] shortcuts) + { + RenderShortcuts(shortcuts); + + int consoleHeight = 25; + int y = consoleHeight - SHORTCUT_BAR_HEIGHT - 1; + Console.BackgroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Black; + + int consoleWidth = 80; + Console.SetCursorPosition(0, y); + Console.Write(new string(' ', consoleWidth)); + + Console.SetCursorPosition(1, y); + Console.Write(question); + + Console.SetCursorPosition(question.Length + 1, y); + } + + // Get the entire document as a string. + private string GetAllText() + { + string text = string.Empty; + foreach (string line in lines) + { + text += line + "\n"; + } + // Strip the trailing newline. + text = text.Remove(text.Length - 1); + + return text; + } + + private void Save(bool showFeedback) + { + if (path == null) + { + RenderPrompt("Path to save to: ", new (string, string)[] { ("Enter", "Save"), ("Esc", "Cancel") }); + + string input = ""; + while (true) + { + ConsoleKeyInfo key = Console.ReadKey(true); + if (key.Key == ConsoleKey.Enter) + { + break; + } + else if (key.Key == ConsoleKey.Escape) + { + input = null; + break; + } + else if (key.Key == ConsoleKey.Backspace && input.Length > 0) + { + input = input.Substring(0, input.Length - 1); + Console.SetCursorPosition(1 + input.Length, Console.WindowHeight - SHORTCUT_BAR_HEIGHT - 1); + Console.Write(" "); + Console.SetCursorPosition(1 + input.Length, Console.WindowHeight - SHORTCUT_BAR_HEIGHT - 1); + } + else if (key.KeyChar >= 32 && key.KeyChar <= 126) + { + input += key.KeyChar; + Console.Write(key.KeyChar); + } + } + + path = input; + + Console.BackgroundColor = ConsoleColor.Black; + Console.Clear(); + RenderUI(); + updatedLinesStart = 0; + updatedLinesEnd = lines.Count - 1; + Render(); + + if (path == null) + { + return; + } + } + else + { + if (!modified) return; + } + + modified = false; + string text = GetAllText(); + try + { + fileSystem.CreateFile(path, text); + + if (showFeedback) + { + RenderUI(); + ShowNotification($"Saved to {path}"); + } + } + catch + { + ShowNotification("Failed to save"); + return; + } + } + + // Quit, and if the file is modified, prompt to save it. + private void Quit() + { + quit = true; + if (modified) + { + RenderPrompt("Save your changes?", new (string, string)[] { ("Y", "Yes"), ("N", "No"), ("Esc", "Cancel") }); + bool choiceMade = false; + while (!choiceMade) + { + ConsoleKeyInfo key = Console.ReadKey(true); + switch (key.Key) + { + case ConsoleKey.Y: + Save(false); + choiceMade = true; + break; + case ConsoleKey.N: + choiceMade = true; + break; + case ConsoleKey.Escape: + choiceMade = true; + quit = false; + + // Hide the prompt. + Console.BackgroundColor = ConsoleColor.Black; + Console.Clear(); + RenderUI(); + updatedLinesStart = 0; + updatedLinesEnd = lines.Count - 1; + Render(); + break; + } + } + } + } + + // Show information about the document. + private void ShowInfo() + { + ShowNotification($"Ln {currentLine + 1}, Col {linePos + 1}"); + } + + // Cut the current line. + private void CutLine() + { + if (lines[currentLine] != string.Empty) + { + clipboard = lines[currentLine]; + if (lines.Count == 1) + { + lines[currentLine] = string.Empty; + linePos = 0; + + updatedLinesStart = 0; + updatedLinesEnd = 0; + } + else + { + lines.RemoveAt(currentLine); + + if (currentLine >= lines.Count) + { + currentLine--; + } + + if (linePos >= lines[currentLine].Length) + { + linePos = lines[currentLine].Length - 1; + } + + updatedLinesStart = currentLine; + updatedLinesEnd = lines.Count; + } + modified = true; + } + else + { + ShowNotification("Nothing was cut"); + } + } + + // Paste from the clipboard. + private void Paste() + { + if (clipboard != string.Empty) + { + Insert(clipboard); + } + else + { + ShowNotification("Nothing to paste"); + } + } + + private void RunLua() + { + Console.BackgroundColor = ConsoleColor.Black; + Console.ForegroundColor = ConsoleColor.White; + Console.Clear(); + Console.SetCursorPosition(0, 0); + + try + { + string source = GetAllText(); + + ILuaState lua = LuaAPI.NewState(); + lua.L_OpenLibs(); + + UniLua.ThreadStatus loadResult = lua.L_LoadString(source); + + if (loadResult == UniLua.ThreadStatus.LUA_OK) + { + UniLua.ThreadStatus callResult = lua.PCall(0, 0, 0); + + if (callResult != UniLua.ThreadStatus.LUA_OK) + { + string errorMsg = lua.ToString(-1); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Script execution error: {errorMsg}"); + } + } + else + { + string errorMsg = lua.ToString(-1); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Script load error: {errorMsg}"); + } + } + catch (Exception e) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Error occurred while running script: {e.Message}"); + } + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(true); + + Console.BackgroundColor = ConsoleColor.Black; + Console.ForegroundColor = ConsoleColor.White; + Console.Clear(); + RenderUI(); + updatedLinesStart = 0; + updatedLinesEnd = lines.Count - 1; + Render(); + } + + private bool HandleInput() + { + ConsoleKeyInfo key = Console.ReadKey(true); + switch (key.Modifiers) + { + case ConsoleModifiers.Control: + switch (key.Key) + { + case ConsoleKey.X: + Quit(); + break; + case ConsoleKey.S: + Save(true); + break; + case ConsoleKey.I: + ShowInfo(); + break; + case ConsoleKey.K: + CutLine(); + break; + case ConsoleKey.V: + Paste(); + break; + case ConsoleKey.R: + RunLua(); + break; + case ConsoleKey.LeftArrow: + JumpToPreviousWord(); + break; + case ConsoleKey.RightArrow: + JumpToNextWord(); + break; + case ConsoleKey.UpArrow: + currentLine = 0; + linePos = 0; + break; + case ConsoleKey.DownArrow: + currentLine = lines.Count - 1; + linePos = lines[currentLine].Length; + break; + } + return false; + } + switch (key.Key) + { + case ConsoleKey.Backspace: + Backspace(); + break; + case ConsoleKey.Enter: + InsertLine(); + break; + case ConsoleKey.LeftArrow: + MoveLeft(); + break; + case ConsoleKey.RightArrow: + MoveRight(); + break; + case ConsoleKey.UpArrow: + MoveUp(); + break; + case ConsoleKey.DownArrow: + MoveDown(); + break; + default: + if (key.KeyChar >= 32 && key.KeyChar <= 126) + { + Insert(key.KeyChar.ToString()); + } + break; + } + return false; + } + + private void UpdateScrolling() + { + bool scrollChanged = false; + int consoleWidth = 80; + int consoleHeight = 25; + + if (currentLine < scrollY) + { + scrollY = currentLine; + scrollChanged = true; + } + else if (currentLine >= scrollY + consoleHeight - TITLEBAR_HEIGHT - SHORTCUT_BAR_HEIGHT) + { + scrollY = currentLine - consoleHeight + TITLEBAR_HEIGHT + SHORTCUT_BAR_HEIGHT + 1; + scrollChanged = true; + } + + if (linePos < scrollX) + { + scrollX = linePos; + scrollChanged = true; + } + else if (linePos > scrollX + consoleWidth - 1) + { + scrollX = linePos - consoleWidth + 1; + scrollChanged = true; + } + + if (scrollChanged) + { + updatedLinesStart = 0; + updatedLinesEnd = lines.Count - 1; + } + } + + private void RenderShortcuts((string, string)[] shortcuts) + { + int consoleWidth = 80; + int y = 24; + + Console.SetCursorPosition(0, y); + Console.BackgroundColor = ConsoleColor.Black; + Console.Write(new string(' ', consoleWidth - 1)); + + Console.SetCursorPosition(0, y); + foreach (var shortcut in shortcuts) + { + Console.BackgroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Black; + Console.Write($"{shortcut.Item1}"); + + Console.BackgroundColor = ConsoleColor.Black; + Console.ForegroundColor = ConsoleColor.White; + Console.Write($" {shortcut.Item2} "); + } + } + + private void RenderUI() + { + int consoleWidth = 80; + + Console.BackgroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Black; + Console.SetCursorPosition(0, 0); + string text = " Nano Editor 1.2"; + Console.WriteLine(text + new string(' ', consoleWidth - text.Length)); + + string displayName = path == null ? "New File" : System.IO.Path.GetFileName(path); + Console.SetCursorPosition((consoleWidth - displayName.Length) / 2, 0); + Console.Write(displayName); + + RenderShortcuts(SHORTCUTS); + } + + public void Start() + { + Console.BackgroundColor = ConsoleColor.Black; + Console.ForegroundColor = ConsoleColor.White; + Console.Clear(); + + RenderUI(); + while (!quit) + { + Render(); + if (pendingNotification != null) + { + ShowNotification(pendingNotification); + pendingNotification = null; + } + + HandleInput(); + UpdateScrolling(); + } + + Console.BackgroundColor = ConsoleColor.Black; + Console.ForegroundColor = ConsoleColor.White; + Console.Clear(); + Console.SetCursorPosition(0, 0); + } + } +} \ No newline at end of file diff --git a/Shell.cs b/Shell.cs index 17739f5..dfd4918 100644 --- a/Shell.cs +++ b/Shell.cs @@ -295,6 +295,9 @@ namespace CMLeonOS case "edit": EditFile(args); break; + case "nano": + NanoFile(args); + break; case "ls": fileSystem.ListFiles(args); break; @@ -742,6 +745,25 @@ namespace CMLeonOS } } + private void NanoFile(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + { + ShowError("Please specify a file name"); + return; + } + + try + { + var nano = new Nano(fileName, true, fileSystem, userSystem, this); + nano.Start(); + } + catch (Exception ex) + { + ShowError($"Error starting nano: {ex.Message}"); + } + } + private void HeadFile(string args) { if (string.IsNullOrEmpty(args))