/// <summary>Reads from the stdin reader, unbuffered, until the specified condition is met.</summary> private static unsafe void ReadStdinUnbufferedUntil( StdInStreamReader reader, byte *buffer, int bufferSize, ref int bytesRead, ref int pos, Func <byte, bool> condition) { while (true) { for (; pos < bytesRead && !condition(buffer[pos]); pos++) { ; } if (pos < bytesRead) { return; } bytesRead = reader.ReadStdinUnbuffered(buffer, bufferSize); pos = 0; } }
/// <summary>Gets the current cursor position. This involves both writing to stdout and reading stdin.</summary> private static unsafe void GetCursorPosition(out int left, out int top) { left = top = 0; // Getting the cursor position involves both writing out a request string and // parsing a response string from the terminal. So if anything is redirected, bail. if (Console.IsInputRedirected || Console.IsOutputRedirected) { return; } // Get the cursor position request format string. Debug.Assert(!string.IsNullOrEmpty(TerminalFormatStrings.CursorPositionReport)); // Synchronize with all other stdin readers. We need to do this in case multiple threads are // trying to read/write concurrently, and to minimize the chances of resulting conflicts. // This does mean that Console.get_CursorLeft/Top can't be used concurrently Console.Read*, etc.; // attempting to do so will block one of them until the other completes, but in doing so we prevent // one thread's get_CursorLeft/Top from providing input to the other's Console.Read*. lock (StdInReader) { // Write out the cursor position report request. WriteStdoutAnsiString(TerminalFormatStrings.CursorPositionReport); // Read the response. There's a race condition here if the user is typing, // or if other threads are accessing the console; there's relatively little // we can do about that, but we try not to lose any data. StdInStreamReader r = StdInReader.Inner; const int BufferSize = 1024; byte * bytes = stackalloc byte[BufferSize]; int bytesRead = 0, i = 0; // Response expected in the form "\ESC[row;colR". However, user typing concurrently // with the request/response sequence can result in other characters, and potentially // other escape sequences (e.g. for an arrow key) being entered concurrently with // the response. To avoid garbage showing up in the user's input, we are very liberal // with regards to eating all input from this point until all aspects of the sequence // have been consumed. // Find the ESC as the start of the sequence. ReadStdinUnbufferedUntil(r, bytes, BufferSize, ref bytesRead, ref i, b => b == 0x1B); i++; // move past the ESC // Find the '[' ReadStdinUnbufferedUntil(r, bytes, BufferSize, ref bytesRead, ref i, b => b == '['); // Find the first Int32 and parse it. ReadStdinUnbufferedUntil(r, bytes, BufferSize, ref bytesRead, ref i, b => IsDigit((char)b)); int row = ParseInt32(bytes, bytesRead, ref i); if (row >= 1) { top = row - 1; } // Find the second Int32 and parse it. ReadStdinUnbufferedUntil(r, bytes, BufferSize, ref bytesRead, ref i, b => IsDigit((char)b)); int col = ParseInt32(bytes, bytesRead, ref i); if (col >= 1) { left = col - 1; } // Find the ending 'R' ReadStdinUnbufferedUntil(r, bytes, BufferSize, ref bytesRead, ref i, b => b == 'R'); } }