diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs index 8ddeca197af..0a0775a18a5 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs @@ -716,35 +716,48 @@ internal static void SetMode(ConsoleHandle consoleHandle, ConsoleModes mode) /// /// /// Handle to the console device returned by GetInputHandle - /// - /// Initial contents of the edit buffer, if any. charactersToRead should be at least as large as the length of this string. + /// + /// Length of initial content of the edit buffer. Zero if no initial content exists. + /// Must be less than editBuffer length. + /// + /// + /// Edit buffer with optional initial content. + /// Caution! Last position in the edit buffer is for a null in native code. /// /// /// Number of characters to read from the device. + /// Must be less than editBuffer length. /// /// - /// true to allow the user to terminate input by hitting the tab or shift-tab key, in addition to the enter key + /// True to allow the user to terminate input by hitting the tab or shift-tab key, in addition to the enter key /// /// - /// bit mask indicating the state of the control/shift keys at the point input was terminated. + /// Bit mask indicating the state of the control/shift keys at the point input was terminated. + /// /// /// /// /// If Win32's ReadConsole fails /// - internal static string ReadConsole(ConsoleHandle consoleHandle, string initialContent, - int charactersToRead, bool endOnTab, out uint keyState) + internal static string ReadConsole( + ConsoleHandle consoleHandle, + int initialContentLength, + Span editBuffer, + int charactersToRead, + bool endOnTab, + out uint keyState) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed"); - Dbg.Assert(initialContent != null, "if no initial content is desired, pass string.Empty"); + Dbg.Assert(initialContentLength < editBuffer.Length, "initialContentLength must be less than editBuffer.Length"); + Dbg.Assert(charactersToRead < editBuffer.Length, "charactersToRead must be less than editBuffer.Length"); keyState = 0; CONSOLE_READCONSOLE_CONTROL control = new CONSOLE_READCONSOLE_CONTROL(); control.nLength = (ULONG)Marshal.SizeOf(control); - control.nInitialChars = (ULONG)initialContent.Length; + control.nInitialChars = (ULONG)initialContentLength; control.dwControlKeyState = 0; if (endOnTab) { @@ -753,28 +766,34 @@ internal static string ReadConsole(ConsoleHandle consoleHandle, string initialCo control.dwCtrlWakeupMask = (1 << TAB); } - DWORD charsReadUnused = 0; - StringBuilder buffer = new StringBuilder(initialContent, charactersToRead); + DWORD charsReaded = 0; + bool result = NativeMethods.ReadConsole( consoleHandle.DangerousGetHandle(), - buffer, + editBuffer, (DWORD)charactersToRead, - out charsReadUnused, + out charsReaded, ref control); keyState = control.dwControlKeyState; if (result == false) { int err = Marshal.GetLastWin32Error(); - HostException e = CreateHostException(err, "ReadConsole", - ErrorCategory.ReadError, ConsoleControlStrings.ReadConsoleExceptionTemplate); + HostException e = CreateHostException( + err, + "ReadConsole", + ErrorCategory.ReadError, + ConsoleControlStrings.ReadConsoleExceptionTemplate); throw e; } - if (charsReadUnused > (uint)buffer.Length) - charsReadUnused = (uint)buffer.Length; - return buffer.ToString(0, (int)charsReadUnused); + if (charsReaded > (uint)charactersToRead) + { + charsReaded = (uint)charactersToRead; + } + + return editBuffer.Slice(0, (int)charsReaded).ToString(); } /// @@ -3019,15 +3038,30 @@ IntPtr reserved [DllImport(PinvokeDllNames.ReadConsoleDllName, SetLastError = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ReadConsole + private static extern unsafe bool ReadConsole ( NakedWin32Handle consoleInput, - StringBuilder buffer, + char* lpBuffer, DWORD numberOfCharsToRead, out DWORD numberOfCharsRead, ref CONSOLE_READCONSOLE_CONTROL controlData ); + internal static unsafe bool ReadConsole + ( + NakedWin32Handle consoleInput, + Span buffer, + DWORD numberOfCharsToRead, + out DWORD numberOfCharsRead, + ref CONSOLE_READCONSOLE_CONTROL controlData + ) + { + fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer)) + { + return ReadConsole(consoleInput, bufferPtr, numberOfCharsToRead, out numberOfCharsRead, ref controlData); + } + } + [DllImport(PinvokeDllNames.PeekConsoleInputDllName, SetLastError = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool PeekConsoleInput diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index d735428d1eb..bc8fa54f8a3 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -307,8 +307,9 @@ private object ReadLineSafe(bool isSecureString, char? printToken) #if UNIX ConsoleKeyInfo keyInfo = Console.ReadKey(true); #else - uint unused = 0; - string key = ConsoleControl.ReadConsole(handle, string.Empty, 1, false, out unused); + const int CharactersToRead = 1; + Span inputBuffer = stackalloc char[CharactersToRead + 1]; + string key = ConsoleControl.ReadConsole(handle, initialContentLength: 0, inputBuffer, charactersToRead: CharactersToRead, endOnTab: false, out _); #endif #if UNIX @@ -1287,7 +1288,7 @@ internal enum ReadLineResult endedOnBreak = 3 } - private const int maxInputLineLength = 8192; + private const int MaxInputLineLength = 1024; /// /// Reads a line of input from the console. Returns when the user hits enter, a break key, a break event occurs. In @@ -1468,13 +1469,19 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca _rawui.ClearKeyCache(); uint keyState = 0; string s = string.Empty; + Span inputBuffer = stackalloc char[MaxInputLineLength + 1]; + if (initialContent.Length > 0) + { + initialContent.AsSpan().CopyTo(inputBuffer); + } + #endif do { #if UNIX keyInfo = Console.ReadKey(true); #else - s += ConsoleControl.ReadConsole(handle, initialContent, maxInputLineLength, endOnTab, out keyState); + s += ConsoleControl.ReadConsole(handle, initialContent.Length, inputBuffer, MaxInputLineLength, endOnTab, out keyState); Dbg.Assert(s != null, "s should never be null"); #endif @@ -1861,9 +1868,9 @@ internal string ReadLineWithTabCompletion(Executor exec) completedInput += restOfLine; } - if (completedInput.Length > (maxInputLineLength - 2)) + if (completedInput.Length > (MaxInputLineLength - 2)) { - completedInput = completedInput.Substring(0, maxInputLineLength - 2); + completedInput = completedInput.Substring(0, MaxInputLineLength - 2); } // Remove any nulls from the string...