using Godot; using System; using Godot.Collections; [Tool] public partial class TerminalTextEdit : TextEdit { private Dictionary _psProcess = new Dictionary(); private FileAccess _psOutputPipe; private FileAccess _psInputPipe; // Track historical boundaries so users can't delete past output private int _protectedLine = 0; private int _protectedColumn = 0; private bool _isUpdatingText = false; public override void _Ready() { // Initial setup Text = "ReEdit Terminal - 0.1.3\nWindows PowerShell\nCopyright (C) Microsoft Corporation. All rights reserved.\n\nTry the new cross-platform PowerShell https://aka.ms/pscore6\n\n"; _protectedLine = GetLineCount() - 1; _protectedColumn = GetLine(_protectedLine).Length; // Force caret to the active prompt area SetCaretLine(_protectedLine); SetCaretColumn(_protectedColumn); TextChanged += OnTextChanged; StartPowerShell(); } private void StartPowerShell() { var args = new string[] { "-NoExit", "-NoProfile", "-Command", "$OutputEncoding = [System.Text.Encoding]::UTF8" }; _psProcess = OS.ExecuteWithPipe("powershell.exe", args); if (_psProcess.Count == 0) { AppendSystemText("\n[Error: Failed to initialize PowerShell process.]\n"); return; } _psOutputPipe = (FileAccess)_psProcess["stdio"]; _psInputPipe = (FileAccess)_psProcess["stdio"]; } public override void _GuiInput(InputEvent @event) { if (@event is InputEventKey keyEvent && keyEvent.Pressed) { if (keyEvent.Keycode == Key.Backspace) { if (GetCaretLine() < _protectedLine || (GetCaretLine() == _protectedLine && GetCaretColumn() <= _protectedColumn)) { AcceptEvent(); // Kill the input event before it acts return; } } if (keyEvent.Keycode == Key.Enter || keyEvent.Keycode == Key.KpEnter) { string activeLine = GetLine(GetCaretLine()); string command = activeLine.Substring(_protectedColumn).Trim(); if (!string.IsNullOrWhiteSpace(command) && _psInputPipe != null) { _psInputPipe.StoreLine(command); } _isUpdatingText = true; InsertTextAtCaret("\n"); _protectedLine = GetLineCount() - 1; _protectedColumn = 0; _isUpdatingText = false; AcceptEvent(); } } } private void OnTextChanged() { if (_isUpdatingText) return; if (GetCaretLine() < _protectedLine || (GetCaretLine() == _protectedLine && GetCaretColumn() < _protectedColumn)) { _isUpdatingText = true; SetCaretLine(_protectedLine); SetCaretColumn(GetLine(_protectedLine).Length); _isUpdatingText = false; } } public override void _Process(double delta) { if (_psOutputPipe != null && _psOutputPipe.GetLength() > 0) { string newText = _psOutputPipe.GetAsText(); if (!string.IsNullOrEmpty(newText)) { AppendSystemText(newText); } } } private void AppendSystemText(string incoming) { _isUpdatingText = true; int oldCaretLine = GetCaretLine(); int oldCaretCol = GetCaretColumn(); SetCaretLine(GetLineCount() - 1); SetCaretColumn(GetLine(GetLineCount() - 1).Length); InsertTextAtCaret(incoming); _protectedLine = GetLineCount() - 1; _protectedColumn = GetLine(_protectedLine).Length; SetCaretLine(_protectedLine); SetCaretColumn(_protectedColumn); _isUpdatingText = false; } public override void _ExitTree() { if (_psProcess.Count > 0 && _psProcess.ContainsKey("pid")) { int pid = (int)_psProcess["pid"]; OS.Kill(pid); } } }