// Reads a line in _readLineSB when consumeKeys is true, // or _availableKeys when consumeKeys is false. // Returns whether the line was terminated using the Enter key. private bool ReadLineCore(bool consumeKeys) { Debug.Assert(_tmpKeys.Count == 0); Debug.Assert(consumeKeys || _availableKeys.Count == 0); // _availableKeys either contains a line that was already read, // or we need to read a new line from stdin. bool freshKeys = _availableKeys.Count == 0; // Don't carry over chars from previous ReadLine call. _readLineSB.Clear(); Interop.Sys.InitializeConsoleBeforeRead(); try { // Read key-by-key until we've read a line. while (true) { ConsoleKeyInfo keyInfo = freshKeys ? ReadKey() : _availableKeys.Pop(); if (!consumeKeys && keyInfo.Key != ConsoleKey.Backspace) // backspace is the only character not written out in the below if/elses. { _tmpKeys.Push(keyInfo); } // Handle the next key. Since for other functions we may have ended up reading some of the user's // input, we need to be able to handle manually processing that input, and so we do that processing // for all input. As such, we need to special-case a few characters, e.g. recognizing when Enter is // pressed to end a line. We also need to handle Backspace specially, to fix up both our buffer of // characters and the position of the cursor. More advanced processing would be possible, but we // try to keep this very simple, at least for now. if (keyInfo.Key == ConsoleKey.Enter) { if (freshKeys) { Console.WriteLine(); } return(true); } else if (IsEol(keyInfo.KeyChar)) { return(false); } else if (keyInfo.Key == ConsoleKey.Backspace) { bool removed = false; if (consumeKeys) { int len = _readLineSB.Length; if (len > 0) { _readLineSB.Length = len - 1; removed = true; } } else { removed = _tmpKeys.TryPop(out _); } if (removed && freshKeys) { // The ReadLine input may wrap across terminal rows and we need to handle that. // note: ConsolePal will cache the cursor position to avoid making many slow cursor position fetch operations. if (ConsolePal.TryGetCursorPosition(out int left, out int top, reinitializeForRead: true) && left == 0 && top > 0) { if (s_clearToEol == null) { s_clearToEol = ConsolePal.TerminalFormatStrings.Instance.ClrEol ?? string.Empty; } // Move to end of previous line ConsolePal.SetCursorPosition(ConsolePal.WindowWidth - 1, top - 1); // Clear from cursor to end of the line ConsolePal.WriteStdoutAnsiString(s_clearToEol, mayChangeCursorPosition: false); } else { if (s_moveLeftString == null) { string?moveLeft = ConsolePal.TerminalFormatStrings.Instance.CursorLeft; s_moveLeftString = !string.IsNullOrEmpty(moveLeft) ? moveLeft + " " + moveLeft : string.Empty; } Console.Write(s_moveLeftString); } } } else if (keyInfo.Key == ConsoleKey.Tab) { if (consumeKeys) { _readLineSB.Append(keyInfo.KeyChar); } if (freshKeys) { Console.Write(' '); } } else if (keyInfo.Key == ConsoleKey.Clear) { _readLineSB.Clear(); if (freshKeys) { Console.Clear(); } } else if (keyInfo.KeyChar != '\0') { if (consumeKeys) { _readLineSB.Append(keyInfo.KeyChar); } if (freshKeys) { Console.Write(keyInfo.KeyChar); } } }
private string?ReadLine(bool consumeKeys) { Debug.Assert(_tmpKeys.Count == 0); string?readLineStr = null; Interop.Sys.InitializeConsoleBeforeRead(); try { // Read key-by-key until we've read a line. while (true) { // Read the next key. This may come from previously read keys, from previously read but // unprocessed data, or from an actual stdin read. bool previouslyProcessed; ConsoleKeyInfo keyInfo = ReadKey(out previouslyProcessed); if (!consumeKeys && keyInfo.Key != ConsoleKey.Backspace) // backspace is the only character not written out in the below if/elses. { _tmpKeys.Push(keyInfo); } // Handle the next key. Since for other functions we may have ended up reading some of the user's // input, we need to be able to handle manually processing that input, and so we do that processing // for all input. As such, we need to special-case a few characters, e.g. recognizing when Enter is // pressed to end a line. We also need to handle Backspace specially, to fix up both our buffer of // characters and the position of the cursor. More advanced processing would be possible, but we // try to keep this very simple, at least for now. if (keyInfo.Key == ConsoleKey.Enter) { readLineStr = _readLineSB.ToString(); _readLineSB.Clear(); if (!previouslyProcessed) { Console.WriteLine(); } break; } else if (IsEol(keyInfo.KeyChar)) { string line = _readLineSB.ToString(); _readLineSB.Clear(); if (line.Length > 0) { readLineStr = line; } break; } else if (keyInfo.Key == ConsoleKey.Backspace) { int len = _readLineSB.Length; if (len > 0) { _readLineSB.Length = len - 1; if (!previouslyProcessed) { // The ReadLine input may wrap accross terminal rows and we need to handle that. // note: ConsolePal will cache the cursor position to avoid making many slow cursor position fetch operations. if (ConsolePal.TryGetCursorPosition(out int left, out int top, reinitializeForRead: true) && left == 0 && top > 0) { if (s_clearToEol == null) { s_clearToEol = ConsolePal.TerminalFormatStrings.Instance.ClrEol ?? string.Empty; } // Move to end of previous line ConsolePal.SetCursorPosition(ConsolePal.WindowWidth - 1, top - 1); // Clear from cursor to end of the line ConsolePal.WriteStdoutAnsiString(s_clearToEol, mayChangeCursorPosition: false); } else { if (s_moveLeftString == null) { string?moveLeft = ConsolePal.TerminalFormatStrings.Instance.CursorLeft; s_moveLeftString = !string.IsNullOrEmpty(moveLeft) ? moveLeft + " " + moveLeft : string.Empty; } Console.Write(s_moveLeftString); } } } } else if (keyInfo.Key == ConsoleKey.Tab) { _readLineSB.Append(keyInfo.KeyChar); if (!previouslyProcessed) { Console.Write(' '); } } else if (keyInfo.Key == ConsoleKey.Clear) { _readLineSB.Clear(); if (!previouslyProcessed) { Console.Clear(); } } else if (keyInfo.KeyChar != '\0') { _readLineSB.Append(keyInfo.KeyChar); if (!previouslyProcessed) { Console.Write(keyInfo.KeyChar); } } }
public unsafe IChildProcessStateHolder SpawnProcess( ref ChildProcessStartInfoInternal startInfo, string resolvedPath, SafeHandle stdIn, SafeHandle stdOut, SafeHandle stdErr) { var arguments = startInfo.Arguments; var environmentVariables = startInfo.EnvironmentVariables; var workingDirectory = startInfo.WorkingDirectory; var flags = startInfo.Flags; Debug.Assert(startInfo.CreateNewConsole || ConsolePal.HasConsoleWindow()); var commandLine = WindowsCommandLineUtil.MakeCommandLine(resolvedPath, arguments ?? Array.Empty <string>(), !flags.HasDisableArgumentQuoting()); var environmentBlock = startInfo.UseCustomEnvironmentVariables ? WindowsEnvironmentBlockUtil.MakeEnvironmentBlock(environmentVariables.Span) : null; // Objects that need cleanup InputWriterOnlyPseudoConsole?pseudoConsole = null; SafeJobObjectHandle? jobObjectHandle = null; SafeProcessHandle? processHandle = null; SafeThreadHandle? threadHandle = null; try { pseudoConsole = startInfo.CreateNewConsole ? InputWriterOnlyPseudoConsole.Create() : null; if (pseudoConsole is not null && flags.HasUseCustomCodePage()) { ChangeCodePage(pseudoConsole, startInfo.CodePage, workingDirectory); } bool killOnClose = startInfo.AllowSignal && WindowsVersion.NeedsWorkaroundForWindows1809; jobObjectHandle = CreateJobObject(killOnClose, startInfo.DisableWindowsErrorReportingDialog); using var inheritableHandleStore = new InheritableHandleStore(3); var childStdIn = stdIn != null?inheritableHandleStore.Add(stdIn) : null; var childStdOut = stdOut != null?inheritableHandleStore.Add(stdOut) : null; var childStdErr = stdErr != null?inheritableHandleStore.Add(stdErr) : null; IntPtr jobObjectHandles = jobObjectHandle.DangerousGetHandle(); Span <IntPtr> inheritableHandles = stackalloc IntPtr[inheritableHandleStore.Count]; inheritableHandleStore.DangerousGetHandles(inheritableHandles); fixed(IntPtr *pInheritableHandles = inheritableHandles) { using var attr = new ProcThreadAttributeList(3); if (pseudoConsole is not null) { attr.UpdatePseudoConsole(pseudoConsole.Handle.DangerousGetHandle()); } attr.UpdateHandleList(pInheritableHandles, inheritableHandles.Length); attr.UpdateJobList(&jobObjectHandles, 1); const int CreationFlags = Kernel32.CREATE_UNICODE_ENVIRONMENT | Kernel32.EXTENDED_STARTUPINFO_PRESENT; int processId; (processId, processHandle, threadHandle) = InvokeCreateProcess( commandLine, CreationFlags, environmentBlock, workingDirectory, childStdIn, childStdOut, childStdErr, attr); return(new WindowsChildProcessState(processId, processHandle, jobObjectHandle, pseudoConsole, startInfo.AllowSignal)); } } catch { if (processHandle is not null) { Kernel32.TerminateProcess(processHandle, -1); processHandle.Dispose(); } pseudoConsole?.Dispose(); jobObjectHandle?.Dispose(); throw; } finally { threadHandle?.Dispose(); } }