public static string FetchLine(Func <string, string> suggestionsCallback) { if (logStream != null) { throw new NotSupportedException("Log is redirected to file"); } if (ConsoleUtils.IsOutputRedirected) { return(Console.ReadLine()); } activeInput = true; RenderInputPrompt(); Console.CursorVisible = true; Console.TreatControlCAsInput = true; int consoleWidth; buffer = new StringBuilder(); bufferPosition = 0; bufferLastLength = 0; activeSuggestion = null; if (inputHistory == null) { inputHistory = new History(); } inputHistory.CursorToEnd(); inputHistory.Append(""); while (activeInput) { bool isChanged = false; ConsoleModifiers mod; ConsoleKeyInfo cki = Console.ReadKey(true); if (cki.Key == ConsoleKey.Escape) { cki = Console.ReadKey(true); mod = ConsoleModifiers.Alt; } else { mod = cki.Modifiers; } // Process input switch (cki.Key) { case ConsoleKey.Enter: { if (buffer.Length > 0) { activeInput = false; isChanged = true; } break; } case ConsoleKey.LeftArrow: { if (bufferPosition > 0) { bufferPosition--; } break; } case ConsoleKey.RightArrow: { if (bufferPosition < buffer.Length) { bufferPosition++; } else if (activeSuggestion != null) { buffer.Clear(); buffer.Append(activeSuggestion); bufferPosition = buffer.Length; isChanged = true; } break; } case ConsoleKey.UpArrow: { if (inputHistory != null && inputHistory.IsPreviousAvailable) { inputHistory.UpdateCurrent(buffer.ToString()); buffer.Clear(); buffer.Append(inputHistory.GetPrevious()); bufferPosition = buffer.Length; isChanged = true; } break; } case ConsoleKey.DownArrow: { if (inputHistory != null && inputHistory.IsNextAvailable) { inputHistory.UpdateCurrent(buffer.ToString()); buffer.Clear(); buffer.Append(inputHistory.GetNext()); bufferPosition = buffer.Length; isChanged = true; } break; } case ConsoleKey.Home: { bufferPosition = 0; break; } case ConsoleKey.End: { bufferPosition = buffer.Length; break; } case ConsoleKey.Backspace: { if (bufferPosition > 0) { bufferPosition--; buffer.Remove(bufferPosition, 1); isChanged = true; } break; } case ConsoleKey.Delete: { if (bufferPosition < buffer.Length) { buffer.Remove(bufferPosition, 1); isChanged = true; } break; } case ConsoleKey.Tab: { if (activeSuggestion != null) { buffer.Clear(); buffer.Append(activeSuggestion); bufferPosition = buffer.Length; isChanged = true; } break; } default: { if (mod == ConsoleModifiers.Control && cki.Key == ConsoleKey.C) { // CTRL+C: Close activeInput = false; buffer = null; } else if (mod == ConsoleModifiers.Control && cki.Key == ConsoleKey.L) { // CTRL+L: Clear screen Console.Clear(); Console.SetCursorPosition(0, 0); RenderInputPrompt(); isChanged = true; } else if (cki.KeyChar != (char)0 && !char.IsControl(cki.KeyChar)) { buffer.Insert(bufferPosition, cki.KeyChar); bufferPosition++; isChanged = true; } break; } } // Fetch suggestions if (activeInput && isChanged && suggestionsCallback != null) { activeSuggestion = suggestionsCallback(buffer.ToString()); } RenderInput(isChanged); isChanged = false; } Console.TreatControlCAsInput = false; Console.CursorVisible = false; activeInput = false; if (buffer != null) { // Set cursor position to the end and create a new line consoleWidth = Console.BufferWidth; Console.SetCursorPosition((activeInputX + buffer.Length) % consoleWidth, activeInputY + (activeInputX + buffer.Length) / consoleWidth); Console.WriteLine(); string input = buffer.ToString(); if (string.IsNullOrEmpty(input)) { inputHistory.Discard(); } else { inputHistory.Accept(input); } return(input); } else { // Erase input line Console.SetCursorPosition(0, activeInputY); Console.ResetColor(); for (int i = 0; i <= activeInputX + bufferLastLength; i++) { Console.Write(' '); } Console.SetCursorPosition(0, activeInputY); return(null); } }