diff --git a/.github/actions/spelling/expect/alphabet.txt b/.github/actions/spelling/expect/alphabet.txt index b2c7597eee86..97180b721152 100644 --- a/.github/actions/spelling/expect/alphabet.txt +++ b/.github/actions/spelling/expect/alphabet.txt @@ -21,6 +21,7 @@ BBBBCCCCC BBGGRR efg EFG +efgh EFGh KLMNOQQQQQQQQQQ QQQQQQQQQQABCDEFGHIJ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 722bf19093b9..4f5aa4448b3d 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -690,7 +690,7 @@ FSINFOCLASS fte Ftm Fullscreens -fullwidth +Fullwidth FUNCTIONCALL fuzzer fuzzmain @@ -1117,6 +1117,7 @@ MDs MEASUREITEM megamix memallocator +meme MENUCHAR MENUCONTROL MENUDROPALIGNMENT diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index 9f267746511d..327993080159 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -21,7 +21,6 @@ #include "../host/readDataCooked.hpp" #include "../host/output.h" #include "../host/_stream.h" // For WriteCharsLegacy -#include "../host/cmdline.h" // For WC_INTERACTIVE #include "test/CommonState.hpp" #include "../cascadia/TerminalCore/Terminal.hpp" @@ -3166,20 +3165,6 @@ void ConptyRoundtripTests::NewLinesAtBottomWithBackground() verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); } -void doWriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view string, DWORD flags = 0) -{ - auto dwNumBytes = string.size() * sizeof(wchar_t); - VERIFY_NT_SUCCESS(WriteCharsLegacy(screenInfo, - string.data(), - string.data(), - string.data(), - &dwNumBytes, - nullptr, - screenInfo.GetTextBuffer().GetCursor().GetPosition().x, - flags, - nullptr)); -} - void ConptyRoundtripTests::WrapNewLineAtBottom() { // The actual bug case is @@ -3221,11 +3206,6 @@ void ConptyRoundtripTests::WrapNewLineAtBottom() return; } - // I've tested this with 0x0, 0x4, 0x80, 0x84, and 0-8, and none of these - // flags seem to make a difference. So we're just assuming 0 here, so we - // don't test a bunch of redundant cases. - const auto writeCharsLegacyMode = 0; - // This test was originally written for // https://github.com/microsoft/terminal/issues/5691 // @@ -3264,7 +3244,7 @@ void ConptyRoundtripTests::WrapNewLineAtBottom() } else if (writingMethod == PrintWithWriteCharsLegacy) { - doWriteCharsLegacy(si, str, writeCharsLegacyMode); + WriteCharsLegacy(si, str, false, nullptr)); } }; @@ -3422,7 +3402,7 @@ void ConptyRoundtripTests::WrapNewLineAtBottomLikeMSYS() } else if (writingMethod == PrintWithWriteCharsLegacy) { - doWriteCharsLegacy(si, str, WC_INTERACTIVE); + WriteCharsLegacy(si, str, true, nullptr)); } }; diff --git a/src/host/CommandListPopup.cpp b/src/host/CommandListPopup.cpp deleted file mode 100644 index b530ca2760d4..000000000000 --- a/src/host/CommandListPopup.cpp +++ /dev/null @@ -1,549 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "CommandListPopup.hpp" -#include "stream.h" -#include "_stream.h" -#include "cmdline.h" -#include "misc.h" -#include "_output.h" -#include "dbcs.h" -#include "../types/inc/GlyphWidth.hpp" - -#include "../interactivity/inc/ServiceLocator.hpp" - -static constexpr size_t COMMAND_NUMBER_SIZE = 8; // size of command number buffer - -// Routine Description: -// - Calculates what the proposed size of the popup should be, based on the commands in the history -// Arguments: -// - history - the history to look through to measure command sizes -// Return Value: -// - the proposed size of the popup with the history list taken into account -static til::size calculatePopupSize(const CommandHistory& history) -{ - // this is the historical size of the popup, so it is now used as a minimum - const til::size minSize = { 40, 10 }; - - // padding is for the command number listing before a command is printed to the window. - // ex: |10: echo blah - // ^^^^ <- these are the cells that are being accounted for by padding - const size_t padding = 4; - - // find the widest command history item and use it for the width - size_t width = minSize.width; - for (CommandHistory::Index i = 0; i < history.GetNumberOfCommands(); ++i) - { - const auto& historyItem = history.GetNth(i); - width = std::max(width, historyItem.size() + padding); - } - if (width > SHRT_MAX) - { - width = SHRT_MAX; - } - - // calculate height, it can range up to 20 rows - auto height = std::clamp(gsl::narrow(history.GetNumberOfCommands()), minSize.height, 20); - - return { gsl::narrow_cast(width), height }; -} - -CommandListPopup::CommandListPopup(SCREEN_INFORMATION& screenInfo, const CommandHistory& history) : - Popup(screenInfo, calculatePopupSize(history)), - _history{ history }, - _currentCommand{ std::min(history.LastDisplayed, history.GetNumberOfCommands() - 1) } -{ - FAIL_FAST_IF(_currentCommand < 0); - _setBottomIndex(); -} - -[[nodiscard]] NTSTATUS CommandListPopup::_handlePopupKeys(COOKED_READ_DATA& cookedReadData, - const wchar_t wch, - const DWORD modifiers) noexcept -{ - try - { - CommandHistory::Index Index = 0; - const auto shiftPressed = WI_IsFlagSet(modifiers, SHIFT_PRESSED); - switch (wch) - { - case VK_F9: - { - const auto hr = CommandLine::Instance().StartCommandNumberPopup(cookedReadData); - if (S_FALSE == hr) - { - // If we couldn't make the popup, break and go around to read another input character. - break; - } - else - { - return hr; - } - } - case VK_ESCAPE: - CommandLine::Instance().EndCurrentPopup(); - return CONSOLE_STATUS_WAIT_NO_BLOCK; - case VK_UP: - if (shiftPressed) - { - return _swapUp(cookedReadData); - } - else - { - _update(-1); - } - break; - case VK_DOWN: - if (shiftPressed) - { - return _swapDown(cookedReadData); - } - else - { - _update(1); - } - break; - case VK_END: - // Move waaay forward, UpdateCommandListPopup() can handle it. - _update(cookedReadData.History().GetNumberOfCommands()); - break; - case VK_HOME: - // Move waaay back, UpdateCommandListPopup() can handle it. - _update(-cookedReadData.History().GetNumberOfCommands()); - break; - case VK_PRIOR: - _update(-Height()); - break; - case VK_NEXT: - _update(Height()); - break; - case VK_DELETE: - return _deleteSelection(cookedReadData); - case VK_LEFT: - case VK_RIGHT: - Index = _currentCommand; - CommandLine::Instance().EndCurrentPopup(); - SetCurrentCommandLine(cookedReadData, Index); - return CONSOLE_STATUS_WAIT_NO_BLOCK; - default: - break; - } - } - CATCH_LOG(); - return STATUS_SUCCESS; -} - -void CommandListPopup::_setBottomIndex() -{ - if (_currentCommand < _history.GetNumberOfCommands() - Height()) - { - _bottomIndex = std::max(_currentCommand, Height() - 1); - } - else - { - _bottomIndex = _history.GetNumberOfCommands() - 1; - } -} - -[[nodiscard]] NTSTATUS CommandListPopup::_deleteSelection(COOKED_READ_DATA& cookedReadData) noexcept -{ - try - { - auto& history = cookedReadData.History(); - history.Remove(_currentCommand); - _setBottomIndex(); - - if (history.GetNumberOfCommands() == 0) - { - // close the popup - return CONSOLE_STATUS_READ_COMPLETE; - } - else if (_currentCommand >= history.GetNumberOfCommands()) - { - _currentCommand = history.GetNumberOfCommands() - 1; - _bottomIndex = _currentCommand; - } - - _drawList(); - } - CATCH_LOG(); - return STATUS_SUCCESS; -} - -// Routine Description: -// - moves the selected history item up in the history list -// Arguments: -// - cookedReadData - the read wait object to operate upon -[[nodiscard]] NTSTATUS CommandListPopup::_swapUp(COOKED_READ_DATA& cookedReadData) noexcept -{ - try - { - auto& history = cookedReadData.History(); - - if (history.GetNumberOfCommands() <= 1 || _currentCommand == 0) - { - return STATUS_SUCCESS; - } - history.Swap(_currentCommand, _currentCommand - 1); - _update(-1); - _drawList(); - } - CATCH_LOG(); - return STATUS_SUCCESS; -} - -// Routine Description: -// - moves the selected history item down in the history list -// Arguments: -// - cookedReadData - the read wait object to operate upon -[[nodiscard]] NTSTATUS CommandListPopup::_swapDown(COOKED_READ_DATA& cookedReadData) noexcept -{ - try - { - auto& history = cookedReadData.History(); - - if (history.GetNumberOfCommands() <= 1 || _currentCommand == history.GetNumberOfCommands() - 1) - { - return STATUS_SUCCESS; - } - history.Swap(_currentCommand, _currentCommand + 1); - _update(1); - _drawList(); - } - CATCH_LOG(); - return STATUS_SUCCESS; -} - -void CommandListPopup::_handleReturn(COOKED_READ_DATA& cookedReadData) -{ - CommandHistory::Index Index = 0; - auto Status = STATUS_SUCCESS; - DWORD LineCount = 1; - Index = _currentCommand; - CommandLine::Instance().EndCurrentPopup(); - SetCurrentCommandLine(cookedReadData, Index); - cookedReadData.ProcessInput(UNICODE_CARRIAGERETURN, 0, Status); - // complete read - if (cookedReadData.IsEchoInput()) - { - // check for alias - cookedReadData.ProcessAliases(LineCount); - } - - Status = STATUS_SUCCESS; - size_t NumBytes; - if (cookedReadData.BytesRead() > cookedReadData.UserBufferSize() || LineCount > 1) - { - if (LineCount > 1) - { - const wchar_t* Tmp; - for (Tmp = cookedReadData.BufferStartPtr(); *Tmp != UNICODE_LINEFEED; Tmp++) - { - FAIL_FAST_IF(!(Tmp < (cookedReadData.BufferStartPtr() + cookedReadData.BytesRead()))); - } - NumBytes = (Tmp - cookedReadData.BufferStartPtr() + 1) * sizeof(*Tmp); - } - else - { - NumBytes = cookedReadData.UserBufferSize(); - } - - // Copy what we can fit into the user buffer - const auto bytesWritten = cookedReadData.SavePromptToUserBuffer(NumBytes / sizeof(wchar_t)); - - // Store all of the remaining as pending until the next read operation. - cookedReadData.SavePendingInput(NumBytes / sizeof(wchar_t), LineCount > 1); - NumBytes = bytesWritten; - } - else - { - NumBytes = cookedReadData.BytesRead(); - NumBytes = cookedReadData.SavePromptToUserBuffer(NumBytes / sizeof(wchar_t)); - } - - cookedReadData.SetReportedByteCount(NumBytes); -} - -void CommandListPopup::_cycleSelectionToMatchingCommands(COOKED_READ_DATA& cookedReadData, const wchar_t wch) -{ - CommandHistory::Index Index = 0; - if (cookedReadData.History().FindMatchingCommand({ &wch, 1 }, - _currentCommand, - Index, - CommandHistory::MatchOptions::JustLooking)) - { - _update(Index - _currentCommand, true); - } -} - -// Routine Description: -// - This routine handles the command list popup. It returns when we're out of input or the user has selected a command line. -// Return Value: -// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created -// - CONSOLE_STATUS_READ_COMPLETE - user hit return -[[nodiscard]] NTSTATUS CommandListPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept -{ - auto Status = STATUS_SUCCESS; - - for (;;) - { - auto wch = UNICODE_NULL; - auto popupKeys = false; - DWORD modifiers = 0; - - Status = _getUserInput(cookedReadData, popupKeys, modifiers, wch); - if (FAILED_NTSTATUS(Status)) - { - return Status; - } - - if (popupKeys) - { - Status = _handlePopupKeys(cookedReadData, wch, modifiers); - if (Status != STATUS_SUCCESS) - { - return Status; - } - } - else if (wch == UNICODE_CARRIAGERETURN) - { - _handleReturn(cookedReadData); - return CONSOLE_STATUS_READ_COMPLETE; - } - else - { - // cycle through commands that start with the letter of the key pressed - _cycleSelectionToMatchingCommands(cookedReadData, wch); - } - } -} - -void CommandListPopup::_DrawContent() -{ - _drawList(); -} - -// Routine Description: -// - Draws a list of commands for the user to choose from -void CommandListPopup::_drawList() -{ - // draw empty popup - til::point WriteCoord; - WriteCoord.x = _region.left + 1; - WriteCoord.y = _region.top + 1; - size_t lStringLength = Width(); - for (til::CoordType i = 0; i < Height(); ++i) - { - const OutputCellIterator spaces(UNICODE_SPACE, _attributes, lStringLength); - const auto result = _screenInfo.Write(spaces, WriteCoord); - lStringLength = result.GetCellDistance(spaces); - WriteCoord.y += 1; - } - - auto api = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().api; - - WriteCoord.y = _region.top + 1; - auto i = std::max(_bottomIndex - Height() + 1, 0); - for (; i <= _bottomIndex; i++) - { - CHAR CommandNumber[COMMAND_NUMBER_SIZE]; - // Write command number to screen. - if (0 != _itoa_s(i, CommandNumber, ARRAYSIZE(CommandNumber), 10)) - { - return; - } - - auto CommandNumberPtr = CommandNumber; - - size_t CommandNumberLength; - if (FAILED(StringCchLengthA(CommandNumberPtr, ARRAYSIZE(CommandNumber), &CommandNumberLength))) - { - return; - } - __assume_bound(CommandNumberLength); - - if (CommandNumberLength + 1 >= ARRAYSIZE(CommandNumber)) - { - return; - } - - CommandNumber[CommandNumberLength] = ':'; - CommandNumber[CommandNumberLength + 1] = ' '; - CommandNumberLength += 2; - if (CommandNumberLength > static_cast(Width())) - { - CommandNumberLength = static_cast(Width()); - } - - WriteCoord.x = _region.left + 1; - - LOG_IF_FAILED(api->WriteConsoleOutputCharacterAImpl(_screenInfo, - { CommandNumberPtr, CommandNumberLength }, - WriteCoord, - CommandNumberLength)); - - // write command to screen - auto command = _history.GetNth(i); - lStringLength = command.size(); - { - auto lTmpStringLength = lStringLength; - auto lPopupLength = static_cast(Width() - CommandNumberLength); - auto lpStr = command.data(); - while (lTmpStringLength--) - { - if (IsGlyphFullWidth(*lpStr++)) - { - lPopupLength -= 2; - } - else - { - lPopupLength--; - } - - if (lPopupLength <= 0) - { - lStringLength -= lTmpStringLength; - if (lPopupLength < 0) - { - lStringLength--; - } - - break; - } - } - } - - WriteCoord.x = gsl::narrow(WriteCoord.x + CommandNumberLength); - size_t used; - LOG_IF_FAILED(api->WriteConsoleOutputCharacterWImpl(_screenInfo, - { command.data(), lStringLength }, - WriteCoord, - used)); - - // write attributes to screen - if (i == _currentCommand) - { - WriteCoord.x = _region.left + 1; - // inverted attributes - lStringLength = Width(); - auto inverted = _attributes; - inverted.Invert(); - - const OutputCellIterator it(inverted, lStringLength); - const auto done = _screenInfo.Write(it, WriteCoord); - - lStringLength = done.GetCellDistance(it); - } - - WriteCoord.y += 1; - } -} - -// Routine Description: -// - For popup lists, will adjust the position of the highlighted item and -// possibly scroll the list if necessary. -// Arguments: -// - originalDelta - The number of lines to move up or down -// - wrap - Down past the bottom or up past the top should wrap the command list -void CommandListPopup::_update(const CommandHistory::Index originalDelta, const bool wrap) -{ - auto delta = originalDelta; - if (delta == 0) - { - return; - } - const auto Size = Height(); - - auto CurCmdNum = _currentCommand; - CommandHistory::Index NewCmdNum = CurCmdNum + delta; - - if (wrap) - { - // Modulo the number of commands to "circle" around if we went off the end. - NewCmdNum %= _history.GetNumberOfCommands(); - } - else - { - if (NewCmdNum >= _history.GetNumberOfCommands()) - { - NewCmdNum = _history.GetNumberOfCommands() - 1; - } - else if (NewCmdNum < 0) - { - NewCmdNum = 0; - } - } - delta = NewCmdNum - CurCmdNum; - - auto Scroll = false; - // determine amount to scroll, if any - if (NewCmdNum <= _bottomIndex - Size) - { - _bottomIndex += delta; - if (_bottomIndex < Size - 1) - { - _bottomIndex = Size - 1; - } - Scroll = true; - } - else if (NewCmdNum > _bottomIndex) - { - _bottomIndex += delta; - if (_bottomIndex >= _history.GetNumberOfCommands()) - { - _bottomIndex = _history.GetNumberOfCommands() - 1; - } - Scroll = true; - } - - // write commands to popup - if (Scroll) - { - _currentCommand = NewCmdNum; - _drawList(); - } - else - { - _updateHighlight(_currentCommand, NewCmdNum); - _currentCommand = NewCmdNum; - } -} - -// Routine Description: -// - Adjusts the highlighted line in a list of commands -// Arguments: -// - OldCurrentCommand - The previous command highlighted -// - NewCurrentCommand - The new command to be highlighted. -void CommandListPopup::_updateHighlight(const CommandHistory::Index OldCurrentCommand, const CommandHistory::Index NewCurrentCommand) -{ - til::CoordType TopIndex; - if (_bottomIndex < Height()) - { - TopIndex = 0; - } - else - { - TopIndex = _bottomIndex - Height() + 1; - } - til::point WriteCoord; - WriteCoord.x = _region.left + 1; - size_t lStringLength = Width(); - - WriteCoord.y = _region.top + 1 + OldCurrentCommand - TopIndex; - - const OutputCellIterator it(_attributes, lStringLength); - const auto done = _screenInfo.Write(it, WriteCoord); - lStringLength = done.GetCellDistance(it); - - // highlight new command - WriteCoord.y = _region.top + 1 + NewCurrentCommand - TopIndex; - - // inverted attributes - auto inverted = _attributes; - inverted.Invert(); - const OutputCellIterator itAttr(inverted, lStringLength); - const auto doneAttr = _screenInfo.Write(itAttr, WriteCoord); - lStringLength = done.GetCellDistance(itAttr); -} diff --git a/src/host/CommandListPopup.hpp b/src/host/CommandListPopup.hpp deleted file mode 100644 index 59bef4c3946f..000000000000 --- a/src/host/CommandListPopup.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- CommandListPopup.hpp - -Abstract: -- Popup used for use command list input -- contains code pulled from popup.cpp and cmdline.cpp - -Author: -- Austin Diviness (AustDi) 18-Aug-2018 ---*/ - -#pragma once - -#include "popup.h" - -class CommandListPopup : public Popup -{ -public: - CommandListPopup(SCREEN_INFORMATION& screenInfo, const CommandHistory& history); - - [[nodiscard]] NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override; - -protected: - void _DrawContent() override; - -private: - void _drawList(); - void _update(const CommandHistory::Index delta, const bool wrap = false); - void _updateHighlight(const CommandHistory::Index oldCommand, const CommandHistory::Index newCommand); - - void _handleReturn(COOKED_READ_DATA& cookedReadData); - void _cycleSelectionToMatchingCommands(COOKED_READ_DATA& cookedReadData, const wchar_t wch); - void _setBottomIndex(); - [[nodiscard]] NTSTATUS _handlePopupKeys(COOKED_READ_DATA& cookedReadData, const wchar_t wch, const DWORD modifiers) noexcept; - [[nodiscard]] NTSTATUS _deleteSelection(COOKED_READ_DATA& cookedReadData) noexcept; - [[nodiscard]] NTSTATUS _swapUp(COOKED_READ_DATA& cookedReadData) noexcept; - [[nodiscard]] NTSTATUS _swapDown(COOKED_READ_DATA& cookedReadData) noexcept; - - CommandHistory::Index _currentCommand; - CommandHistory::Index _bottomIndex; // number of command displayed on last line of popup - const CommandHistory& _history; - -#ifdef UNIT_TESTING - friend class CommandListPopupTests; -#endif -}; diff --git a/src/host/CommandNumberPopup.cpp b/src/host/CommandNumberPopup.cpp deleted file mode 100644 index 6e990439d708..000000000000 --- a/src/host/CommandNumberPopup.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "CommandNumberPopup.hpp" - -#include "stream.h" -#include "_stream.h" -#include "cmdline.h" -#include "resource.h" - -#include "../interactivity/inc/ServiceLocator.hpp" - -// 5 digit number for command history -static constexpr size_t COMMAND_NUMBER_LENGTH = 5; - -static constexpr size_t COMMAND_NUMBER_PROMPT_LENGTH = 22; - -CommandNumberPopup::CommandNumberPopup(SCREEN_INFORMATION& screenInfo) : - Popup(screenInfo, { COMMAND_NUMBER_PROMPT_LENGTH + COMMAND_NUMBER_LENGTH, 1 }) -{ - _userInput.reserve(COMMAND_NUMBER_LENGTH); -} - -// Routine Description: -// - handles numerical user input -// Arguments: -// - cookedReadData - read data to operate on -// - wch - digit to handle -void CommandNumberPopup::_handleNumber(COOKED_READ_DATA& cookedReadData, const wchar_t wch) noexcept -{ - if (_userInput.size() < COMMAND_NUMBER_LENGTH) - { - auto CharsToWrite = sizeof(wchar_t); - const auto realAttributes = cookedReadData.ScreenInfo().GetAttributes(); - cookedReadData.ScreenInfo().SetAttributes(_attributes); - size_t NumSpaces; - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - _userInput.data(), - _userInput.data() + _userInput.size(), - &wch, - &CharsToWrite, - &NumSpaces, - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - nullptr)); - cookedReadData.ScreenInfo().SetAttributes(realAttributes); - try - { - _push(wch); - } - CATCH_LOG(); - } -} - -// Routine Description: -// - handles backspace user input. removes a digit from the user input -// Arguments: -// - cookedReadData - read data to operate on -void CommandNumberPopup::_handleBackspace(COOKED_READ_DATA& cookedReadData) noexcept -{ - if (_userInput.size() > 0) - { - auto CharsToWrite = sizeof(WCHAR); - const auto backspace = UNICODE_BACKSPACE; - const auto realAttributes = cookedReadData.ScreenInfo().GetAttributes(); - cookedReadData.ScreenInfo().SetAttributes(_attributes); - size_t NumSpaces; - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - _userInput.data(), - _userInput.data() + _userInput.size(), - &backspace, - &CharsToWrite, - &NumSpaces, - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - nullptr)); - cookedReadData.ScreenInfo().SetAttributes(realAttributes); - _pop(); - } -} - -// Routine Description: -// - handles escape user input. cancels the popup -// Arguments: -// - cookedReadData - read data to operate on -void CommandNumberPopup::_handleEscape(COOKED_READ_DATA& cookedReadData) noexcept -{ - CommandLine::Instance().EndAllPopups(); - - // Note that cookedReadData's OriginalCursorPosition is the position before ANY text was entered on the edit line. - // We want to use the position before the cursor was moved for this popup handler specifically, which may - // be *anywhere* in the edit line and will be synchronized with the pointers in the cookedReadData - // structure (BufPtr, etc.) - LOG_IF_FAILED(cookedReadData.ScreenInfo().SetCursorPosition(cookedReadData.BeforeDialogCursorPosition(), TRUE)); -} - -// Routine Description: -// - handles return user input. sets the prompt to the history item indicated -// Arguments: -// - cookedReadData - read data to operate on -void CommandNumberPopup::_handleReturn(COOKED_READ_DATA& cookedReadData) noexcept -{ - const auto commandNumber = gsl::narrow(std::min(_parse(), cookedReadData.History().GetNumberOfCommands() - 1)); - - CommandLine::Instance().EndAllPopups(); - SetCurrentCommandLine(cookedReadData, commandNumber); -} - -// Routine Description: -// - This routine handles the command number selection popup. -// Return Value: -// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created -// - CONSOLE_STATUS_READ_COMPLETE - user hit return -[[nodiscard]] NTSTATUS CommandNumberPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept -{ - auto Status = STATUS_SUCCESS; - auto wch = UNICODE_NULL; - auto popupKeys = false; - DWORD modifiers = 0; - - for (;;) - { - Status = _getUserInput(cookedReadData, popupKeys, modifiers, wch); - if (FAILED_NTSTATUS(Status)) - { - return Status; - } - - if (std::iswdigit(wch)) - { - _handleNumber(cookedReadData, wch); - } - else if (wch == UNICODE_BACKSPACE) - { - _handleBackspace(cookedReadData); - } - else if (wch == VK_ESCAPE) - { - _handleEscape(cookedReadData); - break; - } - else if (wch == UNICODE_CARRIAGERETURN) - { - _handleReturn(cookedReadData); - break; - } - } - return CONSOLE_STATUS_WAIT_NO_BLOCK; -} - -void CommandNumberPopup::_DrawContent() -{ - _DrawPrompt(ID_CONSOLE_MSGCMDLINEF9); -} - -// Routine Description: -// - adds single digit number to the popup's number buffer -// Arguments: -// - wch - char of the number to add. must be in the range [L'0', L'9'] -// Note: will throw if wch is out of range -void CommandNumberPopup::_push(const wchar_t wch) -{ - THROW_HR_IF(E_INVALIDARG, !std::iswdigit(wch)); - if (_userInput.size() < COMMAND_NUMBER_LENGTH) - { - _userInput += wch; - } -} - -// Routine Description: -// - removes the last number added to the number buffer -void CommandNumberPopup::_pop() noexcept -{ - if (!_userInput.empty()) - { - _userInput.pop_back(); - } -} - -// Routine Description: -// - get numerical value for the data stored in the number buffer -// Return Value: -// - parsed integer representing the string value found in the number buffer -int CommandNumberPopup::_parse() const noexcept -{ - try - { - return std::stoi(_userInput); - } - catch (...) - { - return 0; - } -} diff --git a/src/host/CommandNumberPopup.hpp b/src/host/CommandNumberPopup.hpp deleted file mode 100644 index 3d0c0c22d811..000000000000 --- a/src/host/CommandNumberPopup.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- CommandNumberPopup.hpp - -Abstract: -- Popup used for use command number input -- contains code pulled from popup.cpp and cmdline.cpp - -Author: -- Austin Diviness (AustDi) 18-Aug-2018 ---*/ - -#pragma once - -#include "popup.h" - -class CommandNumberPopup final : public Popup -{ -public: - explicit CommandNumberPopup(SCREEN_INFORMATION& screenInfo); - - [[nodiscard]] NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override; - -protected: - void _DrawContent() override; - -private: - std::wstring _userInput; - - void _handleNumber(COOKED_READ_DATA& cookedReadData, const wchar_t wch) noexcept; - void _handleBackspace(COOKED_READ_DATA& cookedReadData) noexcept; - void _handleEscape(COOKED_READ_DATA& cookedReadData) noexcept; - void _handleReturn(COOKED_READ_DATA& cookedReadData) noexcept; - - void _push(const wchar_t wch); - void _pop() noexcept; - int _parse() const noexcept; - -#ifdef UNIT_TESTING - friend class CommandNumberPopupTests; -#endif -}; diff --git a/src/host/CopyFromCharPopup.cpp b/src/host/CopyFromCharPopup.cpp deleted file mode 100644 index 9b64938225a9..000000000000 --- a/src/host/CopyFromCharPopup.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "CopyFromCharPopup.hpp" - -#include "_stream.h" -#include "resource.h" - -static constexpr size_t COPY_FROM_CHAR_PROMPT_LENGTH = 28; - -CopyFromCharPopup::CopyFromCharPopup(SCREEN_INFORMATION& screenInfo) : - Popup(screenInfo, { COPY_FROM_CHAR_PROMPT_LENGTH + 2, 1 }) -{ -} - -// Routine Description: -// - This routine handles the delete from cursor to char popup. It returns when we're out of input or the user has entered a char. -// Return Value: -// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created -// - CONSOLE_STATUS_READ_COMPLETE - user hit return -[[nodiscard]] NTSTATUS CopyFromCharPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept -{ - // get user input - auto Char = UNICODE_NULL; - auto PopupKeys = false; - DWORD modifiers = 0; - auto Status = _getUserInput(cookedReadData, PopupKeys, modifiers, Char); - if (FAILED_NTSTATUS(Status)) - { - return Status; - } - - CommandLine::Instance().EndCurrentPopup(); - - if (PopupKeys && Char == VK_ESCAPE) - { - return CONSOLE_STATUS_WAIT_NO_BLOCK; - } - - const auto span = cookedReadData.SpanAtPointer(); - const auto foundLocation = std::find(std::next(span.begin()), span.end(), Char); - if (foundLocation == span.end()) - { - // char not found, delete everything to the right of the cursor - CommandLine::Instance().DeletePromptAfterCursor(cookedReadData); - } - else - { - // char was found, delete everything between the cursor and it - const auto difference = std::distance(span.begin(), foundLocation); - for (unsigned int i = 0; i < gsl::narrow(difference); ++i) - { - CommandLine::Instance().DeleteFromRightOfCursor(cookedReadData); - } - } - return CONSOLE_STATUS_WAIT_NO_BLOCK; -} - -void CopyFromCharPopup::_DrawContent() -{ - _DrawPrompt(ID_CONSOLE_MSGCMDLINEF4); -} diff --git a/src/host/CopyFromCharPopup.hpp b/src/host/CopyFromCharPopup.hpp deleted file mode 100644 index ee40f09be115..000000000000 --- a/src/host/CopyFromCharPopup.hpp +++ /dev/null @@ -1,29 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- CopyFromCharPopup.hpp - -Abstract: -- Popup used for use copying from char input -- contains code pulled from popup.cpp and cmdline.cpp - -Author: -- Austin Diviness (AustDi) 18-Aug-2018 ---*/ - -#pragma once - -#include "popup.h" - -class CopyFromCharPopup final : public Popup -{ -public: - explicit CopyFromCharPopup(SCREEN_INFORMATION& screenInfo); - - [[nodiscard]] NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override; - -protected: - void _DrawContent() override; -}; diff --git a/src/host/CopyToCharPopup.cpp b/src/host/CopyToCharPopup.cpp deleted file mode 100644 index 35fc69405ad7..000000000000 --- a/src/host/CopyToCharPopup.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "CopyToCharPopup.hpp" - -#include "stream.h" -#include "_stream.h" -#include "resource.h" - -static constexpr size_t COPY_TO_CHAR_PROMPT_LENGTH = 26; - -CopyToCharPopup::CopyToCharPopup(SCREEN_INFORMATION& screenInfo) : - Popup(screenInfo, { COPY_TO_CHAR_PROMPT_LENGTH + 2, 1 }) -{ -} - -// Routine Description: -// - copies text from the previous command into the current prompt line, up to but not including the first -// instance of wch after the current cookedReadData's cursor position. if wch is not found, nothing is copied. -// Arguments: -// - cookedReadData - the read data to operate on -// - LastCommand - the most recent command run -// - wch - the wchar to copy up to -void CopyToCharPopup::_copyToChar(COOKED_READ_DATA& cookedReadData, const std::wstring_view LastCommand, const wchar_t wch) -{ - // make sure that there it is possible to copy any found text over - if (cookedReadData.InsertionPoint() >= LastCommand.size()) - { - return; - } - - const auto searchStart = std::next(LastCommand.cbegin(), cookedReadData.InsertionPoint() + 1); - auto location = std::find(searchStart, LastCommand.cend(), wch); - - // didn't find wch so copy nothing - if (location == LastCommand.cend()) - { - return; - } - - const auto startIt = std::next(LastCommand.cbegin(), cookedReadData.InsertionPoint()); - const auto endIt = location; - - cookedReadData.Write({ &*startIt, gsl::narrow(std::distance(startIt, endIt)) }); -} - -// Routine Description: -// - This routine handles the delete char popup. It returns when we're out of input or the user has entered a char. -// Return Value: -// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created -// - CONSOLE_STATUS_READ_COMPLETE - user hit return -[[nodiscard]] NTSTATUS CopyToCharPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept -{ - auto wch = UNICODE_NULL; - auto popupKey = false; - DWORD modifiers = 0; - auto Status = _getUserInput(cookedReadData, popupKey, modifiers, wch); - if (FAILED_NTSTATUS(Status)) - { - return Status; - } - - CommandLine::Instance().EndCurrentPopup(); - - if (popupKey && wch == VK_ESCAPE) - { - return CONSOLE_STATUS_WAIT_NO_BLOCK; - } - - // copy up to specified char - const auto lastCommand = cookedReadData.History().GetLastCommand(); - if (!lastCommand.empty()) - { - _copyToChar(cookedReadData, lastCommand, wch); - } - - return CONSOLE_STATUS_WAIT_NO_BLOCK; -} - -void CopyToCharPopup::_DrawContent() -{ - _DrawPrompt(ID_CONSOLE_MSGCMDLINEF2); -} diff --git a/src/host/CopyToCharPopup.hpp b/src/host/CopyToCharPopup.hpp deleted file mode 100644 index a1c490d3b3dd..000000000000 --- a/src/host/CopyToCharPopup.hpp +++ /dev/null @@ -1,32 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- CopyToCharPopup.hpp - -Abstract: -- Popup used for use copying to char input -- contains code pulled from popup.cpp and cmdline.cpp - -Author: -- Austin Diviness (AustDi) 18-Aug-2018 ---*/ - -#pragma once - -#include "popup.h" - -class CopyToCharPopup final : public Popup -{ -public: - CopyToCharPopup(SCREEN_INFORMATION& screenInfo); - - [[nodiscard]] NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override; - -protected: - void _DrawContent() override; - -private: - void _copyToChar(COOKED_READ_DATA& cookedReadData, const std::wstring_view LastCommand, const wchar_t wch); -}; diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index d85d4e105f82..b42f8396607f 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -39,7 +39,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine; // - coordCursor - New location of cursor. // - fKeepCursorVisible - TRUE if changing window origin desirable when hit right edge // Return Value: -void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, const BOOL fKeepCursorVisible, _Inout_opt_ til::CoordType* psScrollY) +static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, const bool interactive, _Inout_opt_ til::CoordType* psScrollY) { const auto bufferSize = screenInfo.GetBufferSize().Dimensions(); if (coordCursor.x < 0) @@ -77,7 +77,7 @@ void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordC { *psScrollY += bufferSize.height - coordCursor.y - 1; } - coordCursor.y += bufferSize.height - coordCursor.y - 1; + coordCursor.y = bufferSize.height - 1; } const auto cursorMovedPastViewport = coordCursor.y > screenInfo.GetViewport().BottomInclusive(); @@ -91,21 +91,19 @@ void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordC LOG_IF_FAILED(screenInfo.SetViewportOrigin(false, WindowOrigin, true)); } - if (fKeepCursorVisible) + if (interactive) { screenInfo.MakeCursorVisible(coordCursor); } - LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor, !!fKeepCursorVisible)); + LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor, interactive)); } // As the name implies, this writes text without processing its control characters. -static size_t _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const DWORD dwFlags, til::CoordType* const psScrollY, const std::wstring_view& text) +static void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, const bool interactive, til::CoordType* psScrollY) { - const auto keepCursorVisible = WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE); const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT); const auto hasAccessibilityEventing = screenInfo.HasAccessibilityEventing(); auto& textBuffer = screenInfo.GetTextBuffer(); - size_t numSpaces = 0; RowWriteState state{ .text = text, @@ -120,8 +118,6 @@ static size_t _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const textBuffer.Write(cursorPosition.y, textBuffer.GetCurrentAttributes(), state); cursorPosition.x = state.columnEnd; - numSpaces += gsl::narrow_cast(state.columnEnd - state.columnBegin); - if (wrapAtEOL && state.columnEnd >= state.columnLimit) { textBuffer.SetWrapForced(cursorPosition.y, true); @@ -132,50 +128,22 @@ static size_t _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const screenInfo.NotifyAccessibilityEventing(state.columnBegin, cursorPosition.y, state.columnEnd - 1, cursorPosition.y); } - AdjustCursorPosition(screenInfo, cursorPosition, keepCursorVisible, psScrollY); + AdjustCursorPosition(screenInfo, cursorPosition, interactive, psScrollY); } - - return numSpaces; } -// Routine Description: -// - This routine writes a string to the screen, processing any embedded -// unicode characters. The string is also copied to the input buffer, if -// the output mode is line mode. -// Arguments: -// - screenInfo - reference to screen buffer information structure. -// - pwchBufferBackupLimit - Pointer to beginning of buffer. -// - pwchBuffer - Pointer to buffer to copy string to. assumed to be at least as long as pwchRealUnicode. -// This pointer is updated to point to the next position in the buffer. -// - pwchRealUnicode - Pointer to string to write. -// - pcb - On input, number of bytes to write. On output, number of bytes written. -// - pcSpaces - On output, the number of spaces consumed by the written characters. -// - dwFlags - -// WC_INTERACTIVE backspace overwrites characters, control characters are expanded (as in, to "^X") -// WC_KEEP_CURSOR_VISIBLE change window origin desirable when hit rt. edge -// Return Value: -// Note: -// - This routine does not process tabs and backspace properly. That code will be implemented as part of the line editing services. -[[nodiscard]] NTSTATUS WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, - _In_range_(<=, pwchBuffer) const wchar_t* const pwchBufferBackupLimit, - _In_ const wchar_t* pwchBuffer, - _In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode, - _Inout_ size_t* const pcb, - _Out_opt_ size_t* const pcSpaces, - const til::CoordType sOriginalXPosition, - const DWORD dwFlags, - _Inout_opt_ til::CoordType* const psScrollY) -try +// This routine writes a string to the screen while handling control characters. +// `interactive` exists for COOKED_READ_DATA which uses it to transform control characters into visible text like "^X". +// Similarly, `psScrollY` is also used by it to track whether the underlying buffer circled. It requires this information to know where the input line moved to. +void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, const bool interactive, til::CoordType* psScrollY) { static constexpr wchar_t tabSpaces[8]{ L' ', L' ', L' ', L' ', L' ', L' ', L' ', L' ' }; auto& textBuffer = screenInfo.GetTextBuffer(); auto& cursor = textBuffer.GetCursor(); - const auto keepCursorVisible = WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE); const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT); - auto it = pwchRealUnicode; - const auto end = it + *pcb / sizeof(wchar_t); - size_t numSpaces = 0; + auto it = text.begin(); + const auto end = text.end(); // In VT mode, when you have a 120-column terminal you can write 120 columns without the cursor wrapping. // Whenever the cursor is in that 120th column IsDelayedEOLWrap() will return true. I'm not sure why the VT parts @@ -192,7 +160,7 @@ try { pos.x = 0; pos.y++; - AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY); + AdjustCursorPosition(screenInfo, pos, interactive, psScrollY); } } @@ -200,7 +168,7 @@ try // If it's not set, we can just straight up give everything to _writeCharsLegacyUnprocessed. if (WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)) { - numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { it, end }); + _writeCharsLegacyUnprocessed(screenInfo, { it, end }, interactive, psScrollY); it = end; } @@ -209,7 +177,7 @@ try const auto nextControlChar = std::find_if(it, end, [](const auto& wch) { return !IS_GLYPH_CHAR(wch); }); if (nextControlChar != it) { - numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { it, nextControlChar }); + _writeCharsLegacyUnprocessed(screenInfo, { it, nextControlChar }, interactive, psScrollY); it = nextControlChar; } @@ -218,14 +186,14 @@ try switch (*it) { case UNICODE_NULL: - if (WI_IsFlagSet(dwFlags, WC_INTERACTIVE)) + if (interactive) { break; } - numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { &tabSpaces[0], 1 }); + _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, interactive, psScrollY); continue; case UNICODE_BELL: - if (WI_IsFlagSet(dwFlags, WC_INTERACTIVE)) + if (interactive) { break; } @@ -233,171 +201,20 @@ try continue; case UNICODE_BACKSPACE: { + // Backspace handling for interactive mode should happen in COOKED_READ_DATA + // where it has full control over the text and can delete it directly. + // Otherwise handling backspacing tabs/whitespace can turn up complex and bug-prone. + assert(!interactive); auto pos = cursor.GetPosition(); - - if (WI_IsFlagClear(dwFlags, WC_INTERACTIVE)) - { - pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x); - AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY); - continue; - } - - const auto moveUp = [&]() { - pos.x = -1; - AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY); - - const auto y = cursor.GetPosition().y; - auto& row = textBuffer.GetRowByOffset(y); - - pos.x = textBuffer.GetSize().RightExclusive(); - pos.y = y; - - if (row.WasDoubleBytePadded()) - { - pos.x--; - numSpaces--; - } - - row.SetWrapForced(false); - row.SetDoubleBytePadded(false); - }; - - // We have to move up early because the tab handling code below needs to be on - // the row of the tab already, so that we can call GetText() for precedingText. - if (pos.x == 0 && pos.y != 0) - { - moveUp(); - } - - til::CoordType glyphCount = 1; - - if (pwchBuffer != pwchBufferBackupLimit) - { - const auto lastChar = pwchBuffer[-1]; - - // Deleting tabs is a bit tricky, because they have a variable width between 1 and 8 spaces, - // are stored as whitespace but are technically distinct from whitespace. - if (lastChar == UNICODE_TAB) - { - const auto precedingText = textBuffer.GetRowByOffset(pos.y).GetText(pos.x - 8, pos.x); - - // First, we measure the amount of spaces that precede the cursor in the text buffer, - // which is generally the amount of spaces that we end up deleting. We do it this way, - // because we don't know what kind of complex mix of wide/narrow glyphs precede the tab. - // Basically, by asking the text buffer we get the size information of the preceding text. - if (precedingText.size() >= 2 && precedingText.back() == L' ') - { - auto textIt = precedingText.rbegin() + 1; - const auto textEnd = precedingText.rend(); - - for (; textIt != textEnd && *textIt == L' '; ++textIt) - { - glyphCount++; - } - } - - // But there's a problem: When you print " \t" it should delete 6 spaces and not 8. - // In other words, we shouldn't delete any actual preceding whitespaces. We can ask - // the "backup" buffer (= preceding text in the commandline) for this information. - // - // backupEnd points to the character immediately preceding the tab (LastChar). - const auto backupEnd = pwchBuffer - 1; - // backupLimit points to how far back we need to search. Even if we have 9000 characters in our command line, - // we'll only need to check a total of 8 whitespaces. "pwchBuffer - pwchBufferBackupLimit" will - // always be at least 1 because that's the \t character in the backup buffer. In other words, - // backupLimit will at a minimum be equal to backupEnd, or precede it by 7 more characters. - const auto backupLimit = pwchBuffer - std::min(8, pwchBuffer - pwchBufferBackupLimit); - // Now count how many spaces precede the \t character. "backupEnd - backupBeg" will be the amount. - auto backupBeg = backupEnd; - for (; backupBeg != backupLimit && backupBeg[-1] == L' '; --backupBeg, --glyphCount) - { - } - - // There's one final problem: A prompt like... - // fputs("foo: ", stdout); - // fgets(buffer, stdin); - // ...has a trailing whitespace in front of our pwchBufferBackupLimit which we should not backspace over. - // sOriginalXPosition stores the start of the prompt at the pwchBufferBackupLimit. - if (backupBeg == pwchBufferBackupLimit) - { - glyphCount = pos.x - sOriginalXPosition; - } - - // Now that we finally know how many columns precede the cursor we can - // subtract the previously determined amount of ' ' from the '\t'. - glyphCount -= gsl::narrow_cast(backupEnd - backupBeg); - - // Can the above code leave glyphCount <= 0? Let's just not find out! - glyphCount = std::max(1, glyphCount); - } - // Control chars in interactive mode were previously written out - // as ^X for instance, so now we also need to delete 2 glyphs. - else if (IS_CONTROL_CHAR(lastChar)) - { - glyphCount = 2; - } - } - - for (;;) - { - // We've already moved up if the cursor was in the first column so - // we need to start off with overwriting the text with whitespace. - // It wouldn't make sense to check the cursor position again already. - { - const auto previousColumn = pos.x; - pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(previousColumn); - - RowWriteState state{ - .text = { &tabSpaces[0], 8 }, - .columnBegin = pos.x, - .columnLimit = previousColumn, - }; - textBuffer.Write(pos.y, textBuffer.GetCurrentAttributes(), state); - numSpaces -= previousColumn - pos.x; - } - - // The cursor movement logic is a little different for the last iteration, so we exit early here. - glyphCount--; - if (glyphCount <= 0) - { - break; - } - - // Otherwise, in case we need to delete 2 or more glyphs, we need to ensure we properly wrap lines back up. - if (pos.x == 0 && pos.y != 0) - { - moveUp(); - } - } - - // After the last iteration the cursor might now be in the first column after a line - // that was previously padded with a whitespace in the last column due to a wide glyph. - // Now that the wide glyph is presumably gone, we can move up a line. - if (pos.x == 0 && pos.y != 0 && textBuffer.GetRowByOffset(pos.y - 1).WasDoubleBytePadded()) - { - moveUp(); - } - else - { - AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY); - } - - // Notify accessibility to read the backspaced character. - // See GH:12735, MSFT:31748387 - if (screenInfo.HasAccessibilityEventing()) - { - if (const auto pConsoleWindow = ServiceLocator::LocateConsoleWindow()) - { - LOG_IF_FAILED(pConsoleWindow->SignalUia(UIA_Text_TextChangedEventId)); - } - } + pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x); + AdjustCursorPosition(screenInfo, pos, interactive, psScrollY); continue; } case UNICODE_TAB: { const auto pos = cursor.GetPosition(); const auto tabCount = gsl::narrow_cast(NUMBER_OF_SPACES_IN_TAB(pos.x)); - numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { &tabSpaces[0], tabCount }); + _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, interactive, psScrollY); continue; } case UNICODE_LINEFEED: @@ -410,24 +227,24 @@ try textBuffer.GetRowByOffset(pos.y).SetWrapForced(false); pos.y = pos.y + 1; - AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY); + AdjustCursorPosition(screenInfo, pos, interactive, psScrollY); continue; } case UNICODE_CARRIAGERETURN: { auto pos = cursor.GetPosition(); pos.x = 0; - AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY); + AdjustCursorPosition(screenInfo, pos, interactive, psScrollY); continue; } default: break; } - if (WI_IsFlagSet(dwFlags, WC_INTERACTIVE) && IS_CONTROL_CHAR(*it)) + if (interactive && IS_CONTROL_CHAR(*it)) { const wchar_t wchs[2]{ L'^', static_cast(*it + L'@') }; - numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { &wchs[0], 2 }); + _writeCharsLegacyUnprocessed(screenInfo, { &wchs[0], 2 }, interactive, psScrollY); } else { @@ -439,76 +256,12 @@ try const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1); if (result == 1) { - numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { &wch, 1 }); + _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, interactive, psScrollY); } } } } - - if (pcSpaces) - { - *pcSpaces = numSpaces; - } - - return S_OK; } -NT_CATCH_RETURN() - -// Routine Description: -// - This routine writes a string to the screen, processing any embedded -// unicode characters. The string is also copied to the input buffer, if -// the output mode is line mode. -// Arguments: -// - screenInfo - reference to screen buffer information structure. -// - pwchBufferBackupLimit - Pointer to beginning of buffer. -// - pwchBuffer - Pointer to buffer to copy string to. assumed to be at least as long as pwchRealUnicode. -// This pointer is updated to point to the next position in the buffer. -// - pwchRealUnicode - Pointer to string to write. -// - pcb - On input, number of bytes to write. On output, number of bytes written. -// - pcSpaces - On output, the number of spaces consumed by the written characters. -// - dwFlags - -// WC_INTERACTIVE backspace overwrites characters, control characters are expanded (as in, to "^X") -// WC_KEEP_CURSOR_VISIBLE change window origin (viewport) desirable when hit rt. edge -// Return Value: -// Note: -// - This routine does not process tabs and backspace properly. That code will be implemented as part of the line editing services. -[[nodiscard]] NTSTATUS WriteChars(SCREEN_INFORMATION& screenInfo, - _In_range_(<=, pwchBuffer) const wchar_t* const pwchBufferBackupLimit, - _In_ const wchar_t* pwchBuffer, - _In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode, - _Inout_ size_t* const pcb, - _Out_opt_ size_t* const pcSpaces, - const til::CoordType sOriginalXPosition, - const DWORD dwFlags, - _Inout_opt_ til::CoordType* const psScrollY) -try -{ - if (WI_IsAnyFlagClear(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) - { - return WriteCharsLegacy(screenInfo, - pwchBufferBackupLimit, - pwchBuffer, - pwchRealUnicode, - pcb, - pcSpaces, - sOriginalXPosition, - dwFlags, - psScrollY); - } - - auto& machine = screenInfo.GetStateMachine(); - const auto cch = *pcb / sizeof(WCHAR); - - machine.ProcessString({ pwchRealUnicode, cch }); - - if (nullptr != pcSpaces) - { - *pcSpaces = 0; - } - - return STATUS_SUCCESS; -} -NT_CATCH_RETURN() // Routine Description: // - Takes the given text and inserts it into the given screen buffer. @@ -530,23 +283,16 @@ NT_CATCH_RETURN() SCREEN_INFORMATION& screenInfo, bool requiresVtQuirk, std::unique_ptr& waiter) +try { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING))) { - try - { - waiter = std::make_unique(screenInfo, - pwchBuffer, - *pcbBuffer, - gci.OutputCP, - requiresVtQuirk); - } - catch (...) - { - return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException()); - } - + waiter = std::make_unique(screenInfo, + pwchBuffer, + *pcbBuffer, + gci.OutputCP, + requiresVtQuirk); return CONSOLE_STATUS_WAIT; } @@ -563,17 +309,20 @@ NT_CATCH_RETURN() restoreVtQuirk.release(); } - const auto& textBuffer = screenInfo.GetTextBuffer(); - return WriteChars(screenInfo, - pwchBuffer, - pwchBuffer, - pwchBuffer, - pcbBuffer, - nullptr, - textBuffer.GetCursor().GetPosition().x, - 0, - nullptr); + const std::wstring_view str{ pwchBuffer, *pcbBuffer / sizeof(WCHAR) }; + + if (WI_IsAnyFlagClear(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) + { + WriteCharsLegacy(screenInfo, str, false, nullptr); + } + else + { + screenInfo.GetStateMachine().ProcessString(str); + } + + return STATUS_SUCCESS; } +NT_CATCH_RETURN() // Routine Description: // - This method performs the actual work of attempting to write to the console, converting data types as necessary diff --git a/src/host/_stream.h b/src/host/_stream.h index 58351c86cc14..794591a03f0b 100644 --- a/src/host/_stream.h +++ b/src/host/_stream.h @@ -17,76 +17,13 @@ Revision History: #pragma once -#include "../server/IWaitRoutine.h" #include "writeData.hpp" -/*++ -Routine Description: - This routine updates the cursor position. Its input is the non-special - cased new location of the cursor. For example, if the cursor were being - moved one space backwards from the left edge of the screen, the X - coordinate would be -1. This routine would set the X coordinate to - the right edge of the screen and decrement the Y coordinate by one. - -Arguments: - pScreenInfo - Pointer to screen buffer information structure. - coordCursor - New location of cursor. - fKeepCursorVisible - TRUE if changing window origin desirable when hit right edge - -Return Value: ---*/ -void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, const BOOL fKeepCursorVisible, _Inout_opt_ til::CoordType* psScrollY); - -/*++ -Routine Description: - This routine writes a string to the screen, processing any embedded - unicode characters. The string is also copied to the input buffer, if - the output mode is line mode. - -Arguments: - ScreenInfo - Pointer to screen buffer information structure. - lpBufferBackupLimit - Pointer to beginning of buffer. - lpBuffer - Pointer to buffer to copy string to. assumed to be at least - as long as lpRealUnicodeString. This pointer is updated to point to the - next position in the buffer. - lpRealUnicodeString - Pointer to string to write. - NumBytes - On input, number of bytes to write. On output, number of - bytes written. - NumSpaces - On output, the number of spaces consumed by the written characters. - dwFlags - - WC_INTERACTIVE backspace overwrites characters, control characters are expanded (as in, to "^X") - WC_KEEP_CURSOR_VISIBLE change window origin desirable when hit rt. edge - -Return Value: - -Note: - This routine does not process tabs and backspace properly. That code - will be implemented as part of the line editing services. ---*/ -[[nodiscard]] NTSTATUS WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, - _In_range_(<=, pwchBuffer) const wchar_t* const pwchBufferBackupLimit, - _In_ const wchar_t* pwchBuffer, - _In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode, - _Inout_ size_t* const pcb, - _Out_opt_ size_t* const pcSpaces, - const til::CoordType sOriginalXPosition, - const DWORD dwFlags, - _Inout_opt_ til::CoordType* const psScrollY); - -// The new entry point for WriteChars to act as an intercept in case we place a Virtual Terminal processor in the way. -[[nodiscard]] NTSTATUS WriteChars(SCREEN_INFORMATION& screenInfo, - _In_range_(<=, pwchBuffer) const wchar_t* const pwchBufferBackupLimit, - _In_ const wchar_t* pwchBuffer, - _In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode, - _Inout_ size_t* const pcb, - _Out_opt_ size_t* const pcSpaces, - const til::CoordType sOriginalXPosition, - const DWORD dwFlags, - _Inout_opt_ til::CoordType* const psScrollY); +void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str, bool interactive, til::CoordType* psScrollY); // NOTE: console lock must be held when calling this routine // String has been translated to unicode at this point. -[[nodiscard]] NTSTATUS DoWriteConsole(_In_reads_bytes_(*pcbBuffer) PCWCHAR pwchBuffer, +[[nodiscard]] NTSTATUS DoWriteConsole(_In_reads_bytes_(pcbBuffer) const wchar_t* pwchBuffer, _Inout_ size_t* const pcbBuffer, SCREEN_INFORMATION& screenInfo, bool requiresVtQuirk, diff --git a/src/host/alias.cpp b/src/host/alias.cpp index 85fc3d0875bf..a82fa1987615 100644 --- a/src/host/alias.cpp +++ b/src/host/alias.cpp @@ -817,32 +817,19 @@ void Alias::s_ClearCmdExeAliases() CATCH_RETURN(); } -// Routine Description: -// - Trims trailing \r\n off of a string -// Arguments: -// - str - String to trim -void Alias::s_TrimTrailingCrLf(std::wstring& str) -{ - const auto trailingCrLfPos = str.find_last_of(UNICODE_CARRIAGERETURN); - if (std::wstring::npos != trailingCrLfPos) - { - str.erase(trailingCrLfPos); - } -} - // Routine Description: // - Tokenizes a string into a collection using space as a separator // Arguments: // - str - String to tokenize // Return Value: // - Collection of tokenized strings -std::deque Alias::s_Tokenize(const std::wstring& str) +std::deque Alias::s_Tokenize(const std::wstring_view str) { std::deque result; size_t prevIndex = 0; auto spaceIndex = str.find(L' '); - while (std::wstring::npos != spaceIndex) + while (std::wstring_view::npos != spaceIndex) { const auto length = spaceIndex - prevIndex; @@ -867,11 +854,11 @@ std::deque Alias::s_Tokenize(const std::wstring& str) // - str - String to split into just args // Return Value: // - Only the arguments part of the string or empty if there are no arguments. -std::wstring Alias::s_GetArgString(const std::wstring& str) +std::wstring Alias::s_GetArgString(const std::wstring_view str) { std::wstring result; auto firstSpace = str.find_first_of(L' '); - if (std::wstring::npos != firstSpace) + if (std::wstring_view::npos != firstSpace) { firstSpace++; if (firstSpace < str.size()) @@ -1126,16 +1113,8 @@ size_t Alias::s_ReplaceMacros(std::wstring& str, // - If we found a matching alias, this will be the processed data // and lineCount is updated to the new number of lines. // - If we didn't match and process an alias, return an empty string. -std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText, - const std::wstring& exeName, - size_t& lineCount) +std::wstring Alias::s_MatchAndCopyAlias(std::wstring_view sourceText, const std::wstring& exeName, size_t& lineCount) { - // Copy source text into a local for manipulation. - auto sourceCopy = sourceText; - - // Trim trailing \r\n off of sourceCopy if it has one. - s_TrimTrailingCrLf(sourceCopy); - // Check if we have an EXE in the list that matches the request first. auto exeIter = g_aliasData.find(exeName); if (exeIter == g_aliasData.end()) @@ -1152,7 +1131,7 @@ std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText, } // Tokenize the text by spaces - const auto tokens = s_Tokenize(sourceCopy); + const auto tokens = s_Tokenize(sourceText); // If there are no tokens, return an empty string if (tokens.size() == 0) @@ -1169,14 +1148,14 @@ std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText, return std::wstring(); } - const auto target = aliasIter->second; + const auto& target = aliasIter->second; if (target.size() == 0) { return std::wstring(); } // Get the string of all parameters as a shorthand for $* later. - const auto allParams = s_GetArgString(sourceCopy); + const auto allParams = s_GetArgString(sourceText); // The final text will be the target but with macros replaced. auto finalText = target; @@ -1185,59 +1164,6 @@ std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText, return finalText; } -// Routine Description: -// - This routine matches the input string with an alias and copies the alias to the input buffer. -// Arguments: -// - pwchSource - string to match -// - cbSource - length of pwchSource in bytes -// - pwchTarget - where to store matched string -// - cbTargetSize - on input, contains size of pwchTarget. -// - cbTargetWritten - On output, contains length of alias stored in pwchTarget. -// - pwchExe - Name of exe that command is associated with to find related aliases -// - cbExe - Length in bytes of exe name -// - LineCount - aliases can contain multiple commands. $T is the command separator -// Return Value: -// - None. It will just maintain the source as the target if we can't match an alias. -void Alias::s_MatchAndCopyAliasLegacy(_In_reads_bytes_(cbSource) PCWCH pwchSource, - _In_ size_t cbSource, - _Out_writes_bytes_(cbTargetWritten) PWCHAR pwchTarget, - _In_ const size_t cbTargetSize, - size_t& cbTargetWritten, - const std::wstring& exeName, - DWORD& lines) -{ - try - { - std::wstring sourceText(pwchSource, cbSource / sizeof(WCHAR)); - size_t lineCount = lines; - - const auto targetText = s_MatchAndCopyAlias(sourceText, exeName, lineCount); - - // Only return data if the reply was non-empty (we had a match). - if (!targetText.empty()) - { - const auto cchTargetSize = cbTargetSize / sizeof(wchar_t); - - // If the target text will fit in the result buffer, fill out the results. - if (targetText.size() <= cchTargetSize) - { - // Non-null terminated copy into memory space - std::copy_n(targetText.data(), targetText.size(), pwchTarget); - - // Return bytes copied. - cbTargetWritten = gsl::narrow(targetText.size() * sizeof(wchar_t)); - - // Return lines info. - lines = gsl::narrow(lineCount); - } - } - } - catch (...) - { - LOG_HR(wil::ResultFromCaughtException()); - } -} - #ifdef UNIT_TESTING void Alias::s_TestAddAlias(std::wstring& exe, std::wstring& alias, diff --git a/src/host/alias.h b/src/host/alias.h index 1a745c601f77..9c8bc38d5345 100644 --- a/src/host/alias.h +++ b/src/host/alias.h @@ -16,22 +16,11 @@ class Alias public: static void s_ClearCmdExeAliases(); - static void s_MatchAndCopyAliasLegacy(_In_reads_bytes_(cbSource) PCWCH pwchSource, - _In_ size_t cbSource, - _Out_writes_bytes_(cbTargetWritten) PWCHAR pwchTarget, - _In_ const size_t cbTargetSize, - size_t& cbTargetWritten, - const std::wstring& exeName, - DWORD& lines); - - static std::wstring s_MatchAndCopyAlias(const std::wstring& sourceText, - const std::wstring& exeName, - size_t& lineCount); + static std::wstring s_MatchAndCopyAlias(std::wstring_view sourceText, const std::wstring& exeName, size_t& lineCount); private: - static void s_TrimTrailingCrLf(std::wstring& str); - static std::deque s_Tokenize(const std::wstring& str); - static std::wstring s_GetArgString(const std::wstring& str); + static std::deque s_Tokenize(const std::wstring_view str); + static std::wstring s_GetArgString(const std::wstring_view str); static size_t s_ReplaceMacros(std::wstring& str, const std::deque& tokens, const std::wstring& fullArgString); diff --git a/src/host/cmdline.cpp b/src/host/cmdline.cpp index 1e1587e37fc2..cb4f0fb6b092 100644 --- a/src/host/cmdline.cpp +++ b/src/host/cmdline.cpp @@ -4,11 +4,6 @@ #include "precomp.h" #include "cmdline.h" -#include "popup.h" -#include "CommandNumberPopup.hpp" -#include "CommandListPopup.hpp" -#include "CopyFromCharPopup.hpp" -#include "CopyToCharPopup.hpp" #include "_output.h" #include "output.h" @@ -38,1222 +33,14 @@ bool IsWordDelim(const wchar_t wch) return true; } const auto& delimiters = ServiceLocator::LocateGlobals().WordDelimiters; - return std::ranges::find(delimiters, wch) != delimiters.end(); -} - -bool IsWordDelim(const std::wstring_view charData) -{ - return charData.size() == 1 && IsWordDelim(charData.front()); -} - -CommandLine::CommandLine() : - _isVisible{ true } -{ -} - -CommandLine::~CommandLine() = default; - -CommandLine& CommandLine::Instance() -{ - static CommandLine c; - return c; -} - -bool CommandLine::IsEditLineEmpty() -{ - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - - if (!gci.HasPendingCookedRead()) - { - // If the cooked read data pointer is null, there is no edit line data and therefore it's empty. - return true; - } - else if (0 == gci.CookedReadData().VisibleCharCount()) - { - // If we had a valid pointer, but there are no visible characters for the edit line, then it's empty. - // Someone started editing and back spaced the whole line out so it exists, but has no data. - return true; - } - else + if (delimiters.empty()) { return false; } + return std::ranges::find(delimiters, wch) != delimiters.end(); } -void CommandLine::Hide(const bool fUpdateFields) -{ - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (!IsEditLineEmpty()) - { - DeleteCommandLine(gci.CookedReadData(), fUpdateFields); - } - _isVisible = false; -} - -void CommandLine::Show() -{ - _isVisible = true; - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (!IsEditLineEmpty()) - { - RedrawCommandLine(gci.CookedReadData()); - } -} - -// Routine Description: -// - Returns true if the commandline is currently being displayed. This is false -// after Hide() is called, and before Show() is called again. -// Return Value: -// - true if the commandline should be displayed. Does not take into account -// the echo state of the input. This is only controlled by calls to Hide/Show -bool CommandLine::IsVisible() const noexcept -{ - return _isVisible; -} - -// Routine Description: -// - checks for the presence of a popup -// Return Value: -// - true if popup is present -bool CommandLine::HasPopup() const noexcept -{ - return !_popups.empty(); -} - -// Routine Description: -// - gets the topmost popup -// Arguments: -// Return Value: -// - ref to the topmost popup -Popup& CommandLine::GetPopup() const -{ - return *_popups.front(); -} - -// Routine Description: -// - stops the current popup -void CommandLine::EndCurrentPopup() -{ - if (!_popups.empty()) - { - _popups.front()->End(); - _popups.pop_front(); - } -} - -// Routine Description: -// - stops all popups -void CommandLine::EndAllPopups() -{ - while (!_popups.empty()) - { - _popups.front()->End(); - _popups.pop_front(); - } -} - -void DeleteCommandLine(COOKED_READ_DATA& cookedReadData, const bool fUpdateFields) -{ - auto CharsToWrite = cookedReadData.VisibleCharCount(); - auto coordOriginalCursor = cookedReadData.OriginalCursorPosition(); - const auto coordBufferSize = cookedReadData.ScreenInfo().GetBufferSize().Dimensions(); - - // catch the case where the current command has scrolled off the top of the screen. - if (coordOriginalCursor.y < 0) - { - CharsToWrite += coordBufferSize.width * coordOriginalCursor.y; - CharsToWrite += cookedReadData.OriginalCursorPosition().x; // account for prompt - cookedReadData.OriginalCursorPosition().x = 0; - cookedReadData.OriginalCursorPosition().y = 0; - coordOriginalCursor.x = 0; - coordOriginalCursor.y = 0; - } - - if (!CheckBisectStringW(cookedReadData.BufferStartPtr(), - CharsToWrite, - coordBufferSize.width - cookedReadData.OriginalCursorPosition().x)) - { - CharsToWrite++; - } - - try - { - cookedReadData.ScreenInfo().Write(OutputCellIterator(UNICODE_SPACE, CharsToWrite), coordOriginalCursor); - } - CATCH_LOG(); - - if (fUpdateFields) - { - cookedReadData.Erase(); - } - - LOG_IF_FAILED(cookedReadData.ScreenInfo().SetCursorPosition(cookedReadData.OriginalCursorPosition(), true)); -} - -void RedrawCommandLine(COOKED_READ_DATA& cookedReadData) -{ - if (cookedReadData.IsEchoInput()) - { - // Draw the command line - cookedReadData.OriginalCursorPosition() = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - - til::CoordType ScrollY = 0; -#pragma prefast(suppress : 28931, "Status is not unused. It's used in debug assertions.") - auto Status = WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferStartPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY); - FAIL_FAST_IF_NTSTATUS_FAILED(Status); - - cookedReadData.OriginalCursorPosition().y += ScrollY; - - // Move the cursor back to the right position - auto CursorPosition = cookedReadData.OriginalCursorPosition(); - CursorPosition.x += RetrieveTotalNumberOfSpaces(cookedReadData.OriginalCursorPosition().x, - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint()); - if (CheckBisectStringW(cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint(), - cookedReadData.ScreenInfo().GetBufferSize().Width() - cookedReadData.OriginalCursorPosition().x)) - { - CursorPosition.x++; - } - AdjustCursorPosition(cookedReadData.ScreenInfo(), CursorPosition, TRUE, nullptr); - } -} - -// Routine Description: -// - This routine copies the commandline specified by Index into the cooked read buffer -void SetCurrentCommandLine(COOKED_READ_DATA& cookedReadData, _In_ CommandHistory::Index Index) // index, not command number -{ - DeleteCommandLine(cookedReadData, TRUE); - FAIL_FAST_IF_FAILED(cookedReadData.History().RetrieveNth(Index, - cookedReadData.SpanWholeBuffer(), - cookedReadData.BytesRead())); - FAIL_FAST_IF(!(cookedReadData.BufferStartPtr() == cookedReadData.BufferCurrentPtr())); - if (cookedReadData.IsEchoInput()) - { - til::CoordType ScrollY = 0; - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - cookedReadData.OriginalCursorPosition().y += ScrollY; - } - - const auto CharsToWrite = cookedReadData.BytesRead() / sizeof(WCHAR); - cookedReadData.InsertionPoint() = CharsToWrite; - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CharsToWrite); -} - -// Routine Description: -// - This routine handles the command list popup. It puts up the popup, then calls ProcessCommandListInput to get and process input. -// Return Value: -// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created -// - STATUS_SUCCESS - read was fully completed (user hit return) -[[nodiscard]] NTSTATUS CommandLine::_startCommandListPopup(COOKED_READ_DATA& cookedReadData) -{ - if (cookedReadData.HasHistory() && - cookedReadData.History().GetNumberOfCommands()) - { - try - { - auto& popup = *_popups.emplace_front(std::make_unique(cookedReadData.ScreenInfo(), - cookedReadData.History())); - popup.Draw(); - return popup.Process(cookedReadData); - } - CATCH_RETURN(); - } - else - { - return S_FALSE; - } -} - -// Routine Description: -// - This routine handles the "delete up to this char" popup. It puts up the popup, then calls ProcessCopyFromCharInput to get and process input. -// Return Value: -// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created -// - STATUS_SUCCESS - read was fully completed (user hit return) -[[nodiscard]] NTSTATUS CommandLine::_startCopyFromCharPopup(COOKED_READ_DATA& cookedReadData) -{ - // Delete the current command from cursor position to the - // letter specified by the user. The user is prompted via - // popup to enter a character. - if (cookedReadData.HasHistory()) - { - try - { - auto& popup = *_popups.emplace_front(std::make_unique(cookedReadData.ScreenInfo())); - popup.Draw(); - return popup.Process(cookedReadData); - } - CATCH_RETURN(); - } - else - { - return S_FALSE; - } -} - -// Routine Description: -// - This routine handles the "copy up to this char" popup. It puts up the popup, then calls ProcessCopyToCharInput to get and process input. -// Return Value: -// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created -// - STATUS_SUCCESS - read was fully completed (user hit return) -// - S_FALSE - if we couldn't make a popup because we had no commands -[[nodiscard]] NTSTATUS CommandLine::_startCopyToCharPopup(COOKED_READ_DATA& cookedReadData) -{ - // copy the previous command to the current command, up to but - // not including the character specified by the user. the user - // is prompted via popup to enter a character. - if (cookedReadData.HasHistory()) - { - try - { - auto& popup = *_popups.emplace_front(std::make_unique(cookedReadData.ScreenInfo())); - popup.Draw(); - return popup.Process(cookedReadData); - } - CATCH_RETURN(); - } - else - { - return S_FALSE; - } -} - -// Routine Description: -// - This routine handles the "enter command number" popup. It puts up the popup, then calls ProcessCommandNumberInput to get and process input. -// Return Value: -// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created -// - STATUS_SUCCESS - read was fully completed (user hit return) -// - S_FALSE - if we couldn't make a popup because we had no commands or it wouldn't fit. -[[nodiscard]] HRESULT CommandLine::StartCommandNumberPopup(COOKED_READ_DATA& cookedReadData) -{ - if (cookedReadData.HasHistory() && - cookedReadData.History().GetNumberOfCommands() && - cookedReadData.ScreenInfo().GetBufferSize().Width() >= Popup::MINIMUM_COMMAND_PROMPT_SIZE + 2) - { - try - { - auto& popup = *_popups.emplace_front(std::make_unique(cookedReadData.ScreenInfo())); - popup.Draw(); - - // Save the original cursor position in case the user cancels out of the dialog - cookedReadData.BeforeDialogCursorPosition() = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - - // Move the cursor into the dialog so the user can type multiple characters for the command number - const auto CursorPosition = popup.GetCursorPosition(); - LOG_IF_FAILED(cookedReadData.ScreenInfo().SetCursorPosition(CursorPosition, TRUE)); - - // Transfer control to the handler routine - return popup.Process(cookedReadData); - } - CATCH_RETURN(); - } - else - { - return S_FALSE; - } -} - -// Routine Description: -// - Process virtual key code and updates the prompt line with the next history element in the direction -// specified by wch -// Arguments: -// - cookedReadData - The cooked read data to operate on -// - searchDirection - Direction in history to search -// Note: -// - May throw exceptions -void CommandLine::_processHistoryCycling(COOKED_READ_DATA& cookedReadData, - const CommandHistory::SearchDirection searchDirection) -{ - // for doskey compatibility, buffer isn't circular. don't do anything if attempting - // to cycle history past the bounds of the history buffer - if (!cookedReadData.HasHistory()) - { - return; - } - else if (searchDirection == CommandHistory::SearchDirection::Previous && cookedReadData.History().AtFirstCommand()) - { - return; - } - else if (searchDirection == CommandHistory::SearchDirection::Next && cookedReadData.History().AtLastCommand()) - { - return; - } - - DeleteCommandLine(cookedReadData, true); - THROW_IF_FAILED(cookedReadData.History().Retrieve(searchDirection, - cookedReadData.SpanWholeBuffer(), - cookedReadData.BytesRead())); - FAIL_FAST_IF(!(cookedReadData.BufferStartPtr() == cookedReadData.BufferCurrentPtr())); - if (cookedReadData.IsEchoInput()) - { - til::CoordType ScrollY = 0; - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - cookedReadData.OriginalCursorPosition().y += ScrollY; - } - const auto CharsToWrite = cookedReadData.BytesRead() / sizeof(WCHAR); - cookedReadData.InsertionPoint() = CharsToWrite; - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CharsToWrite); -} - -// Routine Description: -// - Sets the text on the prompt to the oldest run command in the cookedReadData's history -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Note: -// - May throw exceptions -void CommandLine::_setPromptToOldestCommand(COOKED_READ_DATA& cookedReadData) -{ - if (cookedReadData.HasHistory() && cookedReadData.History().GetNumberOfCommands()) - { - DeleteCommandLine(cookedReadData, true); - const short commandNumber = 0; - THROW_IF_FAILED(cookedReadData.History().RetrieveNth(commandNumber, - cookedReadData.SpanWholeBuffer(), - cookedReadData.BytesRead())); - FAIL_FAST_IF(!(cookedReadData.BufferStartPtr() == cookedReadData.BufferCurrentPtr())); - if (cookedReadData.IsEchoInput()) - { - til::CoordType ScrollY = 0; - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - cookedReadData.OriginalCursorPosition().y += ScrollY; - } - auto CharsToWrite = cookedReadData.BytesRead() / sizeof(WCHAR); - cookedReadData.InsertionPoint() = CharsToWrite; - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CharsToWrite); - } -} - -// Routine Description: -// - Sets the text on the prompt the most recently run command in cookedReadData's history -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Note: -// - May throw exceptions -void CommandLine::_setPromptToNewestCommand(COOKED_READ_DATA& cookedReadData) -{ - DeleteCommandLine(cookedReadData, true); - if (cookedReadData.HasHistory() && cookedReadData.History().GetNumberOfCommands()) - { - const auto commandNumber = (SHORT)(cookedReadData.History().GetNumberOfCommands() - 1); - THROW_IF_FAILED(cookedReadData.History().RetrieveNth(commandNumber, - cookedReadData.SpanWholeBuffer(), - cookedReadData.BytesRead())); - FAIL_FAST_IF(!(cookedReadData.BufferStartPtr() == cookedReadData.BufferCurrentPtr())); - if (cookedReadData.IsEchoInput()) - { - til::CoordType ScrollY = 0; - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - cookedReadData.OriginalCursorPosition().y += ScrollY; - } - auto CharsToWrite = cookedReadData.BytesRead() / sizeof(WCHAR); - cookedReadData.InsertionPoint() = CharsToWrite; - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CharsToWrite); - } -} - -// Routine Description: -// - Deletes all prompt text to the right of the cursor -// Arguments: -// - cookedReadData - The cooked read data to operate on -void CommandLine::DeletePromptAfterCursor(COOKED_READ_DATA& cookedReadData) noexcept -{ - DeleteCommandLine(cookedReadData, false); - cookedReadData.BytesRead() = cookedReadData.InsertionPoint() * sizeof(WCHAR); - if (cookedReadData.IsEchoInput()) - { - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferStartPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - nullptr)); - } -} - -// Routine Description: -// - Deletes all user input on the prompt to the left of the cursor -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - The new cursor position -til::point CommandLine::_deletePromptBeforeCursor(COOKED_READ_DATA& cookedReadData) noexcept -{ - DeleteCommandLine(cookedReadData, false); - cookedReadData.BytesRead() -= cookedReadData.InsertionPoint() * sizeof(WCHAR); - cookedReadData.InsertionPoint() = 0; - memmove(cookedReadData.BufferStartPtr(), cookedReadData.BufferCurrentPtr(), cookedReadData.BytesRead()); - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr()); - if (cookedReadData.IsEchoInput()) - { - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferStartPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - nullptr)); - } - return cookedReadData.OriginalCursorPosition(); -} - -// Routine Description: -// - Moves the cursor to the end of the prompt text -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - The new cursor position -til::point CommandLine::_moveCursorToEndOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept -{ - cookedReadData.InsertionPoint() = cookedReadData.BytesRead() / sizeof(WCHAR); - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + cookedReadData.InsertionPoint()); - til::point cursorPosition; - cursorPosition.x = gsl::narrow(cookedReadData.OriginalCursorPosition().x + cookedReadData.VisibleCharCount()); - cursorPosition.y = cookedReadData.OriginalCursorPosition().y; - - const auto sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width(); - if (CheckBisectProcessW(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint(), - sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().x, - cookedReadData.OriginalCursorPosition().x, - true)) - { - cursorPosition.x++; - } - return cursorPosition; -} - -// Routine Description: -// - Moves the cursor to the start of the user input on the prompt -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - The new cursor position -til::point CommandLine::_moveCursorToStartOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept -{ - cookedReadData.InsertionPoint() = 0; - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr()); - return cookedReadData.OriginalCursorPosition(); -} - -// Routine Description: -// - Moves the cursor left by a word -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - New cursor position -til::point CommandLine::_moveCursorLeftByWord(COOKED_READ_DATA& cookedReadData) noexcept -{ - PWCHAR LastWord; - auto cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - if (cookedReadData.BufferCurrentPtr() != cookedReadData.BufferStartPtr()) - { - // A bit better word skipping. - LastWord = cookedReadData.BufferCurrentPtr() - 1; - if (LastWord != cookedReadData.BufferStartPtr()) - { - if (*LastWord == L' ') - { - // Skip spaces, until the non-space character is found. - while (--LastWord != cookedReadData.BufferStartPtr()) - { - FAIL_FAST_IF(!(LastWord > cookedReadData.BufferStartPtr())); - if (*LastWord != L' ') - { - break; - } - } - } - if (LastWord != cookedReadData.BufferStartPtr()) - { - if (IsWordDelim(*LastWord)) - { - // Skip WORD_DELIMs until space or non WORD_DELIM is found. - while (--LastWord != cookedReadData.BufferStartPtr()) - { - FAIL_FAST_IF(!(LastWord > cookedReadData.BufferStartPtr())); - if (*LastWord == L' ' || !IsWordDelim(*LastWord)) - { - break; - } - } - } - else - { - // Skip the regular words - while (--LastWord != cookedReadData.BufferStartPtr()) - { - FAIL_FAST_IF(!(LastWord > cookedReadData.BufferStartPtr())); - if (IsWordDelim(*LastWord)) - { - break; - } - } - } - } - FAIL_FAST_IF(!(LastWord >= cookedReadData.BufferStartPtr())); - if (LastWord != cookedReadData.BufferStartPtr()) - { - // LastWord is currently pointing to the last character - // of the previous word, unless it backed up to the beginning - // of the buffer. - // Let's increment LastWord so that it points to the expected - // insertion point. - ++LastWord; - } - cookedReadData.SetBufferCurrentPtr(LastWord); - } - cookedReadData.InsertionPoint() = (cookedReadData.BufferCurrentPtr() - cookedReadData.BufferStartPtr()); - cursorPosition = cookedReadData.OriginalCursorPosition(); - cursorPosition.x = cursorPosition.x + - RetrieveTotalNumberOfSpaces(cookedReadData.OriginalCursorPosition().x, - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint()); - const auto sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width(); - if (CheckBisectStringW(cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint() + 1, - sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().x)) - { - cursorPosition.x++; - } - } - return cursorPosition; -} - -// Routine Description: -// - Moves cursor left by a glyph -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - New cursor position -til::point CommandLine::_moveCursorLeft(COOKED_READ_DATA& cookedReadData) -{ - auto cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - if (cookedReadData.BufferCurrentPtr() != cookedReadData.BufferStartPtr()) - { - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferCurrentPtr() - 1); - cookedReadData.InsertionPoint()--; - cursorPosition.x = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition().x; - cursorPosition.y = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition().y; - cursorPosition.x = cursorPosition.x - - RetrieveNumberOfSpaces(cookedReadData.OriginalCursorPosition().x, - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint()); - const auto sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width(); - if (CheckBisectProcessW(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint() + 2, - sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().x, - cookedReadData.OriginalCursorPosition().x, - true)) - { - if ((cursorPosition.x == -2) || (cursorPosition.x == -1)) - { - cursorPosition.x--; - } - } - } - return cursorPosition; -} - -// Routine Description: -// - Moves the cursor to the right by a word -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - The new cursor position -til::point CommandLine::_moveCursorRightByWord(COOKED_READ_DATA& cookedReadData) noexcept -{ - auto cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - if (cookedReadData.InsertionPoint() < (cookedReadData.BytesRead() / sizeof(WCHAR))) - { - auto NextWord = cookedReadData.BufferCurrentPtr(); - - // A bit better word skipping. - auto BufLast = cookedReadData.BufferStartPtr() + cookedReadData.BytesRead() / sizeof(WCHAR); - - FAIL_FAST_IF(!(NextWord < BufLast)); - if (*NextWord == L' ') - { - // If the current character is space, skip to the next non-space character. - while (++NextWord < BufLast) - { - if (*NextWord != L' ') - { - break; - } - } - } - else - { - // Skip the body part. - auto fStartFromDelim = IsWordDelim(*NextWord); - - while (++NextWord < BufLast) - { - if (fStartFromDelim != IsWordDelim(*NextWord)) - { - break; - } - } - - // Skip the space block. - for (; NextWord < BufLast; NextWord++) - { - if (*NextWord != L' ') - { - break; - } - } - } - - cookedReadData.SetBufferCurrentPtr(NextWord); - cookedReadData.InsertionPoint() = (ULONG)(cookedReadData.BufferCurrentPtr() - cookedReadData.BufferStartPtr()); - cursorPosition = cookedReadData.OriginalCursorPosition(); - cursorPosition.x = cursorPosition.x + - RetrieveTotalNumberOfSpaces(cookedReadData.OriginalCursorPosition().x, - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint()); - const auto sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width(); - if (CheckBisectStringW(cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint() + 1, - sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().x)) - { - cursorPosition.x++; - } - } - return cursorPosition; -} - -// Routine Description: -// - Moves the cursor to the right by a glyph -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - The new cursor position -til::point CommandLine::_moveCursorRight(COOKED_READ_DATA& cookedReadData) noexcept -{ - auto cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - const auto sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width(); - // If not at the end of the line, move cursor position right. - if (cookedReadData.InsertionPoint() < (cookedReadData.BytesRead() / sizeof(WCHAR))) - { - cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - cursorPosition.x = cursorPosition.x + - RetrieveNumberOfSpaces(cookedReadData.OriginalCursorPosition().x, - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint()); - if (CheckBisectProcessW(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint() + 2, - sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().x, - cookedReadData.OriginalCursorPosition().x, - true)) - { - // Snap cursorPosition.x to sScreenBufferSizeX if it is at the edge of the screen - if (cursorPosition.x == (sScreenBufferSizeX - 1)) - cursorPosition.x = sScreenBufferSizeX; - } - - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferCurrentPtr() + 1); - cookedReadData.InsertionPoint()++; - } - // if at the end of the line, copy a character from the same position in the last command - else if (cookedReadData.HasHistory()) - { - size_t NumSpaces; - const auto LastCommand = cookedReadData.History().GetLastCommand(); - if (!LastCommand.empty() && LastCommand.size() > cookedReadData.InsertionPoint()) - { - *cookedReadData.BufferCurrentPtr() = LastCommand[cookedReadData.InsertionPoint()]; - cookedReadData.BytesRead() += sizeof(WCHAR); - cookedReadData.InsertionPoint()++; - if (cookedReadData.IsEchoInput()) - { - til::CoordType ScrollY = 0; - auto CharsToWrite = sizeof(WCHAR); - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr(), - &CharsToWrite, - &NumSpaces, - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - cookedReadData.OriginalCursorPosition().y += ScrollY; - cookedReadData.VisibleCharCount() += NumSpaces; - // update reported cursor position - if (ScrollY != 0) - { - cursorPosition.x = 0; - cursorPosition.y += ScrollY; - } - else - { - cursorPosition.x += 1; - } - } - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferCurrentPtr() + 1); - } - } - return cursorPosition; -} - -// Routine Description: -// - Place a ctrl-z in the current command line -// Arguments: -// - cookedReadData - The cooked read data to operate on -void CommandLine::_insertCtrlZ(COOKED_READ_DATA& cookedReadData) noexcept -{ - size_t NumSpaces = 0; - - *cookedReadData.BufferCurrentPtr() = (WCHAR)0x1a; // ctrl-z - cookedReadData.BytesRead() += sizeof(WCHAR); - cookedReadData.InsertionPoint()++; - if (cookedReadData.IsEchoInput()) - { - til::CoordType ScrollY = 0; - auto CharsToWrite = sizeof(WCHAR); - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr(), - &CharsToWrite, - &NumSpaces, - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - cookedReadData.OriginalCursorPosition().y += ScrollY; - cookedReadData.VisibleCharCount() += NumSpaces; - } - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferCurrentPtr() + 1); -} - -// Routine Description: -// - Empties the command history for cookedReadData -// Arguments: -// - cookedReadData - The cooked read data to operate on -void CommandLine::_deleteCommandHistory(COOKED_READ_DATA& cookedReadData) noexcept -{ - if (cookedReadData.HasHistory()) - { - cookedReadData.History().Empty(); - cookedReadData.History().Flags |= CommandHistory::CLE_ALLOCATED; - } -} - -// Routine Description: -// - Copy the remainder of the previous command to the current command. -// Arguments: -// - cookedReadData - The cooked read data to operate on -void CommandLine::_fillPromptWithPreviousCommandFragment(COOKED_READ_DATA& cookedReadData) noexcept -{ - if (cookedReadData.HasHistory()) - { - size_t NumSpaces, cchCount; - - const auto LastCommand = cookedReadData.History().GetLastCommand(); - if (!LastCommand.empty() && LastCommand.size() > cookedReadData.InsertionPoint()) - { - cchCount = LastCommand.size() - cookedReadData.InsertionPoint(); - const auto bufferSpan = cookedReadData.SpanAtPointer(); - std::copy_n(LastCommand.cbegin() + cookedReadData.InsertionPoint(), cchCount, bufferSpan.begin()); - cookedReadData.InsertionPoint() += cchCount; - cchCount *= sizeof(WCHAR); - cookedReadData.BytesRead() = std::max(LastCommand.size() * sizeof(wchar_t), cookedReadData.BytesRead()); - if (cookedReadData.IsEchoInput()) - { - til::CoordType ScrollY = 0; - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr(), - &cchCount, - &NumSpaces, - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - cookedReadData.OriginalCursorPosition().y += ScrollY; - cookedReadData.VisibleCharCount() += NumSpaces; - } - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferCurrentPtr() + cchCount / sizeof(WCHAR)); - } - } -} - -// Routine Description: -// - Cycles through the stored commands that start with the characters in the current command. -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - The new cursor position -til::point CommandLine::_cycleMatchingCommandHistoryToPrompt(COOKED_READ_DATA& cookedReadData) -{ - auto cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - if (cookedReadData.HasHistory()) - { - CommandHistory::Index index; - if (cookedReadData.History().FindMatchingCommand({ cookedReadData.BufferStartPtr(), cookedReadData.InsertionPoint() }, - cookedReadData.History().LastDisplayed, - index, - CommandHistory::MatchOptions::None)) - { - // save cursor position - const auto CurrentPos = cookedReadData.InsertionPoint(); - - DeleteCommandLine(cookedReadData, true); - THROW_IF_FAILED(cookedReadData.History().RetrieveNth(index, - cookedReadData.SpanWholeBuffer(), - cookedReadData.BytesRead())); - FAIL_FAST_IF(!(cookedReadData.BufferStartPtr() == cookedReadData.BufferCurrentPtr())); - if (cookedReadData.IsEchoInput()) - { - til::CoordType ScrollY = 0; - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - cookedReadData.OriginalCursorPosition().y += ScrollY; - cursorPosition.y += ScrollY; - } - - // restore cursor position - cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CurrentPos); - cookedReadData.InsertionPoint() = CurrentPos; - FAIL_FAST_IF_NTSTATUS_FAILED(cookedReadData.ScreenInfo().SetCursorPosition(cursorPosition, true)); - } - } - return cursorPosition; -} - -// Routine Description: -// - Deletes a glyph from the right side of the cursor -// Arguments: -// - cookedReadData - The cooked read data to operate on -// Return Value: -// - The new cursor position -til::point CommandLine::DeleteFromRightOfCursor(COOKED_READ_DATA& cookedReadData) noexcept -{ - // save cursor position - auto cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - - if (!cookedReadData.AtEol()) - { - // Delete commandline. - // clang-format off -#pragma prefast(suppress: __WARNING_BUFFER_OVERFLOW, "Not sure why prefast is getting confused here") - // clang-format on - DeleteCommandLine(cookedReadData, false); - - // Delete char. - cookedReadData.BytesRead() -= sizeof(WCHAR); - memmove(cookedReadData.BufferCurrentPtr(), - cookedReadData.BufferCurrentPtr() + 1, - cookedReadData.BytesRead() - (cookedReadData.InsertionPoint() * sizeof(WCHAR))); - - { - auto buf = (PWCHAR)((PBYTE)cookedReadData.BufferStartPtr() + cookedReadData.BytesRead()); - *buf = (WCHAR)' '; - } - - // Write commandline. - if (cookedReadData.IsEchoInput()) - { - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferStartPtr(), - cookedReadData.BufferStartPtr(), - &cookedReadData.BytesRead(), - &cookedReadData.VisibleCharCount(), - cookedReadData.OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - nullptr)); - } - - // restore cursor position - const auto sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width(); - if (CheckBisectProcessW(cookedReadData.ScreenInfo(), - cookedReadData.BufferStartPtr(), - cookedReadData.InsertionPoint() + 1, - sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().x, - cookedReadData.OriginalCursorPosition().x, - true)) - { - cursorPosition.x++; - } - } - return cursorPosition; -} - -// TODO: [MSFT:4586207] Clean up this mess -- needs helpers. http://osgvsowi/4586207 -// Routine Description: -// - This routine process command line editing keys. -// Return Value: -// - CONSOLE_STATUS_WAIT - CommandListPopup ran out of input -// - CONSOLE_STATUS_READ_COMPLETE - user hit in CommandListPopup -// - STATUS_SUCCESS - everything's cool -[[nodiscard]] NTSTATUS CommandLine::ProcessCommandLine(COOKED_READ_DATA& cookedReadData, - _In_ WCHAR wch, - const DWORD dwKeyState) +bool IsWordDelim(const std::wstring_view charData) { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - auto cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition(); - NTSTATUS Status; - - const auto altPressed = WI_IsAnyFlagSet(dwKeyState, LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); - const auto ctrlPressed = WI_IsAnyFlagSet(dwKeyState, LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); - auto UpdateCursorPosition = false; - switch (wch) - { - case VK_ESCAPE: - DeleteCommandLine(cookedReadData, true); - break; - case VK_DOWN: - try - { - _processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next); - Status = STATUS_SUCCESS; - } - catch (...) - { - Status = wil::ResultFromCaughtException(); - } - break; - case VK_UP: - case VK_F5: - try - { - _processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous); - Status = STATUS_SUCCESS; - } - catch (...) - { - Status = wil::ResultFromCaughtException(); - } - break; - case VK_PRIOR: - try - { - _setPromptToOldestCommand(cookedReadData); - Status = STATUS_SUCCESS; - } - catch (...) - { - Status = wil::ResultFromCaughtException(); - } - break; - case VK_NEXT: - try - { - _setPromptToNewestCommand(cookedReadData); - Status = STATUS_SUCCESS; - } - catch (...) - { - Status = wil::ResultFromCaughtException(); - } - break; - case VK_END: - if (ctrlPressed) - { - DeletePromptAfterCursor(cookedReadData); - } - else - { - cursorPosition = _moveCursorToEndOfPrompt(cookedReadData); - UpdateCursorPosition = true; - } - break; - case VK_HOME: - if (ctrlPressed) - { - cursorPosition = _deletePromptBeforeCursor(cookedReadData); - UpdateCursorPosition = true; - } - else - { - cursorPosition = _moveCursorToStartOfPrompt(cookedReadData); - UpdateCursorPosition = true; - } - break; - case VK_LEFT: - if (ctrlPressed) - { - cursorPosition = _moveCursorLeftByWord(cookedReadData); - UpdateCursorPosition = true; - } - else - { - cursorPosition = _moveCursorLeft(cookedReadData); - UpdateCursorPosition = true; - } - break; - case VK_F1: - { - // we don't need to check for end of buffer here because we've - // already done it. - cursorPosition = _moveCursorRight(cookedReadData); - UpdateCursorPosition = true; - break; - } - case VK_RIGHT: - // we don't need to check for end of buffer here because we've - // already done it. - if (ctrlPressed) - { - cursorPosition = _moveCursorRightByWord(cookedReadData); - UpdateCursorPosition = true; - } - else - { - cursorPosition = _moveCursorRight(cookedReadData); - UpdateCursorPosition = true; - } - break; - case VK_F2: - { - Status = _startCopyToCharPopup(cookedReadData); - if (S_FALSE == Status) - { - // We couldn't make the popup, so loop around and read the next character. - break; - } - else - { - return Status; - } - } - case VK_F3: - _fillPromptWithPreviousCommandFragment(cookedReadData); - break; - case VK_F4: - { - Status = _startCopyFromCharPopup(cookedReadData); - if (S_FALSE == Status) - { - // We couldn't display a popup. Go around a loop behind. - break; - } - else - { - return Status; - } - } - case VK_F6: - { - _insertCtrlZ(cookedReadData); - break; - } - case VK_F7: - if (!ctrlPressed && !altPressed) - { - Status = _startCommandListPopup(cookedReadData); - } - else if (altPressed) - { - _deleteCommandHistory(cookedReadData); - } - break; - - case VK_F8: - try - { - cursorPosition = _cycleMatchingCommandHistoryToPrompt(cookedReadData); - UpdateCursorPosition = true; - } - catch (...) - { - Status = wil::ResultFromCaughtException(); - } - break; - case VK_F9: - { - Status = StartCommandNumberPopup(cookedReadData); - if (S_FALSE == Status) - { - // If we couldn't make the popup, break and go around to read another input character. - break; - } - else - { - return Status; - } - } - case VK_F10: - // Alt+F10 clears the aliases for specifically cmd.exe. - if (altPressed) - { - Alias::s_ClearCmdExeAliases(); - } - break; - case VK_INSERT: - cookedReadData.SetInsertMode(!cookedReadData.IsInsertMode()); - cookedReadData.ScreenInfo().SetCursorDBMode(cookedReadData.IsInsertMode() != gci.GetInsertMode()); - break; - case VK_DELETE: - cursorPosition = DeleteFromRightOfCursor(cookedReadData); - UpdateCursorPosition = true; - break; - default: - FAIL_FAST_HR(E_NOTIMPL); - break; - } - - if (UpdateCursorPosition && cookedReadData.IsEchoInput()) - { - AdjustCursorPosition(cookedReadData.ScreenInfo(), cursorPosition, true, nullptr); - } - - return STATUS_SUCCESS; + return charData.size() == 1 && IsWordDelim(charData.front()); } diff --git a/src/host/cmdline.h b/src/host/cmdline.h index a8a5d19ca5b0..1ce6ae712c51 100644 --- a/src/host/cmdline.h +++ b/src/host/cmdline.h @@ -3,88 +3,8 @@ #pragma once -#include "input.h" #include "screenInfo.hpp" -#include "server.h" - -#include "history.h" -#include "alias.h" -#include "readDataCooked.hpp" -#include "popup.h" - -class CommandLine -{ -public: - ~CommandLine(); - - static CommandLine& Instance(); - - static bool IsEditLineEmpty(); - void Hide(const bool fUpdateFields); - void Show(); - bool IsVisible() const noexcept; - - [[nodiscard]] NTSTATUS ProcessCommandLine(COOKED_READ_DATA& cookedReadData, - _In_ WCHAR wch, - const DWORD dwKeyState); - - [[nodiscard]] HRESULT StartCommandNumberPopup(COOKED_READ_DATA& cookedReadData); - - bool HasPopup() const noexcept; - Popup& GetPopup() const; - - void EndCurrentPopup(); - void EndAllPopups(); - - void DeletePromptAfterCursor(COOKED_READ_DATA& cookedReadData) noexcept; - til::point DeleteFromRightOfCursor(COOKED_READ_DATA& cookedReadData) noexcept; - -protected: - CommandLine(); - - // delete these because we don't want to accidentally get copies of the singleton - CommandLine(const CommandLine&) = delete; - CommandLine& operator=(const CommandLine&) = delete; - - [[nodiscard]] NTSTATUS _startCommandListPopup(COOKED_READ_DATA& cookedReadData); - [[nodiscard]] NTSTATUS _startCopyFromCharPopup(COOKED_READ_DATA& cookedReadData); - [[nodiscard]] NTSTATUS _startCopyToCharPopup(COOKED_READ_DATA& cookedReadData); - - void _processHistoryCycling(COOKED_READ_DATA& cookedReadData, const CommandHistory::SearchDirection searchDirection); - void _setPromptToOldestCommand(COOKED_READ_DATA& cookedReadData); - void _setPromptToNewestCommand(COOKED_READ_DATA& cookedReadData); - til::point _deletePromptBeforeCursor(COOKED_READ_DATA& cookedReadData) noexcept; - til::point _moveCursorToEndOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept; - til::point _moveCursorToStartOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept; - til::point _moveCursorLeftByWord(COOKED_READ_DATA& cookedReadData) noexcept; - til::point _moveCursorLeft(COOKED_READ_DATA& cookedReadData); - til::point _moveCursorRightByWord(COOKED_READ_DATA& cookedReadData) noexcept; - til::point _moveCursorRight(COOKED_READ_DATA& cookedReadData) noexcept; - void _insertCtrlZ(COOKED_READ_DATA& cookedReadData) noexcept; - void _deleteCommandHistory(COOKED_READ_DATA& cookedReadData) noexcept; - void _fillPromptWithPreviousCommandFragment(COOKED_READ_DATA& cookedReadData) noexcept; - til::point _cycleMatchingCommandHistoryToPrompt(COOKED_READ_DATA& cookedReadData); - -#ifdef UNIT_TESTING - friend class CommandLineTests; - friend class CommandNumberPopupTests; -#endif - -private: - std::deque> _popups; - bool _isVisible; -}; - -void DeleteCommandLine(COOKED_READ_DATA& cookedReadData, const bool fUpdateFields); - -void RedrawCommandLine(COOKED_READ_DATA& cookedReadData); - -// Values for WriteChars(), WriteCharsLegacy() dwFlags -#define WC_INTERACTIVE 0x01 -#define WC_KEEP_CURSOR_VISIBLE 0x02 // Word delimiters bool IsWordDelim(const wchar_t wch); bool IsWordDelim(const std::wstring_view charData); - -void SetCurrentCommandLine(COOKED_READ_DATA& cookedReadData, _In_ CommandHistory::Index Index); diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 0b5b6db272ee..4722cebe2b94 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -130,6 +130,11 @@ bool CONSOLE_INFORMATION::HasPendingCookedRead() const noexcept return _cookedReadData != nullptr; } +bool CONSOLE_INFORMATION::HasPendingPopup() const noexcept +{ + return _cookedReadData && _cookedReadData->PresentingPopup(); +} + const COOKED_READ_DATA& CONSOLE_INFORMATION::CookedReadData() const noexcept { return *_cookedReadData; diff --git a/src/host/ft_fuzzer/fuzzmain.cpp b/src/host/ft_fuzzer/fuzzmain.cpp index 4b8f04dea6e4..cd3708d59ed6 100644 --- a/src/host/ft_fuzzer/fuzzmain.cpp +++ b/src/host/ft_fuzzer/fuzzmain.cpp @@ -3,15 +3,13 @@ #include "precomp.h" +#include + #include "../ConsoleArguments.hpp" #include "../srvinit.h" -#include "../../server/Entrypoints.h" +#include "../_stream.h" #include "../../interactivity/inc/ServiceLocator.hpp" -#include "../../server/DeviceHandle.h" #include "../../server/IoThread.h" -#include "../_stream.h" -#include "../getset.h" -#include struct NullDeviceComm : public IDeviceComm { @@ -132,17 +130,8 @@ extern "C" __declspec(dllexport) int LLVMFuzzerTestOneInput(const uint8_t* data, const auto u16String{ til::u8u16(std::string_view{ reinterpret_cast(data), size }) }; til::CoordType scrollY{}; - auto sizeInBytes{ u16String.size() * 2 }; gci.LockConsole(); auto u = wil::scope_exit([&]() { gci.UnlockConsole(); }); - (void)WriteCharsLegacy(gci.GetActiveOutputBuffer(), - u16String.data(), - u16String.data(), - u16String.data(), - &sizeInBytes, - nullptr, - 0, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &scrollY); + WriteCharsLegacy(gci.GetActiveOutputBuffer(), u16String, true, &scrollY); return 0; } diff --git a/src/host/getset.cpp b/src/host/getset.cpp index f2cd485842f0..5c2954f4fa42 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -598,13 +598,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont const auto requestedBufferSize = til::wrap_coord_size(data.dwSize); if (requestedBufferSize != coordScreenBufferSize) { - auto& commandLine = CommandLine::Instance(); - - commandLine.Hide(FALSE); - LOG_IF_FAILED(context.ResizeScreenBuffer(requestedBufferSize, TRUE)); - - commandLine.Show(); } const auto newBufferSize = context.GetBufferSize().Dimensions(); @@ -655,15 +649,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont if (NewSize.width != context.GetViewport().Width() || NewSize.height != context.GetViewport().Height()) { - // GH#1856 - make sure to hide the commandline _before_ we execute - // the resize, and the re-display it after the resize. If we leave - // it displayed, we'll crash during the resize when we try to figure - // out if the bounds of the old commandline fit within the new - // window (it might not). - auto& commandLine = CommandLine::Instance(); - commandLine.Hide(FALSE); context.SetViewportSize(&NewSize); - commandLine.Show(); const auto pWindow = ServiceLocator::LocateConsoleWindow(); if (pWindow != nullptr) diff --git a/src/host/history.cpp b/src/host/history.cpp index 1d80100c4b77..36064d19b045 100644 --- a/src/host/history.cpp +++ b/src/host/history.cpp @@ -167,47 +167,9 @@ const std::vector& CommandHistory::GetCommands() const noexcept return _commands; } -[[nodiscard]] HRESULT CommandHistory::RetrieveNth(const Index index, std::span buffer, size_t& commandSize) +std::wstring_view CommandHistory::Retrieve(const SearchDirection searchDirection) { - LastDisplayed = index; - - try - { - const auto& cmd = _commands.at(index); - if (cmd.size() > buffer.size()) - { - commandSize = buffer.size(); // room for CRLF? - } - else - { - commandSize = cmd.size(); - } - - std::copy_n(cmd.cbegin(), commandSize, buffer.begin()); - - commandSize *= sizeof(wchar_t); - - return S_OK; - } - CATCH_RETURN(); -} - -[[nodiscard]] HRESULT CommandHistory::Retrieve(const SearchDirection searchDirection, - const std::span buffer, - size_t& commandSize) -{ - FAIL_FAST_IF(!(WI_IsFlagSet(Flags, CLE_ALLOCATED))); - - if (_commands.size() == 0) - { - return E_FAIL; - } - - if (_commands.size() == 1) - { - LastDisplayed = 0; - } - else if (searchDirection == SearchDirection::Previous) + if (searchDirection == SearchDirection::Previous) { // if this is the first time for this read that a command has // been retrieved, return the current command. otherwise, return @@ -218,15 +180,27 @@ const std::vector& CommandHistory::GetCommands() const noexcept } else { - _Prev(LastDisplayed); + LastDisplayed--; } } else { - _Next(LastDisplayed); + LastDisplayed++; + } + + return RetrieveNth(LastDisplayed); +} + +std::wstring_view CommandHistory::RetrieveNth(Index index) +{ + if (_commands.empty()) + { + LastDisplayed = 0; + return {}; } - return RetrieveNth(LastDisplayed, buffer, commandSize); + LastDisplayed = std::clamp(index, 0, GetNumberOfCommands() - 1); + return _commands.at(LastDisplayed); } std::wstring_view CommandHistory::GetLastCommand() const diff --git a/src/host/history.h b/src/host/history.h index 07b1248edb8f..4fa1ce2c5601 100644 --- a/src/host/history.h +++ b/src/host/history.h @@ -43,13 +43,8 @@ class CommandHistory [[nodiscard]] HRESULT Add(const std::wstring_view command, const bool suppressDuplicates); - [[nodiscard]] HRESULT Retrieve(const SearchDirection searchDirection, - const std::span buffer, - size_t& commandSize); - - [[nodiscard]] HRESULT RetrieveNth(const Index index, - const std::span buffer, - size_t& commandSize); + std::wstring_view Retrieve(const SearchDirection searchDirection); + std::wstring_view RetrieveNth(Index index); Index GetNumberOfCommands() const; std::wstring_view GetNth(Index index) const; diff --git a/src/host/host-common.vcxitems b/src/host/host-common.vcxitems index 3ff022e82cf8..ee482e31a93d 100644 --- a/src/host/host-common.vcxitems +++ b/src/host/host-common.vcxitems @@ -3,10 +3,6 @@ - - - - @@ -29,7 +25,6 @@ - Create @@ -64,10 +59,6 @@ - - - - @@ -90,7 +81,6 @@ - diff --git a/src/host/input.cpp b/src/host/input.cpp index 94e58d1407a5..5f0aa0db1846 100644 --- a/src/host/input.cpp +++ b/src/host/input.cpp @@ -118,7 +118,7 @@ void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak) if (keyEvent.GetVirtualKeyCode() == 'C' && IsInProcessedInputMode()) { HandleCtrlEvent(CTRL_C_EVENT); - if (gci.PopupCount == 0) + if (!gci.HasPendingPopup()) { gci.pInputBuffer->TerminateRead(WaitTerminationReason::CtrlC); } @@ -134,7 +134,7 @@ void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak) { gci.pInputBuffer->Flush(); HandleCtrlEvent(CTRL_BREAK_EVENT); - if (gci.PopupCount == 0) + if (!gci.HasPendingPopup()) { gci.pInputBuffer->TerminateRead(WaitTerminationReason::CtrlBreak); } diff --git a/src/host/lib/hostlib.vcxproj.filters b/src/host/lib/hostlib.vcxproj.filters index fb42149b626d..c108f2d14119 100644 --- a/src/host/lib/hostlib.vcxproj.filters +++ b/src/host/lib/hostlib.vcxproj.filters @@ -159,21 +159,6 @@ Source Files - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - Source Files @@ -329,21 +314,6 @@ Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files diff --git a/src/host/misc.cpp b/src/host/misc.cpp index 531ec2a9a630..2e054e082693 100644 --- a/src/host/misc.cpp +++ b/src/host/misc.cpp @@ -47,140 +47,6 @@ void SetConsoleCPInfo(const BOOL fOutput) } } -// Routine Description: -// - This routine check bisected on Unicode string end. -// Arguments: -// - pwchBuffer - Pointer to Unicode string buffer. -// - cWords - Number of Unicode string. -// - cBytes - Number of bisect position by byte counts. -// Return Value: -// - TRUE - Bisected character. -// - FALSE - Correctly. -BOOL CheckBisectStringW(_In_reads_bytes_(cBytes) const WCHAR* pwchBuffer, - _In_ size_t cWords, - _In_ size_t cBytes) noexcept -{ - while (cWords && cBytes) - { - if (IsGlyphFullWidth(*pwchBuffer)) - { - if (cBytes < 2) - { - return TRUE; - } - else - { - cWords--; - cBytes -= 2; - pwchBuffer++; - } - } - else - { - cWords--; - cBytes--; - pwchBuffer++; - } - } - - return FALSE; -} - -// Routine Description: -// - This routine check bisected on Unicode string end. -// Arguments: -// - ScreenInfo - reference to screen information structure. -// - pwchBuffer - Pointer to Unicode string buffer. -// - cWords - Number of Unicode string. -// - cBytes - Number of bisect position by byte counts. -// - fPrintableControlChars - TRUE if control characters are being expanded (to ^X) -// Return Value: -// - TRUE - Bisected character. -// - FALSE - Correctly. -BOOL CheckBisectProcessW(const SCREEN_INFORMATION& ScreenInfo, - _In_reads_bytes_(cBytes) const WCHAR* pwchBuffer, - _In_ size_t cWords, - _In_ size_t cBytes, - _In_ til::CoordType sOriginalXPosition, - _In_ BOOL fPrintableControlChars) -{ - if (WI_IsFlagSet(ScreenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)) - { - while (cWords && cBytes) - { - const auto Char = *pwchBuffer; - if (Char >= UNICODE_SPACE) - { - if (IsGlyphFullWidth(Char)) - { - if (cBytes < 2) - { - return TRUE; - } - else - { - cWords--; - cBytes -= 2; - pwchBuffer++; - sOriginalXPosition += 2; - } - } - else - { - cWords--; - cBytes--; - pwchBuffer++; - sOriginalXPosition++; - } - } - else - { - cWords--; - pwchBuffer++; - switch (Char) - { - case UNICODE_BELL: - if (fPrintableControlChars) - goto CtrlChar; - break; - case UNICODE_BACKSPACE: - case UNICODE_LINEFEED: - case UNICODE_CARRIAGERETURN: - break; - case UNICODE_TAB: - { - size_t TabSize = NUMBER_OF_SPACES_IN_TAB(sOriginalXPosition); - sOriginalXPosition = (til::CoordType)(sOriginalXPosition + TabSize); - if (cBytes < TabSize) - return TRUE; - cBytes -= TabSize; - break; - } - default: - if (fPrintableControlChars) - { - CtrlChar: - if (cBytes < 2) - return TRUE; - cBytes -= 2; - sOriginalXPosition += 2; - } - else - { - cBytes--; - sOriginalXPosition++; - } - } - } - } - return FALSE; - } - else - { - return CheckBisectStringW(pwchBuffer, cWords, cBytes); - } -} - // Routine Description: // - Converts unicode characters to ANSI given a destination codepage // Arguments: diff --git a/src/host/misc.h b/src/host/misc.h index ca90ac23ac83..3069bd576046 100644 --- a/src/host/misc.h +++ b/src/host/misc.h @@ -28,16 +28,6 @@ WCHAR CharToWchar(_In_reads_(cch) const char* const pch, const UINT cch); void SetConsoleCPInfo(const BOOL fOutput); -BOOL CheckBisectStringW(_In_reads_bytes_(cBytes) const WCHAR* pwchBuffer, - _In_ size_t cWords, - _In_ size_t cBytes) noexcept; -BOOL CheckBisectProcessW(const SCREEN_INFORMATION& ScreenInfo, - _In_reads_bytes_(cBytes) const WCHAR* pwchBuffer, - _In_ size_t cWords, - _In_ size_t cBytes, - _In_ til::CoordType sOriginalXPosition, - _In_ BOOL fPrintableControlChars); - int ConvertToOem(const UINT uiCodePage, _In_reads_(cchSource) const WCHAR* const pwchSource, const UINT cchSource, diff --git a/src/host/popup.cpp b/src/host/popup.cpp deleted file mode 100644 index b91376eb6794..000000000000 --- a/src/host/popup.cpp +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "popup.h" - -#include "_output.h" -#include "output.h" - -#include "dbcs.h" -#include "srvinit.h" -#include "stream.h" - -#include "resource.h" - -#include "utils.hpp" - -#include "../interactivity/inc/ServiceLocator.hpp" - -#pragma hdrstop - -using namespace Microsoft::Console::Types; -using Microsoft::Console::Interactivity::ServiceLocator; -// Routine Description: -// - Creates an object representing an interactive popup overlay during cooked mode command line editing. -// - NOTE: Modifies global popup count (and adjusts cursor visibility as appropriate.) -// Arguments: -// - screenInfo - Reference to screen on which the popup should be drawn/overlaid. -// - proposedSize - Suggested size of the popup. May be adjusted based on screen size. -Popup::Popup(SCREEN_INFORMATION& screenInfo, const til::size proposedSize) : - _screenInfo(screenInfo), - _userInputFunction(&Popup::_getUserInputInternal) -{ - _attributes = screenInfo.GetPopupAttributes(); - - const auto size = _CalculateSize(screenInfo, proposedSize); - const auto origin = _CalculateOrigin(screenInfo, size); - - _region.left = origin.x; - _region.top = origin.y; - _region.right = origin.x + size.width - 1; - _region.bottom = origin.y + size.height - 1; - - _oldScreenSize = screenInfo.GetBufferSize().Dimensions(); - - til::inclusive_rect TargetRect; - TargetRect.left = 0; - TargetRect.top = _region.top; - TargetRect.right = _oldScreenSize.width - 1; - TargetRect.bottom = _region.bottom; - - // copy the data into the backup buffer - _oldContents = std::move(screenInfo.ReadRect(Viewport::FromInclusive(TargetRect))); - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto countWas = gci.PopupCount.fetch_add(1ui16); - if (0 == countWas) - { - // If this is the first popup to be shown, stop the cursor from appearing/blinking - screenInfo.GetTextBuffer().GetCursor().SetIsPopupShown(true); - } -} - -// Routine Description: -// - Cleans up a popup object -// - NOTE: Modifies global popup count (and adjusts cursor visibility as appropriate.) -Popup::~Popup() -{ - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto countWas = gci.PopupCount.fetch_sub(1); - if (1 == countWas) - { - // Notify we're done showing popups. - gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().SetIsPopupShown(false); - } -} - -void Popup::Draw() -{ - _DrawBorder(); - _DrawContent(); -} - -// Routine Description: -// - Draws the outlines of the popup area in the screen buffer -void Popup::_DrawBorder() -{ - // fill attributes of top line - til::point WriteCoord; - WriteCoord.x = _region.left; - WriteCoord.y = _region.top; - _screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord); - - // draw upper left corner - _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT, 1), WriteCoord); - - // draw upper bar - WriteCoord.x += 1; - _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord); - - // draw upper right corner - WriteCoord.x = _region.right; - _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_LEFT, 1), WriteCoord); - - for (til::CoordType i = 0; i < Height(); i++) - { - WriteCoord.y += 1; - WriteCoord.x = _region.left; - - // fill attributes - _screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord); - - _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord); - - WriteCoord.x = _region.right; - _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord); - } - - // Draw bottom line. - // Fill attributes of top line. - WriteCoord.x = _region.left; - WriteCoord.y = _region.bottom; - _screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord); - - // Draw bottom left corner. - WriteCoord.x = _region.left; - _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_RIGHT, 1), WriteCoord); - - // Draw lower bar. - WriteCoord.x += 1; - _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord); - - // draw lower right corner - WriteCoord.x = _region.right; - _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_LEFT, 1), WriteCoord); -} - -// Routine Description: -// - Draws prompt information in the popup area to tell the user what to enter. -// Arguments: -// - id - Resource ID for string to display to user -void Popup::_DrawPrompt(const UINT id) -{ - auto text = _LoadString(id); - - // Draw empty popup. - til::point WriteCoord; - WriteCoord.x = _region.left + 1; - WriteCoord.y = _region.top + 1; - auto lStringLength = Width(); - for (til::CoordType i = 0; i < Height(); i++) - { - const OutputCellIterator it(UNICODE_SPACE, _attributes, lStringLength); - const auto done = _screenInfo.Write(it, WriteCoord); - lStringLength = done.GetCellDistance(it); - - WriteCoord.y += 1; - } - - WriteCoord.x = _region.left + 1; - WriteCoord.y = _region.top + 1; - - // write prompt to screen - lStringLength = gsl::narrow(text.size()); - if (lStringLength > Width()) - { - text = text.substr(0, Width()); - } - - size_t used; - LOG_IF_FAILED(ServiceLocator::LocateGlobals().api->WriteConsoleOutputCharacterWImpl(_screenInfo, - text, - WriteCoord, - used)); -} - -// Routine Description: -// - Cleans up a popup by restoring the stored buffer information to the region of -// the screen that the popup was covering and frees resources. -void Popup::End() -{ - // restore previous contents to screen - - til::inclusive_rect SourceRect; - SourceRect.left = 0; - SourceRect.top = _region.top; - SourceRect.right = _oldScreenSize.width - 1; - SourceRect.bottom = _region.bottom; - - const auto sourceViewport = Viewport::FromInclusive(SourceRect); - - _screenInfo.WriteRect(_oldContents, sourceViewport.Origin()); -} - -// Routine Description: -// - Helper to calculate the size of the popup. -// Arguments: -// - screenInfo - Screen buffer we will be drawing into -// - proposedSize - The suggested size of the popup that may need to be adjusted to fit -// Return Value: -// - Coordinate size that the popup should consume in the screen buffer -til::size Popup::_CalculateSize(const SCREEN_INFORMATION& screenInfo, const til::size proposedSize) -{ - // determine popup dimensions - auto size = proposedSize; - size.width += 2; // add borders - size.height += 2; // add borders - - const auto viewportSize = screenInfo.GetViewport().Dimensions(); - - size.width = std::min(size.width, viewportSize.width); - size.height = std::min(size.height, viewportSize.height); - - // make sure there's enough room for the popup borders - THROW_HR_IF(E_NOT_SUFFICIENT_BUFFER, size.width < 2 || size.height < 2); - - return size; -} - -// Routine Description: -// - Helper to calculate the origin point (within the screen buffer) for the popup -// Arguments: -// - screenInfo - Screen buffer we will be drawing into -// - size - The size that the popup will consume -// Return Value: -// - Coordinate position of the origin point of the popup -til::point Popup::_CalculateOrigin(const SCREEN_INFORMATION& screenInfo, const til::size size) -{ - const auto viewport = screenInfo.GetViewport(); - - // determine origin. center popup on window - til::point origin; - origin.x = (viewport.Width() - size.width) / 2 + viewport.Left(); - origin.y = (viewport.Height() - size.height) / 2 + viewport.Top(); - return origin; -} - -// Routine Description: -// - Helper to return the width of the popup in columns -// Return Value: -// - Width of popup inside attached screen buffer. -til::CoordType Popup::Width() const noexcept -{ - return _region.right - _region.left - 1; -} - -// Routine Description: -// - Helper to return the height of the popup in columns -// Return Value: -// - Height of popup inside attached screen buffer. -til::CoordType Popup::Height() const noexcept -{ - return _region.bottom - _region.top - 1; -} - -// Routine Description: -// - Helper to get the position on top of some types of popup dialogs where -// we should overlay the cursor for user input. -// Return Value: -// - Coordinate location on the popup where the cursor should be placed. -til::point Popup::GetCursorPosition() const noexcept -{ - til::point CursorPosition; - CursorPosition.x = _region.right - MINIMUM_COMMAND_PROMPT_SIZE; - CursorPosition.y = _region.top + 1; - return CursorPosition; -} - -// Routine Description: -// - changes the function used to gather user input. for allowing custom input during unit tests only -// Arguments: -// - function - function to use to fetch input -void Popup::SetUserInputFunction(UserInputFunction function) noexcept -{ - _userInputFunction = function; -} - -// Routine Description: -// - gets a single char input from the user -// Arguments: -// - cookedReadData - cookedReadData for this popup operation -// - popupKey - on completion, will be true if key was a popup key -// - wch - on completion, the char read from the user -// Return Value: -// - relevant NTSTATUS -[[nodiscard]] NTSTATUS Popup::_getUserInput(COOKED_READ_DATA& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept -{ - return _userInputFunction(cookedReadData, popupKey, modifiers, wch); -} - -// Routine Description: -// - gets a single char input from the user using the InputBuffer -// Arguments: -// - cookedReadData - cookedReadData for this popup operation -// - popupKey - on completion, will be true if key was a popup key -// - wch - on completion, the char read from the user -// Return Value: -// - relevant NTSTATUS -[[nodiscard]] NTSTATUS Popup::_getUserInputInternal(COOKED_READ_DATA& cookedReadData, - bool& popupKey, - DWORD& modifiers, - wchar_t& wch) noexcept -{ - const auto pInputBuffer = cookedReadData.GetInputBuffer(); - auto Status = GetChar(pInputBuffer, - &wch, - true, - nullptr, - &popupKey, - &modifiers); - if (FAILED_NTSTATUS(Status) && Status != CONSOLE_STATUS_WAIT) - { - cookedReadData.BytesRead() = 0; - } - return Status; -} diff --git a/src/host/popup.h b/src/host/popup.h deleted file mode 100644 index 73a91bafe274..000000000000 --- a/src/host/popup.h +++ /dev/null @@ -1,82 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- popup.h - -Abstract: -- This file contains the internal structures and definitions used by command line input and editing. - -Author: -- Therese Stowell (ThereseS) 15-Nov-1991 - -Revision History: -- Mike Griese (migrie) Jan 2018: - Refactored the history and alias functionality into their own files. -- Michael Niksa (miniksa) May 2018: - Separated out popups from the rest of command line functionality. ---*/ - -#pragma once - -#include "readDataCooked.hpp" -#include "screenInfo.hpp" -#include "readDataCooked.hpp" - -class CommandHistory; - -class Popup -{ -public: - static constexpr til::CoordType MINIMUM_COMMAND_PROMPT_SIZE = 5; - - using UserInputFunction = std::function; - - Popup(SCREEN_INFORMATION& screenInfo, const til::size proposedSize); - virtual ~Popup(); - [[nodiscard]] virtual NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept = 0; - - void Draw(); - - void End(); - - til::CoordType Width() const noexcept; - til::CoordType Height() const noexcept; - - til::point GetCursorPosition() const noexcept; - -protected: - // used in test code to alter how the popup fetches use input - void SetUserInputFunction(UserInputFunction function) noexcept; - -#ifdef UNIT_TESTING - friend class CopyFromCharPopupTests; - friend class CopyToCharPopupTests; - friend class CommandNumberPopupTests; - friend class CommandListPopupTests; -#endif - - [[nodiscard]] NTSTATUS _getUserInput(COOKED_READ_DATA& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept; - void _DrawPrompt(const UINT id); - virtual void _DrawContent() = 0; - - til::inclusive_rect _region; // region popup occupies - SCREEN_INFORMATION& _screenInfo; - TextAttribute _attributes; // text attributes - -private: - til::size _CalculateSize(const SCREEN_INFORMATION& screenInfo, const til::size proposedSize); - til::point _CalculateOrigin(const SCREEN_INFORMATION& screenInfo, const til::size size); - - void _DrawBorder(); - - [[nodiscard]] static NTSTATUS _getUserInputInternal(COOKED_READ_DATA& cookedReadData, - bool& popupKey, - DWORD& modifiers, - wchar_t& wch) noexcept; - - OutputCellRect _oldContents; // contains data under popup - til::size _oldScreenSize; - UserInputFunction _userInputFunction; -}; diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 75e57b559639..e9ce413c2065 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -3,46 +3,42 @@ #include "precomp.h" #include "readDataCooked.hpp" -#include "dbcs.h" + +#include "alias.h" +#include "history.h" +#include "resource.h" #include "stream.h" -#include "misc.h" #include "_stream.h" -#include "inputBuffer.hpp" -#include "cmdline.h" -#include "../types/inc/GlyphWidth.hpp" -#include "../types/inc/convert.hpp" - #include "../interactivity/inc/ServiceLocator.hpp" -#define LINE_INPUT_BUFFER_SIZE (256 * sizeof(WCHAR)) - using Microsoft::Console::Interactivity::ServiceLocator; +// As per https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10Obvious +constexpr int integerLog10(uint32_t v) +{ + return (v >= 1000000000) ? 9 : + (v >= 100000000) ? 8 : + (v >= 10000000) ? 7 : + (v >= 1000000) ? 6 : + (v >= 100000) ? 5 : + (v >= 10000) ? 4 : + (v >= 1000) ? 3 : + (v >= 100) ? 2 : + (v >= 10) ? 1 : + 0; +} + // Routine Description: // - Constructs cooked read data class to hold context across key presses while a user is modifying their 'input line'. // Arguments: // - pInputBuffer - Buffer that data will be read from. // - pInputReadHandleData - Context stored across calls from the same input handle to return partial data appropriately. // - screenInfo - Output buffer that will be used for 'echoing' the line back to the user so they can see/manipulate it -// - BufferSize - -// - BytesRead - -// - CurrentPosition - -// - BufPtr - -// - BackupLimit - // - UserBufferSize - The byte count of the buffer presented by the client // - UserBuffer - The buffer that was presented by the client for filling with input data on read conclusion/return from server/host. -// - OriginalCursorPosition - -// - NumberOfVisibleChars // - CtrlWakeupMask - Special client parameter to interrupt editing, end the wait, and return control to the client application -// - Echo - -// - InsertMode - -// - Processed - -// - Line - -// - pTempHandle - A handle to the output buffer to prevent it from being destroyed while we're using it to present 'edit line' text. // - initialData - any text data that should be prepopulated into the buffer // - pClientProcess - Attached process handle object -// Return Value: -// - THROW: Throws E_INVALIDARG for invalid pointers. COOKED_READ_DATA::COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer, _In_ INPUT_READ_HANDLE_DATA* const pInputReadHandleData, SCREEN_INFORMATION& screenInfo, @@ -54,242 +50,21 @@ COOKED_READ_DATA::COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer, _In_ ConsoleProcessHandle* const pClientProcess) : ReadData(pInputBuffer, pInputReadHandleData), _screenInfo{ screenInfo }, - _bytesRead{ 0 }, - _currentPosition{ 0 }, - _userBufferSize{ UserBufferSize }, - _userBuffer{ UserBuffer }, - _tempHandle{ nullptr }, + _userBuffer{ UserBuffer, UserBufferSize }, _exeName{ exeName }, - _pdwNumBytes{ nullptr }, - - _commandHistory{ CommandHistory::s_Find(pClientProcess) }, - _controlKeyState{ 0 }, + _processHandle{ pClientProcess }, + _history{ CommandHistory::s_Find(pClientProcess) }, _ctrlWakeupMask{ CtrlWakeupMask }, - _visibleCharCount{ 0 }, - _originalCursorPosition{ -1, -1 }, - _beforeDialogCursorPosition{ 0, 0 }, - - _echoInput{ WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_ECHO_INPUT) }, - _lineInput{ WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_LINE_INPUT) }, - _processedInput{ WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_PROCESSED_INPUT) }, - _insertMode{ ServiceLocator::LocateGlobals().getConsoleInformation().GetInsertMode() }, - _unicode{ false }, - _clientProcess{ pClientProcess } + _buffer{ initialData }, + _insertMode{ ServiceLocator::LocateGlobals().getConsoleInformation().GetInsertMode() } { #ifndef UNIT_TESTING - THROW_IF_FAILED(screenInfo.GetMainBuffer().AllocateIoHandle(ConsoleHandleData::HandleType::Output, - GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - _tempHandle)); + // The screen buffer instance is basically a reference counted HANDLE given out to the user. + // We need to ensure that it stays alive for the duration of the read. + // Coincidentally this serves another important purpose: It checks whether we're allowed to read from + // the given buffer in the first place. If it's missing the FILE_SHARE_READ flag, we can't read from it. + THROW_IF_FAILED(_screenInfo.AllocateIoHandle(ConsoleHandleData::HandleType::Output, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, _tempHandle)); #endif - - // to emulate OS/2 KbdStringIn, we read into our own big buffer - // (256 bytes) until the user types enter. then return as many - // chars as will fit in the user's buffer. - _bufferSize = std::max(UserBufferSize, LINE_INPUT_BUFFER_SIZE); - _buffer = std::make_unique(_bufferSize); - _backupLimit = reinterpret_cast(_buffer.get()); - _bufPtr = reinterpret_cast(_buffer.get()); - - // Initialize the user's buffer to spaces. This is done so that - // moving in the buffer via cursor doesn't do strange things. - std::fill_n(_bufPtr, _bufferSize / sizeof(wchar_t), UNICODE_SPACE); - - if (!initialData.empty()) - { - memcpy_s(_bufPtr, _bufferSize, initialData.data(), initialData.size() * 2); - - _bytesRead += initialData.size() * 2; - - const auto cchInitialData = initialData.size(); - VisibleCharCount() = cchInitialData; - _bufPtr += cchInitialData; - _currentPosition = cchInitialData; - - OriginalCursorPosition() = screenInfo.GetTextBuffer().GetCursor().GetPosition(); - OriginalCursorPosition().x -= (til::CoordType)_currentPosition; - - const auto sScreenBufferSizeX = screenInfo.GetBufferSize().Width(); - while (OriginalCursorPosition().x < 0) - { - OriginalCursorPosition().x += sScreenBufferSizeX; - OriginalCursorPosition().y -= 1; - } - } - - // TODO MSFT:11285829 find a better way to manage the lifetime of this object in relation to gci -} - -// Routine Description: -// - Destructs a read data class. -// - Decrements count of readers waiting on the given handle. -COOKED_READ_DATA::~COOKED_READ_DATA() -{ - CommandLine::Instance().EndAllPopups(); -} - -std::span COOKED_READ_DATA::SpanWholeBuffer() -{ - return std::span{ _backupLimit, (_bufferSize / sizeof(wchar_t)) }; -} - -std::span COOKED_READ_DATA::SpanAtPointer() -{ - auto wholeSpan = SpanWholeBuffer(); - return wholeSpan.subspan(_bufPtr - _backupLimit); -} - -bool COOKED_READ_DATA::HasHistory() const noexcept -{ - return _commandHistory != nullptr; -} - -CommandHistory& COOKED_READ_DATA::History() noexcept -{ - return *_commandHistory; -} - -const size_t& COOKED_READ_DATA::VisibleCharCount() const noexcept -{ - return _visibleCharCount; -} - -size_t& COOKED_READ_DATA::VisibleCharCount() noexcept -{ - return _visibleCharCount; -} - -SCREEN_INFORMATION& COOKED_READ_DATA::ScreenInfo() noexcept -{ - return _screenInfo; -} - -til::point COOKED_READ_DATA::OriginalCursorPosition() const noexcept -{ - return _originalCursorPosition; -} - -til::point& COOKED_READ_DATA::OriginalCursorPosition() noexcept -{ - return _originalCursorPosition; -} - -til::point& COOKED_READ_DATA::BeforeDialogCursorPosition() noexcept -{ - return _beforeDialogCursorPosition; -} - -bool COOKED_READ_DATA::IsEchoInput() const noexcept -{ - return _echoInput; -} - -bool COOKED_READ_DATA::IsInsertMode() const noexcept -{ - return _insertMode; -} - -void COOKED_READ_DATA::SetInsertMode(const bool mode) noexcept -{ - _insertMode = mode; -} - -bool COOKED_READ_DATA::IsUnicode() const noexcept -{ - return _unicode; -} - -// Routine Description: -// - gets the size of the user buffer -// Return Value: -// - the size of the user buffer in bytes -size_t COOKED_READ_DATA::UserBufferSize() const noexcept -{ - return _userBufferSize; -} - -// Routine Description: -// - gets a pointer to the beginning of the prompt storage -// Return Value: -// - pointer to the first char in the internal prompt storage array -wchar_t* COOKED_READ_DATA::BufferStartPtr() noexcept -{ - return _backupLimit; -} - -// Routine Description: -// - gets a pointer to where the next char will be inserted into the prompt storage -// Return Value: -// - pointer to the current insertion point of the prompt storage array -wchar_t* COOKED_READ_DATA::BufferCurrentPtr() noexcept -{ - return _bufPtr; -} - -// Routine Description: -// - Set the location of the next char insert into the prompt storage to be at -// ptr. ptr must point into a valid portion of the internal prompt storage array -// Arguments: -// - ptr - the new char insertion location -void COOKED_READ_DATA::SetBufferCurrentPtr(wchar_t* ptr) noexcept -{ - _bufPtr = ptr; -} - -// Routine Description: -// - gets the number of bytes read so far into the prompt buffer -// Return Value: -// - the number of bytes read -const size_t& COOKED_READ_DATA::BytesRead() const noexcept -{ - return _bytesRead; -} - -// Routine Description: -// - gets the number of bytes read so far into the prompt buffer -// Return Value: -// - the number of bytes read -size_t& COOKED_READ_DATA::BytesRead() noexcept -{ - return _bytesRead; -} - -// Routine Description: -// - gets the index for the current insertion point of the prompt -// Return Value: -// - the index of the current insertion point -const size_t& COOKED_READ_DATA::InsertionPoint() const noexcept -{ - return _currentPosition; -} - -// Routine Description: -// - gets the index for the current insertion point of the prompt -// Return Value: -// - the index of the current insertion point -size_t& COOKED_READ_DATA::InsertionPoint() noexcept -{ - return _currentPosition; -} - -// Routine Description: -// - sets the number of bytes that will be reported when this read block completes its read -// Arguments: -// - count - the number of bytes to report -void COOKED_READ_DATA::SetReportedByteCount(const size_t count) noexcept -{ - FAIL_FAST_IF_NULL(_pdwNumBytes); - *_pdwNumBytes = count; -} - -// Routine Description: -// - resets the prompt to be as if it was erased -void COOKED_READ_DATA::Erase() noexcept -{ - _bufPtr = _backupLimit; - _bytesRead = 0; - _currentPosition = 0; - _visibleCharCount = 0; } // Routine Description: @@ -314,24 +89,15 @@ bool COOKED_READ_DATA::Notify(const WaitTerminationReason TerminationReason, _Out_ NTSTATUS* const pReplyStatus, _Out_ size_t* const pNumBytes, _Out_ DWORD* const pControlKeyState, - _Out_ void* const /*pOutputData*/) + _Out_ void* const /*pOutputData*/) noexcept +try { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // this routine should be called by a thread owning the same - // lock on the same console as we're reading from. - FAIL_FAST_IF(!gci.IsConsoleLocked()); - *pNumBytes = 0; *pControlKeyState = 0; - *pReplyStatus = STATUS_SUCCESS; - FAIL_FAST_IF(_pInputReadHandleData->IsInputPending()); - - // this routine should be called by a thread owning the same lock on the same console as we're reading from. - FAIL_FAST_IF(_pInputReadHandleData->GetReadCount() == 0); - // if ctrl-c or ctrl-break was seen, terminate read. if (WI_IsAnyFlagSet(TerminationReason, (WaitTerminationReason::CtrlC | WaitTerminationReason::CtrlBreak))) { @@ -351,7 +117,6 @@ bool COOKED_READ_DATA::Notify(const WaitTerminationReason TerminationReason, // We must see if we were woken up because the handle is being closed. If // so, we decrement the read count. If it goes to zero, we wake up the // close thread. Otherwise, we wake up any other thread waiting for data. - if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::HandleClosing)) { *pReplyStatus = STATUS_ALERTED; @@ -359,71 +124,23 @@ bool COOKED_READ_DATA::Notify(const WaitTerminationReason TerminationReason, return true; } - // If we get to here, this routine was called either by the input thread - // or a write routine. Both of these callers grab the current console - // lock. - - // MSFT:13994975 This is REALLY weird. - // When we're doing cooked reading for popups, we come through this method - // twice. Once when we press F7 to bring up the popup, then again when we - // press enter to input the selected command. - // The first time, there is no popup, and we go to CookedRead. We pass into - // CookedRead `pNumBytes`, which is passed to us as the address of the - // stack variable dwNumBytes, in ConsoleWaitBlock::Notify. - // CookedRead sets this->_pdwNumBytes to that value, and starts the popup, - // which returns all the way up, and pops the ConsoleWaitBlock::Notify - // stack frame containing the address we're pointing at. - // Then on the second time through this function, we hit this if block, - // because there is a popup to get input from. - // However, pNumBytes is now the address of a different stack frame, and not - // necessarily the same as before (presumably not at all). The - // Callback would try and write the number of bytes read to the - // value in _pdwNumBytes, and then we'd return up to ConsoleWaitBlock::Notify, - // who's dwNumBytes had nothing in it. - // To fix this, when we hit this with a popup, we're going to make sure to - // refresh the value of _pdwNumBytes to the current address we want to put - // the out value into. - // It's still really weird, but limits the potential fallout of changing a - // piece of old spaghetti code. - if (_commandHistory) - { - if (CommandLine::Instance().HasPopup()) - { - // (see above comment, MSFT:13994975) - // Make sure that the popup writes the dwNumBytes to the right place - if (pNumBytes) - { - _pdwNumBytes = pNumBytes; - } - - auto& popup = CommandLine::Instance().GetPopup(); - *pReplyStatus = popup.Process(*this); - if (*pReplyStatus == CONSOLE_STATUS_READ_COMPLETE || - (*pReplyStatus != CONSOLE_STATUS_WAIT && *pReplyStatus != CONSOLE_STATUS_WAIT_NO_BLOCK)) - { - *pReplyStatus = S_OK; - gci.SetCookedReadData(nullptr); - return true; - } - return false; - } - } - - *pReplyStatus = Read(fIsUnicode, *pNumBytes, *pControlKeyState); - if (*pReplyStatus != CONSOLE_STATUS_WAIT) + if (Read(fIsUnicode, *pNumBytes, *pControlKeyState)) { gci.SetCookedReadData(nullptr); return true; } - else - { - return false; - } + + return false; } +NT_CATCH_RETURN() -bool COOKED_READ_DATA::AtEol() const noexcept +void COOKED_READ_DATA::MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) noexcept { - return _bytesRead == (_currentPosition * 2); + // See the comment in WaitBlock.cpp for more information. + if (_userBuffer.data() == oldBuffer) + { + _userBuffer = { static_cast(newBuffer), _userBuffer.size() }; + } } // Routine Description: @@ -436,633 +153,999 @@ bool COOKED_READ_DATA::AtEol() const noexcept // - numBytes - On in, the number of bytes available in the client // buffer. On out, the number of bytes consumed in the client buffer. // - controlKeyState - For some types of reads, this is the modifier key state with the last button press. -[[nodiscard]] HRESULT COOKED_READ_DATA::Read(const bool isUnicode, - size_t& numBytes, - ULONG& controlKeyState) noexcept +bool COOKED_READ_DATA::Read(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState) { controlKeyState = 0; - auto Status = _readCharInputLoop(isUnicode, numBytes); + const auto done = _readCharInputLoop(); + + // NOTE: Don't call _flushBuffer in a wil::scope_exit/defer. + // It may throw and throwing during an ongoing exception is a bad idea. + _flushBuffer(); - // if the read was completed (status != wait), free the cooked read - // data. also, close the temporary output handle that was opened to - // echo the characters read. - if (Status != CONSOLE_STATUS_WAIT) + if (done) { - Status = _handlePostCharInputLoop(isUnicode, numBytes, controlKeyState); + _handlePostCharInputLoop(isUnicode, numBytes, controlKeyState); } - return Status; + return done; } -void COOKED_READ_DATA::ProcessAliases(DWORD& lineCount) +// Printing wide glyphs at the end of a row results in a forced line wrap and a padding whitespace to be inserted. +// When the text buffer resizes these padding spaces may vanish and the _distanceCursor and _distanceEnd measurements become inaccurate. +// To fix this, this function is called before a resize and will clear the input line. Afterwards, RedrawAfterResize() will restore it. +void COOKED_READ_DATA::EraseBeforeResize() { - Alias::s_MatchAndCopyAliasLegacy(_backupLimit, - _bytesRead, - _backupLimit, - _bufferSize, - _bytesRead, - _exeName, - lineCount); + while (!_popups.empty()) + { + _popupDone(); + } + + if (_distanceEnd) + { + _unwindCursorPosition(_distanceCursor); + _erase(_distanceEnd); + _unwindCursorPosition(_distanceEnd); + _distanceCursor = 0; + _distanceEnd = 0; + } } -// Routine Description: -// - This method handles the various actions that occur on the edit line like pressing keys left/right/up/down, paging, and -// the final ENTER key press that will end the wait and finally return the data. -// Arguments: -// - pCookedReadData - Pointer to cooked read data information (edit line, client buffer, etc.) -// - wch - The most recently pressed/retrieved character from the input buffer (keystroke) -// - keyState - Modifier keys/state information with the pressed key/character -// - status - The return code to pass to the client -// Return Value: -// - true if read is completed. false if we need to keep waiting and be called again with the user's next keystroke. -bool COOKED_READ_DATA::ProcessInput(const wchar_t wchOrig, - const DWORD keyState, - NTSTATUS& status) +// The counter-part to EraseBeforeResize(). +void COOKED_READ_DATA::RedrawAfterResize() +{ + _markAsDirty(); + _flushBuffer(); +} + +void COOKED_READ_DATA::SetInsertMode(bool insertMode) noexcept +{ + _insertMode = insertMode; +} + +bool COOKED_READ_DATA::IsEmpty() const noexcept +{ + return _buffer.empty() && _popups.empty(); +} + +bool COOKED_READ_DATA::PresentingPopup() const noexcept { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - size_t NumSpaces = 0; - til::CoordType ScrollY = 0; - size_t NumToWrite; - auto wch = wchOrig; - bool fStartFromDelim; - - status = STATUS_SUCCESS; - if (_bytesRead >= (_bufferSize - (2 * sizeof(WCHAR))) && wch != UNICODE_CARRIAGERETURN && wch != UNICODE_BACKSPACE) + return !_popups.empty(); +} + +til::point_span COOKED_READ_DATA::GetBoundaries() const noexcept +{ + const auto cursorPos = _screenInfo.GetTextBuffer().GetCursor().GetPosition(); + const auto beg = _offsetPosition(cursorPos, -_distanceCursor); + const auto end = _offsetPosition(beg, _distanceEnd); + return { beg, end }; +} + +// Reads text off of the InputBuffer and dispatches it to the current popup or otherwise into the _buffer contents. +bool COOKED_READ_DATA::_readCharInputLoop() +{ + for (;;) { - return false; + const auto hasPopup = !_popups.empty(); + auto charOrVkey = UNICODE_NULL; + auto commandLineEditingKeys = false; + auto popupKeys = false; + const auto pCommandLineEditingKeys = hasPopup ? nullptr : &commandLineEditingKeys; + const auto pPopupKeys = hasPopup ? &popupKeys : nullptr; + DWORD modifiers = 0; + + const auto status = GetChar(_pInputBuffer, &charOrVkey, true, pCommandLineEditingKeys, pPopupKeys, &modifiers); + if (status == CONSOLE_STATUS_WAIT) + { + return false; + } + THROW_IF_NTSTATUS_FAILED(status); + + if (hasPopup) + { + const auto wch = static_cast(popupKeys ? 0 : charOrVkey); + const auto vkey = static_cast(popupKeys ? charOrVkey : 0); + if (_popupHandleInput(wch, vkey, modifiers)) + { + return true; + } + } + else + { + if (commandLineEditingKeys) + { + _handleVkey(charOrVkey, modifiers); + } + else if (_handleChar(charOrVkey, modifiers)) + { + return true; + } + } } +} + +// Handles character input for _readCharInputLoop() when no popups exist. +bool COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) +{ + // All paths in this function modify the buffer. if (_ctrlWakeupMask != 0 && wch < L' ' && (_ctrlWakeupMask & (1 << wch))) { - *_bufPtr = wch; - _bytesRead += sizeof(WCHAR); - _bufPtr += 1; - _currentPosition += 1; - _controlKeyState = keyState; + _controlKeyState = modifiers; + _buffer.push_back(wch); + _markAsDirty(); return true; } - if (wch == EXTKEY_ERASE_PREV_WORD) + switch (wch) { - wch = UNICODE_BACKSPACE; - } - - if (AtEol()) + case UNICODE_CARRIAGERETURN: { - // If at end of line, processing is relatively simple. Just store the character and write it to the screen. - if (wch == UNICODE_BACKSPACE2) - { - wch = UNICODE_BACKSPACE; - } - - if (wch != UNICODE_BACKSPACE || _bufPtr != _backupLimit) + static constexpr std::wstring_view cr{ L"\r" }; + static constexpr std::wstring_view crlf{ L"\r\n" }; + const auto processedInput = WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_PROCESSED_INPUT); + _buffer.append(processedInput ? crlf : cr); + _bufferCursor = _buffer.size(); + _markAsDirty(); + return true; + } + case UNICODE_BACKSPACE: + if (WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_PROCESSED_INPUT)) { - fStartFromDelim = IsWordDelim(_bufPtr[-1]); + const auto pos = TextBuffer::GraphemePrev(_buffer, _bufferCursor); + _buffer.erase(pos, _bufferCursor - pos); + _bufferCursor = pos; + _markAsDirty(); - auto loop = true; - while (loop) + if (_screenInfo.HasAccessibilityEventing()) { - loop = false; - if (_echoInput) - { - NumToWrite = sizeof(WCHAR); - status = WriteCharsLegacy(_screenInfo, - _backupLimit, - _bufPtr, - &wch, - &NumToWrite, - &NumSpaces, - _originalCursorPosition.x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY); - if (SUCCEEDED_NTSTATUS(status)) - { - _originalCursorPosition.y += ScrollY; - } - else - { - RIPMSG1(RIP_WARNING, "WriteCharsLegacy failed %x", status); - } - } - - _visibleCharCount += NumSpaces; - if (wch == UNICODE_BACKSPACE && _processedInput) - { - _bytesRead -= sizeof(WCHAR); - // clang-format off -#pragma prefast(suppress: __WARNING_POTENTIAL_BUFFER_OVERFLOW_HIGH_PRIORITY, "This access is fine") - // clang-format on - *_bufPtr = (WCHAR)' '; - _bufPtr -= 1; - _currentPosition -= 1; - - // Repeat until it hits the word boundary - if (wchOrig == EXTKEY_ERASE_PREV_WORD && - _bufPtr != _backupLimit && - fStartFromDelim ^ !IsWordDelim(_bufPtr[-1])) - { - loop = true; - } - } - else + if (const auto pConsoleWindow = ServiceLocator::LocateConsoleWindow()) { - *_bufPtr = wch; - _bytesRead += sizeof(WCHAR); - _bufPtr += 1; - _currentPosition += 1; + LOG_IF_FAILED(pConsoleWindow->SignalUia(UIA_Text_TextChangedEventId)); } } + return false; + } + // If processed mode is disabled, control characters like backspace are treated like any other character. + [[fallthrough]]; + default: + if (_insertMode) + { + _buffer.insert(_bufferCursor, 1, wch); } + else + { + // TODO: If the input grapheme is >1 char, then this will replace >1 grapheme + // --> We should accumulate input text as much as possible and then call _processInput with wstring_view. + const auto nextGraphemeLength = TextBuffer::GraphemeNext(_buffer, _bufferCursor) - _bufferCursor; + _buffer.replace(_bufferCursor, nextGraphemeLength, 1, wch); + } + _bufferCursor++; + _markAsDirty(); + return false; } - else - { - auto CallWrite = true; - const auto sScreenBufferSizeX = _screenInfo.GetBufferSize().Width(); - - // processing in the middle of the line is more complex: +} - // calculate new cursor position - // store new char - // clear the current command line from the screen - // write the new command line to the screen - // update the cursor position +// Handles non-character input for _readCharInputLoop() when no popups exist. +void COOKED_READ_DATA::_handleVkey(uint16_t vkey, DWORD modifiers) +{ + const auto ctrlPressed = WI_IsAnyFlagSet(modifiers, LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); + const auto altPressed = WI_IsAnyFlagSet(modifiers, LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); - if (wch == UNICODE_BACKSPACE && _processedInput) + switch (vkey) + { + case VK_ESCAPE: + _buffer.clear(); + _bufferCursor = 0; + _markAsDirty(); + break; + case VK_HOME: + if (ctrlPressed) { - // for backspace, use writechars to calculate the new cursor position. - // this call also sets the cursor to the right position for the - // second call to writechars. - - if (_bufPtr != _backupLimit) + _buffer.erase(0, _bufferCursor); + } + _bufferCursor = 0; + _markAsDirty(); + break; + case VK_END: + if (ctrlPressed) + { + _buffer.erase(_bufferCursor); + } + _bufferCursor = _buffer.size(); + _markAsDirty(); + break; + case VK_LEFT: + if (_bufferCursor != 0) + { + if (ctrlPressed) { - fStartFromDelim = IsWordDelim(_bufPtr[-1]); - - auto loop = true; - while (loop) + // This would ideally use GraphemePrev() as well, but IsWordDelim() hasn't been refactored yet. + // Seek to the preceding left-hand word boundary. + // (The boundary between a non-word on the left and word on the right.) + --_bufferCursor; + while (_bufferCursor != 0 && (!IsWordDelim(_buffer[_bufferCursor - 1]) || IsWordDelim(_buffer[_bufferCursor]))) { - loop = false; - // we call writechar here so that cursor position gets updated - // correctly. we also call it later if we're not at eol so - // that the remainder of the string can be updated correctly. - - if (_echoInput) - { - NumToWrite = sizeof(WCHAR); - status = WriteCharsLegacy(_screenInfo, - _backupLimit, - _bufPtr, - &wch, - &NumToWrite, - nullptr, - _originalCursorPosition.x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - nullptr); - if (FAILED_NTSTATUS(status)) - { - RIPMSG1(RIP_WARNING, "WriteCharsLegacy failed %x", status); - } - } - _bytesRead -= sizeof(WCHAR); - _bufPtr -= 1; - _currentPosition -= 1; - memmove(_bufPtr, - _bufPtr + 1, - _bytesRead - (_currentPosition * sizeof(WCHAR))); - { - auto buf = (PWCHAR)((PBYTE)_backupLimit + _bytesRead); - *buf = (WCHAR)' '; - } - NumSpaces = 0; - - // Repeat until it hits the word boundary - if (wchOrig == EXTKEY_ERASE_PREV_WORD && - _bufPtr != _backupLimit && - fStartFromDelim ^ !IsWordDelim(_bufPtr[-1])) - { - loop = true; - } + --_bufferCursor; } } else { - CallWrite = false; + _bufferCursor = TextBuffer::GraphemePrev(_buffer, _bufferCursor); } + _markAsDirty(); } - else + break; + case VK_F1: + case VK_RIGHT: + if (_bufferCursor != _buffer.size()) { - // store the char - if (wch == UNICODE_CARRIAGERETURN) + if (ctrlPressed && vkey == VK_RIGHT) { - _bufPtr = (PWCHAR)((PBYTE)_backupLimit + _bytesRead); - *_bufPtr = wch; - _bufPtr += 1; - _bytesRead += sizeof(WCHAR); - _currentPosition += 1; + // This would ideally use GraphemeNext() as well, but IsWordDelim() hasn't been refactored yet. + // Seek to the preceding left-hand word boundary. + // (The boundary between a non-word on the left and word on the right.) + ++_bufferCursor; + while (_bufferCursor != _buffer.size() && (!IsWordDelim(_buffer[_bufferCursor - 1]) || IsWordDelim(_buffer[_bufferCursor]))) + { + ++_bufferCursor; + } } else { - auto fBisect = false; - - if (_echoInput) - { - if (CheckBisectProcessW(_screenInfo, - _backupLimit, - _currentPosition + 1, - sScreenBufferSizeX - _originalCursorPosition.x, - _originalCursorPosition.x, - TRUE)) - { - fBisect = true; - } - } - - if (_insertMode) - { - memmove(_bufPtr + 1, - _bufPtr, - _bytesRead - (_currentPosition * sizeof(WCHAR))); - _bytesRead += sizeof(WCHAR); - } - *_bufPtr = wch; - _bufPtr += 1; - _currentPosition += 1; - - // calculate new cursor position - if (_echoInput) - { - NumSpaces = RetrieveNumberOfSpaces(_originalCursorPosition.x, - _backupLimit, - _currentPosition - 1); - if (NumSpaces > 0 && fBisect) - NumSpaces--; - } + _bufferCursor = TextBuffer::GraphemeNext(_buffer, _bufferCursor); } + _markAsDirty(); } - - if (_echoInput && CallWrite) + else { - til::point CursorPosition; - - // save cursor position - CursorPosition = _screenInfo.GetTextBuffer().GetCursor().GetPosition(); - CursorPosition.x = (til::CoordType)(CursorPosition.x + NumSpaces); - - // clear the current command line from the screen - // clang-format off -#pragma prefast(suppress: __WARNING_BUFFER_OVERFLOW, "Not sure why prefast doesn't like this call.") - // clang-format on - DeleteCommandLine(*this, FALSE); - - // write the new command line to the screen - NumToWrite = _bytesRead; - - DWORD dwFlags = WC_INTERACTIVE; - if (wch == UNICODE_CARRIAGERETURN) + // Traditionally pressing right at the end of an input line will paste characters from the previous command. + // I'd rather remove this "feature" than make it work with modern Unicode expectations. + // Unfortunately the time for that decision is not here yet, so here's the incorrect approach... + if (_history) { - dwFlags |= WC_KEEP_CURSOR_VISIBLE; + const auto cmd = _history->GetLastCommand(); + const auto beg = _bufferCursor; + if (beg < cmd.size()) + { + const auto end = TextBuffer::GraphemeNext(cmd, beg); + _buffer.append(cmd, beg, end - beg); + _bufferCursor = _buffer.size(); + _markAsDirty(); + } } - status = WriteCharsLegacy(_screenInfo, - _backupLimit, - _backupLimit, - _backupLimit, - &NumToWrite, - &_visibleCharCount, - _originalCursorPosition.x, - dwFlags, - &ScrollY); - if (FAILED_NTSTATUS(status)) + } + break; + case VK_INSERT: + _insertMode = !_insertMode; + _screenInfo.SetCursorDBMode(_insertMode != ServiceLocator::LocateGlobals().getConsoleInformation().GetInsertMode()); + _markAsDirty(); + break; + case VK_DELETE: + _buffer.erase(_bufferCursor, TextBuffer::GraphemeNext(_buffer, _bufferCursor) - _bufferCursor); + _markAsDirty(); + break; + case VK_UP: + case VK_F5: + if (_history && !_history->AtFirstCommand()) + { + _replaceBuffer(_history->Retrieve(CommandHistory::SearchDirection::Previous)); + } + break; + case VK_DOWN: + if (_history && !_history->AtLastCommand()) + { + _replaceBuffer(_history->Retrieve(CommandHistory::SearchDirection::Next)); + } + break; + case VK_PRIOR: + if (_history && !_history->AtFirstCommand()) + { + _replaceBuffer(_history->RetrieveNth(0)); + } + break; + case VK_NEXT: + if (_history && !_history->AtLastCommand()) + { + _replaceBuffer(_history->RetrieveNth(INT_MAX)); + } + break; + case VK_F2: + if (_history) + { + _popupPush(PopupKind::CopyToChar); + } + break; + case VK_F3: + if (_history) + { + const auto last = _history->GetLastCommand(); + if (last.size() > _bufferCursor) { - RIPMSG1(RIP_WARNING, "WriteCharsLegacy failed 0x%x", status); - _bytesRead = 0; - return true; + const auto count = last.size() - _bufferCursor; + _buffer.replace(_bufferCursor, count, last, _bufferCursor, count); + _bufferCursor += count; + _markAsDirty(); } - - // update cursor position - if (wch != UNICODE_CARRIAGERETURN) + } + break; + case VK_F4: + // Historically the CopyFromChar popup was constrained to only work when a history exists, + // but I don't see why that should be. It doesn't depend on _history at all. + _popupPush(PopupKind::CopyFromChar); + break; + case VK_F6: + // Don't ask me why but F6 is an alias for ^Z. + _handleChar(0x1a, modifiers); + break; + case VK_F7: + if (!ctrlPressed && !altPressed) + { + if (_history && _history->GetNumberOfCommands()) { - if (CheckBisectProcessW(_screenInfo, - _backupLimit, - _currentPosition + 1, - sScreenBufferSizeX - _originalCursorPosition.x, - _originalCursorPosition.x, - TRUE)) - { - if (CursorPosition.x == (sScreenBufferSizeX - 1)) - { - CursorPosition.x++; - } - } - - // adjust cursor position for WriteChars - _originalCursorPosition.y += ScrollY; - CursorPosition.y += ScrollY; - AdjustCursorPosition(_screenInfo, CursorPosition, TRUE, nullptr); + _popupPush(PopupKind::CommandList); } } - } - - // in cooked mode, enter (carriage return) is converted to - // carriage return linefeed (0xda). carriage return is always - // stored at the end of the buffer. - if (wch == UNICODE_CARRIAGERETURN) - { - if (_processedInput) + else if (altPressed) { - if (_bytesRead < _bufferSize) + if (_history) { - *_bufPtr = UNICODE_LINEFEED; - if (_echoInput) - { - NumToWrite = sizeof(WCHAR); - status = WriteCharsLegacy(_screenInfo, - _backupLimit, - _bufPtr, - _bufPtr, - &NumToWrite, - nullptr, - _originalCursorPosition.x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - nullptr); - if (FAILED_NTSTATUS(status)) - { - RIPMSG1(RIP_WARNING, "WriteCharsLegacy failed 0x%x", status); - } - } - _bytesRead += sizeof(WCHAR); - _bufPtr++; - _currentPosition += 1; + _history->Empty(); + _history->Flags |= CommandHistory::CLE_ALLOCATED; } } - // reset the cursor back to 25% if necessary - if (_lineInput) + break; + case VK_F8: + if (_history) { - if (_insertMode != gci.GetInsertMode()) + CommandHistory::Index index = 0; + const auto prefix = std::wstring_view{ _buffer }.substr(0, _bufferCursor); + if (_history->FindMatchingCommand(prefix, _history->LastDisplayed, index, CommandHistory::MatchOptions::None)) { - // Make cursor small. - LOG_IF_FAILED(CommandLine::Instance().ProcessCommandLine(*this, VK_INSERT, 0)); + _buffer.assign(_history->RetrieveNth(index)); + _bufferCursor = std::min(_bufferCursor, _buffer.size()); + _markAsDirty(); } - - status = STATUS_SUCCESS; - return true; } + break; + case VK_F9: + if (_history && _history->GetNumberOfCommands()) + { + _popupPush(PopupKind::CommandNumber); + } + break; + case VK_F10: + // Alt+F10 clears the aliases for specifically cmd.exe. + if (altPressed) + { + Alias::s_ClearCmdExeAliases(); + } + break; + default: + assert(false); // Unrecognized VK. Fix or don't call this function? + break; } - - return false; } -// Routine Description: -// - Writes string to current position in prompt line. can overwrite text to the right of the cursor. -// Arguments: -// - wstr - the string to write -// Return Value: -// - The number of chars written -size_t COOKED_READ_DATA::Write(const std::wstring_view wstr) +// Handles any tasks that need to be completed after the read input loop finishes, +// like handling doskey aliases and converting the input to non-UTF16. +void COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState) { - auto end = wstr.end(); - const auto charsRemaining = (_bufferSize / sizeof(wchar_t)) - (_bufPtr - _backupLimit); - if (wstr.size() > charsRemaining) + auto writer = _userBuffer; + std::wstring_view input{ _buffer }; + size_t lineCount = 1; + + if (WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_ECHO_INPUT)) { - end = std::next(wstr.begin(), charsRemaining); + // The last characters in line-read are a \r\n (unless _ctrlWakeupMask was used). + // s_MatchAndCopyAlias doesn't want to know about them. + if (input.ends_with(L"\r\n")) + { + input.remove_suffix(2); + } + + if (_history) + { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + LOG_IF_FAILED(_history->Add(input, WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP))); + } + + Tracing::s_TraceCookedRead(_processHandle, input); + + const auto alias = Alias::s_MatchAndCopyAlias(input, _exeName, lineCount); + if (!alias.empty()) + { + _buffer = alias; + } + + // NOTE: Even if there's no alias we should restore the trailing \r\n that we removed above. + input = std::wstring_view{ _buffer }; + + // doskey aliases may result in multiple lines of output (for instance `doskey test=echo foo$Techo bar$Techo baz`). + // We need to emit them as multiple cooked reads as well, so that each read completes at a \r\n. + if (lineCount > 1) + { + // ProcessAliases() is supposed to end each line with \r\n. If it doesn't we might as well fail-fast. + const auto firstLineEnd = input.find(UNICODE_LINEFEED) + 1; + input = input.substr(0, std::min(input.size(), firstLineEnd)); + } } - std::copy(wstr.begin(), end, _bufPtr); - const size_t charsInserted = end - wstr.begin(); - auto bytesInserted = charsInserted * sizeof(wchar_t); - _currentPosition += charsInserted; - _bytesRead += bytesInserted; + const auto inputSizeBefore = input.size(); + _pInputBuffer->Consume(isUnicode, input, writer); - if (IsEchoInput()) + if (lineCount > 1) { - size_t NumSpaces = 0; - til::CoordType ScrollY = 0; - - FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(ScreenInfo(), - _backupLimit, - _bufPtr, - _bufPtr, - &bytesInserted, - &NumSpaces, - OriginalCursorPosition().x, - WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE, - &ScrollY)); - OriginalCursorPosition().y += ScrollY; - VisibleCharCount() += NumSpaces; + // This is a continuation of the above identical if condition. + // We've truncated the `input` slice and now we need to restore it. + const auto inputSizeAfter = input.size(); + const auto amountConsumed = inputSizeBefore - inputSizeAfter; + input = std::wstring_view{ _buffer }; + input = input.substr(std::min(input.size(), amountConsumed)); + GetInputReadHandleData()->SaveMultilinePendingInput(input); } - _bufPtr += charsInserted; + else if (!input.empty()) + { + GetInputReadHandleData()->SavePendingInput(input); + } + + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.Flags |= CONSOLE_IGNORE_NEXT_KEYUP; - return charsInserted; + // If we previously called SetCursorDBMode() with true, + // this will ensure that the cursor returns to its normal look. + _screenInfo.SetCursorDBMode(false); + + numBytes = _userBuffer.size() - writer.size(); + controlKeyState = _controlKeyState; } -// Routine Description: -// - saves data in the prompt buffer to the outgoing user buffer -// Arguments: -// - cch - the number of chars to write to the user buffer -// Return Value: -// - the number of bytes written to the user buffer -size_t COOKED_READ_DATA::SavePromptToUserBuffer(const size_t cch) +// Signals to _flushBuffer() that the contents of _buffer are stale and need to be redrawn. +// ALL _buffer and _bufferCursor changes must be flagged with _markAsDirty(). +// +// By using _bufferDirty to avoid redrawing the buffer unless needed, we turn the amortized time complexity of _readCharInputLoop() +// from O(n^2) (n(n+1)/2 redraws) into O(n). Pasting text would quickly turn into "accidentally quadratic" meme material otherwise. +void COOKED_READ_DATA::_markAsDirty() +{ + _bufferDirty = true; +} + +// Draws the contents of _buffer onto the screen. +void COOKED_READ_DATA::_flushBuffer() { - size_t bytesToWrite = 0; - const auto hr = SizeTMult(cch, sizeof(wchar_t), &bytesToWrite); - if (FAILED(hr)) + // _flushBuffer() is called often and is a good place to assert() that our _bufferCursor is still in bounds. + assert(_bufferCursor <= _buffer.size()); + _bufferCursor = std::min(_bufferCursor, _buffer.size()); + + if (!_bufferDirty) { - return 0; + return; } - memmove(_userBuffer, _backupLimit, bytesToWrite); - - if (!IsUnicode()) + if (WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_ECHO_INPUT)) { - try + _unwindCursorPosition(_distanceCursor); + + const std::wstring_view view{ _buffer }; + const auto distanceBeforeCursor = _writeChars(view.substr(0, _bufferCursor)); + const auto distanceAfterCursor = _writeChars(view.substr(_bufferCursor)); + const auto distanceEnd = distanceBeforeCursor + distanceAfterCursor; + const auto eraseDistance = std::max(0, _distanceEnd - distanceEnd); + + // If the contents of _buffer became shorter we'll have to erase the previously printed contents. + if (eraseDistance > 0) { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto wstr = ConvertToW(gci.CP, { _userBuffer, cch }); - const auto copyAmount = std::min(wstr.size() * sizeof(wchar_t), _userBufferSize); - std::copy_n(reinterpret_cast(wstr.data()), copyAmount, _userBuffer); - return copyAmount; + _erase(eraseDistance); } - CATCH_LOG(); + + _unwindCursorPosition(distanceAfterCursor + eraseDistance); + + _distanceCursor = distanceBeforeCursor; + _distanceEnd = distanceEnd; } - return bytesToWrite; + + _bufferDirty = false; } -// Routine Description: -// - saves data in the prompt buffer as pending input -// Arguments: -// - index - the index of what wchar to start the saving -// - multiline - whether the pending input should be saved as multiline or not -void COOKED_READ_DATA::SavePendingInput(const size_t index, const bool multiline) +// This is just a small helper to fill the next N cells starting at the current cursor position with whitespace. +// The implementation is inefficient for `count`s larger than 7, but such calls are uncommon to happen (namely only when resizing the window). +void COOKED_READ_DATA::_erase(size_t count) const +{ + const std::wstring str(count, L' '); + std::ignore = _writeChars(str); +} + +// A helper to write text and calculate the number of cells we've written. +// _unwindCursorPosition then allows us to go that many cells back. Tracking cells instead of explicit +// buffer positions allows us to pay no further mind to whether the buffer scrolled up or not. +til::CoordType COOKED_READ_DATA::_writeChars(const std::wstring_view& text) const { - auto& inputReadHandleData = *GetInputReadHandleData(); - const std::wstring_view pending{ _backupLimit + index, - BytesRead() / sizeof(wchar_t) - index }; - if (multiline) + if (text.empty()) { - inputReadHandleData.SaveMultilinePendingInput(pending); + return 0; } - else + + const auto& textBuffer = _screenInfo.GetTextBuffer(); + const auto& cursor = textBuffer.GetCursor(); + const auto width = textBuffer.GetSize().Width(); + const auto initialCursorPos = cursor.GetPosition(); + til::CoordType scrollY = 0; + + WriteCharsLegacy(_screenInfo, text, true, &scrollY); + + const auto finalCursorPos = cursor.GetPosition(); + return (finalCursorPos.y - initialCursorPos.y + scrollY) * width + finalCursorPos.x - initialCursorPos.x; +} + +// Moves the given point by the given distance inside the text buffer, as if moving a cursor with the left/right arrow keys. +til::point COOKED_READ_DATA::_offsetPosition(til::point pos, til::CoordType distance) const +{ + const auto size = _screenInfo.GetTextBuffer().GetSize().Dimensions(); + const auto w64 = static_cast(size.width); + const auto h64 = static_cast(size.height); + const auto area = w64 * h64; + + auto off = w64 * pos.y + pos.x; + off += distance; + off = off < 0 ? 0 : (off > area ? area : off); + + return { + gsl::narrow_cast(off % w64), + gsl::narrow_cast(off / w64), + }; +} + +// This moves the cursor `distance`-many cells back up in the buffer. +// It's intended to be used in combination with _writeChars. +void COOKED_READ_DATA::_unwindCursorPosition(til::CoordType distance) const +{ + if (distance <= 0) { - inputReadHandleData.SavePendingInput(pending); + // If all the code in this file works correctly, negative distances should not occur. + // If they do occur it would indicate a bug that would need to be fixed urgently. + assert(distance == 0); + return; } + + const auto& textBuffer = _screenInfo.GetTextBuffer(); + const auto& cursor = textBuffer.GetCursor(); + const auto pos = _offsetPosition(cursor.GetPosition(), -distance); + + std::ignore = _screenInfo.SetCursorPosition(pos, true); + _screenInfo.MakeCursorVisible(pos); } -// Routine Description: -// - saves data in the prompt buffer as pending input -// Arguments: -// - isUnicode - Treat as UCS-2 unicode or use Input CP to convert when done. -// - numBytes - On in, the number of bytes available in the client -// buffer. On out, the number of bytes consumed in the client buffer. -// Return Value: -// - Status code that indicates success, wait, etc. -[[nodiscard]] NTSTATUS COOKED_READ_DATA::_readCharInputLoop(const bool isUnicode, size_t& numBytes) noexcept +// Just a simple helper to replace the entire buffer contents. +void COOKED_READ_DATA::_replaceBuffer(const std::wstring_view& str) { - auto Status = STATUS_SUCCESS; + _buffer.assign(str); + _bufferCursor = _buffer.size(); + _markAsDirty(); +} - while (_bytesRead < _bufferSize) +// If the viewport is large enough to fit a popup, this function prepares everything for +// showing the given type. It handles computing the size of the popup, its position, +// backs the affected area up and draws the border and initial contents. +void COOKED_READ_DATA::_popupPush(const PopupKind kind) +try +{ + auto& textBuffer = _screenInfo.GetTextBuffer(); + const auto viewport = _screenInfo.GetViewport(); + const auto viewportOrigin = viewport.Origin(); + const auto viewportSize = viewport.Dimensions(); + + til::size proposedSize; + switch (kind) { - auto wch = UNICODE_NULL; - auto commandLineEditingKeys = false; - DWORD keyState = 0; - - // This call to GetChar may block. - Status = GetChar(_pInputBuffer, - &wch, - true, - &commandLineEditingKeys, - nullptr, - &keyState); - if (FAILED_NTSTATUS(Status)) + case PopupKind::CopyToChar: + proposedSize = { 26, 1 }; + break; + case PopupKind::CopyFromChar: + proposedSize = { 28, 1 }; + break; + case PopupKind::CommandNumber: + proposedSize = { 22 + CommandNumberMaxInputLength, 1 }; + break; + case PopupKind::CommandList: + { + const auto& commands = _history->GetCommands(); + const auto commandCount = _history->GetNumberOfCommands(); + + size_t maxStringLength = 0; + for (const auto& c : commands) { - if (Status != CONSOLE_STATUS_WAIT) - { - _bytesRead = 0; - } - break; + maxStringLength = std::max(maxStringLength, c.size()); } - // we should probably set these up in GetChars, but we set them - // up here because the debugger is multi-threaded and calls - // read before outputting the prompt. + // Account for the "123: " prefix each line gets. + maxStringLength += integerLog10(commandCount); + maxStringLength += 3; + + // conhost used to draw the command list with a size of 40x10, but at some point it switched over to dynamically + // sizing it depending on the history count and width of the entries. Back then it was implemented with the + // assumption that the code unit count equals the column count, which I kept because I don't want the TextBuffer + // class to expose how wide characters are, any more than necessary. It makes implementing Unicode support + // much harder, because things like combining marks and work zones may cause TextBuffer to end up deciding + // a piece of text has a different size than what you thought it had when measuring it on its own. + proposedSize.width = gsl::narrow_cast(std::clamp(maxStringLength, 40, til::CoordTypeMax)); + proposedSize.height = std::clamp(commandCount, 10, 20); + break; + } + default: + assert(false); + return; + } + + // Subtract 2 because we need to draw the border around our content. We must return early if we're + // unable to do so, otherwise the remaining code fails because the size would be zero/negative. + const til::size contentSize{ + std::min(proposedSize.width, viewportSize.width - 2), + std::min(proposedSize.height, viewportSize.height - 2), + }; + if (!contentSize) + { + return; + } - if (_originalCursorPosition.x == -1) + const auto widthSizeT = gsl::narrow_cast(contentSize.width + 2); + const auto heightSizeT = gsl::narrow_cast(contentSize.height + 2); + const til::point contentOrigin{ + (viewportSize.width - contentSize.width) / 2 + viewportOrigin.x, + (viewportSize.height - contentSize.height) / 2 + viewportOrigin.y, + }; + const til::rect contentRect{ + contentOrigin, + contentSize, + }; + const auto backupRect = Viewport::FromExclusive({ + contentRect.left - 1, + contentRect.top - 1, + contentRect.right + 1, + contentRect.bottom + 1, + }); + + auto& popup = _popups.emplace_back(kind, contentRect, backupRect, std::vector{ widthSizeT * heightSizeT }); + + // Create a backup of the TextBuffer parts we're scribbling over. + // We need to flush the buffer to ensure we capture the latest contents. + // NOTE: This may theoretically modify popup.backupRect (practically unlikely). + _flushBuffer(); + THROW_IF_FAILED(ServiceLocator::LocateGlobals().api->ReadConsoleOutputWImpl(_screenInfo, popup.backup, backupRect, popup.backupRect)); + + // Draw the border around our content and fill it with whitespace to prepare it for future usage. + { + const auto attributes = _screenInfo.GetPopupAttributes(); + + RowWriteState state{ + .columnBegin = contentRect.left - 1, + .columnLimit = contentRect.right + 1, + }; + + // top line ┌───┐ + std::wstring buffer; + buffer.assign(widthSizeT, L'─'); + buffer.front() = L'┌'; + buffer.back() = L'┐'; + state.text = buffer; + textBuffer.Write(contentRect.top - 1, attributes, state); + + // bottom line └───┘ + buffer.front() = L'└'; + buffer.back() = L'┘'; + state.text = buffer; + textBuffer.Write(contentRect.bottom, attributes, state); + + // middle lines │ │ + buffer.assign(widthSizeT, L' '); + buffer.front() = L'│'; + buffer.back() = L'│'; + for (til::CoordType y = contentRect.top; y < contentRect.bottom; ++y) { - _originalCursorPosition = _screenInfo.GetTextBuffer().GetCursor().GetPosition(); + state.text = buffer; + textBuffer.Write(y, attributes, state); } + } - if (commandLineEditingKeys) - { - // TODO: this is super weird for command line popups only - _unicode = isUnicode; + switch (kind) + { + case PopupKind::CopyToChar: + _popupDrawPrompt(popup, ID_CONSOLE_MSGCMDLINEF2); + break; + case PopupKind::CopyFromChar: + _popupDrawPrompt(popup, ID_CONSOLE_MSGCMDLINEF4); + break; + case PopupKind::CommandNumber: + popup.commandNumber.buffer.fill(' '); + popup.commandNumber.bufferSize = 0; + _popupDrawPrompt(popup, ID_CONSOLE_MSGCMDLINEF9); + break; + case PopupKind::CommandList: + popup.commandList.height = std::min(contentSize.height, _history->GetNumberOfCommands()); + popup.commandList.selected = _history->LastDisplayed; + popup.commandList.top = popup.commandList.selected - contentSize.height / 2; + _popupDrawCommandList(popup); + break; + default: + assert(false); + } + + // If this is the first popup to be shown, stop the cursor from appearing/blinking + if (_popups.size() == 1) + { + textBuffer.GetCursor().SetIsPopupShown(true); + } +} +catch (...) +{ + LOG_CAUGHT_EXCEPTION(); + // Using _popupDone() is a convenient way to restore the buffer contents if anything in this call failed. + // This could technically dismiss an unrelated popup that was already in _popups, but reaching this point is unlikely anyways. + _popupDone(); +} - _pdwNumBytes = &numBytes; +// Dismisses all current popups at once. Right now we don't need support for just dismissing the topmost popup. +// In fact, there's only a single situation right now where there can be >1 popup: +// Pressing F7 followed by F9 (CommandNumber on top of CommandList). +void COOKED_READ_DATA::_popupDone() +{ + while (!_popups.empty()) + { + auto& popup = _popups.back(); - Status = CommandLine::Instance().ProcessCommandLine(*this, wch, keyState); - if (Status == CONSOLE_STATUS_READ_COMPLETE || Status == CONSOLE_STATUS_WAIT) - { - break; - } - if (FAILED_NTSTATUS(Status)) - { - if (Status == CONSOLE_STATUS_WAIT_NO_BLOCK) - { - Status = CONSOLE_STATUS_WAIT; - } - else - { - _bytesRead = 0; - } - break; - } - } - else + // Restore TextBuffer contents. They could be empty if _popupPush() + // threw an exception in the middle of construction. + if (!popup.backup.empty()) { - if (ProcessInput(wch, keyState, Status)) - { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.Flags |= CONSOLE_IGNORE_NEXT_KEYUP; - break; - } + [[maybe_unused]] Viewport unused; + LOG_IF_FAILED(ServiceLocator::LocateGlobals().api->WriteConsoleOutputWImpl(_screenInfo, popup.backup, popup.backupRect, unused)); } + + _popups.pop_back(); } - return Status; + + // Restore cursor blinking. + _screenInfo.GetTextBuffer().GetCursor().SetIsPopupShown(false); } -// Routine Description: -// - handles any tasks that need to be completed after the read input loop finishes -// Arguments: -// - isUnicode - Treat as UCS-2 unicode or use Input CP to convert when done. -// - numBytes - On in, the number of bytes available in the client -// buffer. On out, the number of bytes consumed in the client buffer. -// - controlKeyState - For some types of reads, this is the modifier key state with the last button press. -// Return Value: -// - Status code that indicates success, out of memory, etc. -[[nodiscard]] NTSTATUS COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState) noexcept +bool COOKED_READ_DATA::_popupHandleInput(wchar_t wch, uint16_t vkey, DWORD modifiers) +{ + if (_popups.empty()) + { + assert(false); // Don't call this function. + return false; + } + + auto& popup = _popups.back(); + switch (popup.kind) + { + case PopupKind::CopyToChar: + _popupHandleCopyToCharInput(popup, wch, vkey, modifiers); + return false; + case PopupKind::CopyFromChar: + _popupHandleCopyFromCharInput(popup, wch, vkey, modifiers); + return false; + case PopupKind::CommandNumber: + _popupHandleCommandNumberInput(popup, wch, vkey, modifiers); + return false; + case PopupKind::CommandList: + return _popupHandleCommandListInput(popup, wch, vkey, modifiers); + default: + return false; + } +} + +void COOKED_READ_DATA::_popupHandleCopyToCharInput(Popup& /*popup*/, const wchar_t wch, const uint16_t vkey, const DWORD /*modifiers*/) { - std::span writer{ _userBuffer, _userBufferSize }; - std::wstring_view input{ _backupLimit, _bytesRead / sizeof(wchar_t) }; - DWORD LineCount = 1; + if (vkey == VK_ESCAPE) + { + _popupDone(); + return; + } - if (_echoInput) + if (wch) { - const auto idx = input.find(UNICODE_CARRIAGERETURN); - if (idx != decltype(input)::npos) + const auto cmd = _history->GetLastCommand(); + const auto idx = cmd.find(wch, _bufferCursor); + + if (idx != decltype(cmd)::npos) { - if (_commandHistory) - { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - LOG_IF_FAILED(_commandHistory->Add({ _backupLimit, idx }, WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP))); - } + // When we enter this if condition it's guaranteed that _bufferCursor must be + // smaller than idx, which in turn implies that it's smaller than cmd.size(). + // As such, calculating length is safe and str.size() == length. + const auto count = idx - _bufferCursor; + _buffer.replace(_bufferCursor, count, cmd, _bufferCursor, count); + _bufferCursor += count; + _markAsDirty(); + } - Tracing::s_TraceCookedRead(_clientProcess, _backupLimit, base::saturated_cast(idx)); + _popupDone(); + } +} - // Don't be fooled by ProcessAliases only taking one argument. It rewrites multiple - // class members on return, including `_bytesRead`, requiring us to reconstruct `input`. - ProcessAliases(LineCount); - input = { _backupLimit, _bytesRead / sizeof(wchar_t) }; +void COOKED_READ_DATA::_popupHandleCopyFromCharInput(Popup& /*popup*/, const wchar_t wch, const uint16_t vkey, const DWORD /*modifiers*/) +{ + if (vkey == VK_ESCAPE) + { + _popupDone(); + return; + } - // The exact reasons for this are unclear to me (the one writing this comment), but this code used to - // split the contents of a multiline alias (for instance `doskey test=echo foo$Techo bar$Techo baz`) - // into multiple separate read outputs, ensuring that the client receives them line by line. - // - // This code first truncates the `input` to only contain the first line, so that Consume() below only - // writes that line into the user buffer. We'll later store the remainder in SaveMultilinePendingInput(). - if (LineCount > 1) - { - // ProcessAliases() is supposed to end each line with \r\n. If it doesn't we might as well fail-fast. - const auto firstLineEnd = input.find(UNICODE_LINEFEED) + 1; - input = input.substr(0, std::min(input.size(), firstLineEnd)); - } + if (wch) + { + const auto idx = _buffer.find(wch, _bufferCursor); + _buffer.erase(_bufferCursor, std::min(idx, _buffer.size()) - _bufferCursor); + _markAsDirty(); + _popupDone(); + } +} + +void COOKED_READ_DATA::_popupHandleCommandNumberInput(Popup& popup, const wchar_t wch, const uint16_t vkey, const DWORD /*modifiers*/) +{ + if (vkey == VK_ESCAPE) + { + _popupDone(); + return; + } + + if (wch == UNICODE_CARRIAGERETURN) + { + popup.commandNumber.buffer[popup.commandNumber.bufferSize++] = L'\0'; + _replaceBuffer(_history->RetrieveNth(std::stoi(popup.commandNumber.buffer.data()))); + _popupDone(); + return; + } + + if (wch >= L'0' && wch <= L'9') + { + if (popup.commandNumber.bufferSize < CommandNumberMaxInputLength) + { + popup.commandNumber.buffer[popup.commandNumber.bufferSize++] = wch; } } + else if (wch == UNICODE_BACKSPACE) + { + if (popup.commandNumber.bufferSize > 0) + { + popup.commandNumber.buffer[--popup.commandNumber.bufferSize] = L' '; + } + } + else + { + return; + } - const auto inputSizeBefore = input.size(); - GetInputBuffer()->Consume(isUnicode, input, writer); + RowWriteState state{ + .text = { popup.commandNumber.buffer.data(), CommandNumberMaxInputLength }, + .columnBegin = popup.contentRect.right - CommandNumberMaxInputLength, + .columnLimit = popup.contentRect.right, + }; + _screenInfo.GetTextBuffer().Write(popup.contentRect.top, _screenInfo.GetPopupAttributes(), state); +} + +bool COOKED_READ_DATA::_popupHandleCommandListInput(Popup& popup, const wchar_t wch, const uint16_t vkey, const DWORD modifiers) +{ + auto& cl = popup.commandList; - if (LineCount > 1) + if (wch == UNICODE_CARRIAGERETURN) { - // This is a continuation of the above identical if condition. - // We've truncated the `input` slice and now we need to restore it. - const auto inputSizeAfter = input.size(); - const auto amountConsumed = inputSizeBefore - inputSizeAfter; - input = { _backupLimit, _bytesRead / sizeof(wchar_t) }; - input = input.substr(std::min(input.size(), amountConsumed)); - GetInputReadHandleData()->SaveMultilinePendingInput(input); + _buffer.assign(_history->RetrieveNth(cl.selected)); + _buffer.append(L"\r\n"); + _bufferCursor = _buffer.size(); + _markAsDirty(); + _popupDone(); + return true; } - else if (!input.empty()) + + switch (vkey) { - GetInputReadHandleData()->SavePendingInput(input); + case VK_F9: + _popupPush(PopupKind::CommandNumber); + return false; + case VK_ESCAPE: + _popupDone(); + return false; + case VK_LEFT: + case VK_RIGHT: + _replaceBuffer(_history->RetrieveNth(cl.selected)); + _popupDone(); + return false; + case VK_UP: + if (WI_IsFlagSet(modifiers, SHIFT_PRESSED)) + { + _history->Swap(cl.selected, cl.selected - 1); + } + cl.selected--; + break; + case VK_DOWN: + if (WI_IsFlagSet(modifiers, SHIFT_PRESSED)) + { + _history->Swap(cl.selected, cl.selected + 1); + } + cl.selected++; + break; + case VK_HOME: + cl.selected = 0; + break; + case VK_END: + cl.selected = INT_MAX; + break; + case VK_PRIOR: + cl.selected -= popup.contentRect.height(); + break; + case VK_NEXT: + cl.selected += popup.contentRect.height(); + break; + default: + return false; } - numBytes = _userBufferSize - writer.size(); - controlKeyState = _controlKeyState; - return STATUS_SUCCESS; + _popupDrawCommandList(popup); + return false; } -void COOKED_READ_DATA::MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) +void COOKED_READ_DATA::_popupDrawPrompt(const Popup& popup, const UINT id) const { - // See the comment in WaitBlock.cpp for more information. - if (_userBuffer == oldBuffer) + const auto text = _LoadString(id); + RowWriteState state{ + .text = text, + .columnBegin = popup.contentRect.left, + .columnLimit = popup.contentRect.right, + }; + _screenInfo.GetTextBuffer().Write(popup.contentRect.top, _screenInfo.GetPopupAttributes(), state); +} + +void COOKED_READ_DATA::_popupDrawCommandList(Popup& popup) const +{ + assert(popup.kind == PopupKind::CommandList); + + auto& cl = popup.commandList; + const auto num = _history->GetNumberOfCommands(); + const auto w = popup.contentRect.narrow_width(); + + { + cl.selected = std::clamp(cl.selected, 0, num - 1); + + if (cl.top > cl.selected) + { + cl.top = cl.selected; + } + else + { + const auto bottom = cl.top + cl.height; + if (bottom <= cl.selected) + { + cl.top = cl.selected - cl.height + 1; + } + } + + cl.top = std::clamp(cl.top, 0, num - cl.height); + } + + std::wstring buffer; + buffer.reserve(w * 2); + + const auto& attrRegular = _screenInfo.GetPopupAttributes(); + auto attrInverted = attrRegular; + attrInverted.Invert(); + + RowWriteState state{ + .columnBegin = popup.contentRect.left, + .columnLimit = popup.contentRect.right, + }; + + for (til::CoordType off = 0; off < cl.height; ++off) { - _userBuffer = static_cast(newBuffer); + const auto y = popup.contentRect.top + off; + const auto historyIndex = cl.top + off; + const auto& attr = historyIndex == cl.selected ? attrInverted : attrRegular; + + buffer.clear(); + buffer.append(std::to_wstring(historyIndex)); + buffer.append(L": "); + buffer.append(_history->GetNth(historyIndex)); + buffer.append(w, L' '); + + state.text = buffer; + _screenInfo.GetTextBuffer().Write(y, attr, state); } } diff --git a/src/host/readDataCooked.hpp b/src/host/readDataCooked.hpp index be2e8645bc19..49b341dc0abe 100644 --- a/src/host/readDataCooked.hpp +++ b/src/host/readDataCooked.hpp @@ -1,29 +1,5 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- readDataCooked.hpp - -Abstract: -- This file defines the read data structure for reading the command line. -- Cooked reads specifically refer to when the console host acts as a command line on behalf - of another console application (e.g. aliases, command history, completion, line manipulation, etc.) -- The data struct will help store context across multiple calls or in the case of a wait condition. -- Wait conditions happen frequently for cooked reads because they're virtually always waiting for - the user to finish "manipulating" the edit line before hitting enter and submitting the final - result to the client application. -- A cooked read is also limited specifically to string/textual information. Only keyboard-type input applies. -- This can be triggered via ReadConsole A/W and ReadFile A/W calls. - -Author: -- Austin Diviness (AustDi) 1-Mar-2017 -- Michael Niksa (MiNiksa) 1-Mar-2017 - -Revision History: -- Pulled from original authoring by Therese Stowell (ThereseS, 1990) -- Separated from cmdline.h/cmdline.cpp (AustDi, 2017) ---*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. #pragma once @@ -43,122 +19,124 @@ class COOKED_READ_DATA final : public ReadData _In_ const std::wstring_view initialData, _In_ ConsoleProcessHandle* const pClientProcess); - ~COOKED_READ_DATA() override; - COOKED_READ_DATA(COOKED_READ_DATA&&) = default; - - bool AtEol() const noexcept; - - void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) override; + void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) noexcept override; bool Notify(const WaitTerminationReason TerminationReason, const bool fIsUnicode, _Out_ NTSTATUS* const pReplyStatus, _Out_ size_t* const pNumBytes, _Out_ DWORD* const pControlKeyState, - _Out_ void* const pOutputData) override; - - std::span SpanAtPointer(); - std::span SpanWholeBuffer(); - - size_t Write(const std::wstring_view wstr); - - void ProcessAliases(DWORD& lineCount); - - [[nodiscard]] HRESULT Read(const bool isUnicode, - size_t& numBytes, - ULONG& controlKeyState) noexcept; - - bool ProcessInput(const wchar_t wch, - const DWORD keyState, - NTSTATUS& status); - - CommandHistory& History() noexcept; - bool HasHistory() const noexcept; - - const size_t& VisibleCharCount() const noexcept; - size_t& VisibleCharCount() noexcept; - - SCREEN_INFORMATION& ScreenInfo() noexcept; - - til::point OriginalCursorPosition() const noexcept; - til::point& OriginalCursorPosition() noexcept; - - til::point& BeforeDialogCursorPosition() noexcept; - - bool IsEchoInput() const noexcept; - bool IsInsertMode() const noexcept; - void SetInsertMode(const bool mode) noexcept; - bool IsUnicode() const noexcept; - - size_t UserBufferSize() const noexcept; - - wchar_t* BufferStartPtr() noexcept; - wchar_t* BufferCurrentPtr() noexcept; - void SetBufferCurrentPtr(wchar_t* ptr) noexcept; + _Out_ void* const pOutputData) noexcept override; - const size_t& BytesRead() const noexcept; - size_t& BytesRead() noexcept; + bool Read(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState); - const size_t& InsertionPoint() const noexcept; - size_t& InsertionPoint() noexcept; + void EraseBeforeResize(); + void RedrawAfterResize(); - void SetReportedByteCount(const size_t count) noexcept; - - void Erase() noexcept; - size_t SavePromptToUserBuffer(const size_t cch); - void SavePendingInput(const size_t cch, const bool multiline); - -#if UNIT_TESTING - friend class CommandLineTests; - friend class CopyToCharPopupTests; - friend class CommandNumberPopupTests; - friend class CommandListPopupTests; - friend class PopupTestHelper; -#endif + void SetInsertMode(bool insertMode) noexcept; + bool IsEmpty() const noexcept; + bool PresentingPopup() const noexcept; + til::point_span GetBoundaries() const noexcept; private: - size_t _bufferSize; // size in bytes - size_t _bytesRead; - - // insertion position into the buffer (where the conceptual prompt cursor is) - size_t _currentPosition; // char position, not byte position - - wchar_t* _bufPtr; // current position to insert chars at - - // should be const. the first char of the buffer - wchar_t* _backupLimit; - - size_t _userBufferSize; // doubled size in ansi case - char* _userBuffer; - - size_t* _pdwNumBytes; + static constexpr uint8_t CommandNumberMaxInputLength = 5; + + enum class PopupKind + { + // Copies text from the previous command between the current cursor position and the first instance + // of a given char (but not including it) into the current prompt line at the current cursor position. + // Basically, F3 and this prompt have identical behavior, but the prompt searches for a terminating character. + // + // Let's say your last command was: + // echo hello + // And you type the following with the cursor at "^": + // echo abcd efgh + // ^ + // Then this command, given the char "o" will turn it into + // echo hell efgh + CopyToChar, + // Erases text between the current cursor position and the first instance of a given char (but not including it). + // It's unknown to me why this is was historically called "copy from char" as it conhost never copied anything. + CopyFromChar, + // Let's you choose to replace the current prompt with one from the command history by index. + CommandNumber, + // Let's you choose to replace the current prompt with one from the command history via a + // visual select dialog. Among all the popups this one is the most widely used one by far. + CommandList, + }; + + struct Popup + { + PopupKind kind; + + // The inner rectangle of the popup, excluding the border that we draw. + // In absolute TextBuffer coordinates. + til::rect contentRect; + // The area we've backed up and need to restore when we dismiss the popup. + // It'll practically always be 1 larger than contentRect in all 4 directions. + Microsoft::Console::Types::Viewport backupRect; + // The backed up buffer contents. Uses CHAR_INFO for convenience. + std::vector backup; + + // Using a std::variant would be preferable in modern C++ but is practically equally annoying to use. + union + { + // Used by PopupKind::CommandNumber + struct + { + // Keep 1 char space for the trailing \0 char. + std::array buffer; + uint8_t bufferSize; + } commandNumber; + + // Used by PopupKind::CommandList + struct + { + til::CoordType height; + CommandHistory::Index top; + CommandHistory::Index selected; + } commandList; + }; + }; + + bool _readCharInputLoop(); + bool _handleChar(wchar_t wch, DWORD modifiers); + void _handleVkey(uint16_t vkey, DWORD modifiers); + void _handlePostCharInputLoop(bool isUnicode, size_t& numBytes, ULONG& controlKeyState); + void _markAsDirty(); + void _flushBuffer(); + void _erase(size_t count) const; + til::CoordType _writeChars(const std::wstring_view& text) const; + til::point _offsetPosition(til::point pos, til::CoordType distance) const; + til::point _offsetCursorPosition(til::CoordType distance) const; + void _unwindCursorPosition(til::CoordType distance) const; + void _replaceBuffer(const std::wstring_view& str); + + void _popupPush(PopupKind kind); + void _popupDone(); + void _popupHandleCopyToCharInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); + void _popupHandleCopyFromCharInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); + void _popupHandleCommandNumberInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); + bool _popupHandleCommandListInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); + bool _popupHandleInput(wchar_t wch, uint16_t vkey, DWORD keyState); + void _popupDrawPrompt(const Popup& popup, UINT id) const; + void _popupDrawCommandList(Popup& popup) const; - std::unique_ptr _buffer; + SCREEN_INFORMATION& _screenInfo; + std::span _userBuffer; std::wstring _exeName; + ConsoleProcessHandle* _processHandle = nullptr; + CommandHistory* _history = nullptr; + ULONG _ctrlWakeupMask = 0; + ULONG _controlKeyState = 0; std::unique_ptr _tempHandle; - // TODO MSFT:11285829 make this something other than a deletable pointer - // non-ownership pointer - CommandHistory* _commandHistory; - - ULONG _controlKeyState; - ULONG _ctrlWakeupMask; - size_t _visibleCharCount; // TODO MSFT:11285829 is this cells or glyphs? ie. is a wide char counted as 1 or 2? - SCREEN_INFORMATION& _screenInfo; - - // Note that cookedReadData's _originalCursorPosition is the position before ANY text was entered on the edit line. - til::point _originalCursorPosition; - til::point _beforeDialogCursorPosition; // Currently only used for F9 (ProcessCommandNumberInput) since it's the only pop-up to move the cursor when it starts. - - const bool _echoInput; - const bool _lineInput; - const bool _processedInput; - bool _insertMode; - bool _unicode; - - ConsoleProcessHandle* const _clientProcess; - - [[nodiscard]] NTSTATUS _readCharInputLoop(const bool isUnicode, size_t& numBytes) noexcept; + std::wstring _buffer; + size_t _bufferCursor = 0; + bool _insertMode = false; + til::CoordType _distanceCursor = 0; + til::CoordType _distanceEnd = 0; + bool _bufferDirty = false; - [[nodiscard]] NTSTATUS _handlePostCharInputLoop(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState) noexcept; + std::vector _popups; }; diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 2ddd2e4c615a..3def5c63d76d 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -873,12 +873,7 @@ void SCREEN_INFORMATION::ProcessResizeWindow(const til::rect* const prcClientNew // 1.a In some modes, the screen buffer size needs to change on window size, // so do that first. - // _AdjustScreenBuffer might hide the commandline. If it does so, it'll - // return S_OK instead of S_FALSE. In that case, we'll need to re-show - // the commandline ourselves once the viewport size is updated. - // (See 1.b below) - const auto adjustBufferSizeResult = _AdjustScreenBuffer(prcClientNew); - LOG_IF_FAILED(adjustBufferSizeResult); + LOG_IF_FAILED(_AdjustScreenBuffer(prcClientNew)); // 2. Now calculate how large the new viewport should be til::size coordViewportSize; @@ -888,16 +883,6 @@ void SCREEN_INFORMATION::ProcessResizeWindow(const til::rect* const prcClientNew // The old/new comparison is to figure out which side the window was resized from. _AdjustViewportSize(prcClientNew, prcClientOld, &coordViewportSize); - // 1.b If we did actually change the buffer size, then we need to show the - // commandline again. We hid it during _AdjustScreenBuffer, but we - // couldn't turn it back on until the Viewport was updated to the new - // size. See MSFT:19976291 - if (SUCCEEDED(adjustBufferSizeResult) && adjustBufferSizeResult != S_FALSE) - { - auto& commandLine = CommandLine::Instance(); - commandLine.Show(); - } - // 4. Finally, update the scroll bars. UpdateScrollBars(); @@ -1016,23 +1001,7 @@ void SCREEN_INFORMATION::ProcessResizeWindow(const til::rect* const prcClientNew // Only attempt to modify the buffer if something changed. Expensive operation. if (coordBufferSizeOld != coordBufferSizeNew) { - auto& commandLine = CommandLine::Instance(); - - // TODO: Deleting and redrawing the command line during resizing can cause flickering. See: http://osgvsowi/658439 - // 1. Delete input string if necessary (see menu.c) - commandLine.Hide(FALSE); - - const auto savedCursorVisibility = _textBuffer->GetCursor().IsVisible(); - _textBuffer->GetCursor().SetIsVisible(false); - - // 2. Call the resize screen buffer method (expensive) to redimension the backing buffer (and reflow) LOG_IF_FAILED(ResizeScreenBuffer(coordBufferSizeNew, FALSE)); - - // MSFT:19976291 Don't re-show the commandline here. We need to wait for - // the viewport to also get resized before we can re-show the commandline. - // ProcessResizeWindow will call commandline.Show() for us. - _textBuffer->GetCursor().SetIsVisible(savedCursorVisibility); - // Return S_OK, to indicate we succeeded and actually did something. hr = S_OK; } @@ -1540,8 +1509,17 @@ bool SCREEN_INFORMATION::IsMaximizedY() const // cancel any active selection before resizing or it will not necessarily line up with the new buffer positions Selection::Instance().ClearSelection(); - // cancel any popups before resizing or they will not necessarily line up with new buffer positions - CommandLine::Instance().EndAllPopups(); + if (gci.HasPendingCookedRead()) + { + gci.CookedReadData().EraseBeforeResize(); + } + const auto cookedReadRestore = wil::scope_exit([]() { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (gci.HasPendingCookedRead()) + { + gci.CookedReadData().RedrawAfterResize(); + } + }); const auto fWrapText = gci.GetWrapText(); // GH#3493: Don't reflow the alt buffer. @@ -1937,10 +1915,7 @@ void SCREEN_INFORMATION::_handleDeferredResize(SCREEN_INFORMATION& siMain) // too much work. if (newBufferSize != oldScreenBufferSize) { - auto& commandLine = CommandLine::Instance(); - commandLine.Hide(FALSE); LOG_IF_FAILED(siMain.ResizeScreenBuffer(newBufferSize, TRUE)); - commandLine.Show(); } // Not that the buffer is smaller, actually make sure to resize our @@ -2311,56 +2286,6 @@ void SCREEN_INFORMATION::SetTerminalConnection(_In_ VtEngine* const pTtyConnecti } } -// Routine Description: -// - This routine copies a rectangular region from the screen buffer. no clipping is done. -// Arguments: -// - viewport - rectangle in source buffer to copy -// Return Value: -// - output cell rectangle copy of screen buffer data -// Note: -// - will throw exception on error. -OutputCellRect SCREEN_INFORMATION::ReadRect(const Viewport viewport) const -{ - // If the viewport given doesn't fit inside this screen, it's not a valid argument. - THROW_HR_IF(E_INVALIDARG, !GetBufferSize().IsInBounds(viewport)); - - OutputCellRect result(viewport.Height(), viewport.Width()); - const OutputCell paddingCell{ std::wstring_view{ &UNICODE_SPACE, 1 }, {}, GetAttributes() }; - for (til::CoordType rowIndex = 0, height = viewport.Height(); rowIndex < height; ++rowIndex) - { - auto location = viewport.Origin(); - location.y += rowIndex; - - auto data = GetCellLineDataAt(location); - const auto span = result.GetRow(rowIndex); - auto it = span.begin(); - - // Copy row data while there still is data and we haven't run out of rect to store it into. - while (data && it < span.end()) - { - *it++ = *data++; - } - - // Pad out any remaining space. - while (it < span.end()) - { - *it++ = paddingCell; - } - - // if we're clipping a dbcs char then don't include it, add a space instead - if (span.begin()->DbcsAttr() == DbcsAttribute::Trailing) - { - *span.begin() = paddingCell; - } - if (span.rbegin()->DbcsAttr() == DbcsAttribute::Leading) - { - *span.rbegin() = paddingCell; - } - } - - return result; -} - // Routine Description: // - Writes cells to the output buffer at the cursor position. // Arguments: diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 23e285ff5abf..455036a993d7 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -123,8 +123,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console static void s_InsertScreenBuffer(_In_ SCREEN_INFORMATION* const pScreenInfo); static void s_RemoveScreenBuffer(_In_ SCREEN_INFORMATION* const pScreenInfo); - OutputCellRect ReadRect(const Microsoft::Console::Types::Viewport location) const; - TextBufferCellIterator GetCellDataAt(const til::point at) const; TextBufferCellIterator GetCellLineDataAt(const til::point at) const; TextBufferCellIterator GetCellDataAt(const til::point at, const Microsoft::Console::Types::Viewport limit) const; diff --git a/src/host/scrolling.cpp b/src/host/scrolling.cpp index add5a205e804..2e42d620767a 100644 --- a/src/host/scrolling.cpp +++ b/src/host/scrolling.cpp @@ -208,7 +208,7 @@ bool Scrolling::s_HandleKeyScrollingEvent(const INPUT_KEY_INFO* const pKeyInfo) const auto VirtualKeyCode = pKeyInfo->GetVirtualKey(); const auto fIsCtrlPressed = pKeyInfo->IsCtrlPressed(); - const auto fIsEditLineEmpty = CommandLine::IsEditLineEmpty(); + const auto fIsEditLineEmpty = !gci.HasPendingCookedRead() || gci.CookedReadData().IsEmpty(); // If escape, enter or ctrl-c, cancel scroll. if (VirtualKeyCode == VK_ESCAPE || diff --git a/src/host/selectionInput.cpp b/src/host/selectionInput.cpp index d85c5835bd05..0de91c3b1aa2 100644 --- a/src/host/selectionInput.cpp +++ b/src/host/selectionInput.cpp @@ -8,8 +8,6 @@ #include "../interactivity/inc/ServiceLocator.hpp" #include "../types/inc/convert.hpp" -#include - using namespace Microsoft::Console::Types; using Microsoft::Console::Interactivity::ServiceLocator; // Routine Description: @@ -949,49 +947,27 @@ bool Selection::_HandleMarkModeSelectionNav(const INPUT_KEY_INFO* const pInputKe [[nodiscard]] bool Selection::s_GetInputLineBoundaries(_Out_opt_ til::point* const pcoordInputStart, _Out_opt_ til::point* const pcoordInputEnd) { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto bufferSize = gci.GetActiveOutputBuffer().GetBufferSize(); - - auto& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer(); - - const auto pendingCookedRead = gci.HasPendingCookedRead(); - const auto isVisible = CommandLine::Instance().IsVisible(); - - // if we have no read data, we have no input line. - if (!pendingCookedRead || gci.CookedReadData().VisibleCharCount() == 0 || !isVisible) - { - return false; - } - - const auto& cookedRead = gci.CookedReadData(); - const auto coordStart = cookedRead.OriginalCursorPosition(); - auto coordEnd = cookedRead.OriginalCursorPosition(); - if (coordEnd.x < 0 && coordEnd.y < 0) + if (gci.HasPendingCookedRead()) { - // if the original cursor position from the input line data is invalid, then the buffer cursor position is the final position - coordEnd = textBuffer.GetCursor().GetPosition(); - } - else - { - // otherwise, we need to add the number of characters in the input line to the original cursor position - bufferSize.MoveInBounds(gsl::narrow(cookedRead.VisibleCharCount()), coordEnd); - } - - // - 1 so the coordinate is on top of the last position of the text, not one past it. - bufferSize.MoveInBounds(-1, coordEnd); - - if (pcoordInputStart != nullptr) - { - pcoordInputStart->x = coordStart.x; - pcoordInputStart->y = coordStart.y; - } - - if (pcoordInputEnd != nullptr) - { - *pcoordInputEnd = coordEnd; + auto boundaries = gci.CookedReadData().GetBoundaries(); + if (boundaries.start < boundaries.end) + { + if (pcoordInputStart != nullptr) + { + *pcoordInputStart = boundaries.start; + } + if (pcoordInputEnd != nullptr) + { + // - 1 so the coordinate is on top of the last position of the text, not one past it. + gci.GetActiveOutputBuffer().GetBufferSize().MoveInBounds(-1, boundaries.end); + *pcoordInputEnd = boundaries.end; + } + return true; + } } - return true; + return false; } // Routine Description: diff --git a/src/host/server.h b/src/host/server.h index ebc2ac100919..3332cc6b383c 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -16,20 +16,17 @@ Revision History: #pragma once +#include "conimeinfo.h" +#include "CursorBlinker.hpp" #include "IIoProvider.hpp" - +#include "readDataCooked.hpp" #include "settings.hpp" - -#include "conimeinfo.h" #include "VtIo.hpp" -#include "CursorBlinker.hpp" - +#include "../audio/midi/MidiAudio.hpp" +#include "../host/RenderData.hpp" #include "../server/ProcessList.h" #include "../server/WaitQueue.h" -#include "../host/RenderData.hpp" -#include "../audio/midi/MidiAudio.hpp" - #include // clang-format off @@ -91,8 +88,6 @@ class CONSOLE_INFORMATION : DWORD Flags = 0; - std::atomic PopupCount = 0; - // the following fields are used for ansi-unicode translation UINT CP = 0; UINT OutputCP = 0; @@ -121,6 +116,7 @@ class CONSOLE_INFORMATION : bool IsInVtIoMode() const; bool HasPendingCookedRead() const noexcept; + bool HasPendingPopup() const noexcept; const COOKED_READ_DATA& CookedReadData() const noexcept; COOKED_READ_DATA& CookedReadData() noexcept; void SetCookedReadData(COOKED_READ_DATA* readData) noexcept; @@ -167,8 +163,6 @@ class CONSOLE_INFORMATION : MidiAudio _midiAudio; }; -#define ConsoleLocked() (ServiceLocator::LocateGlobals()->getConsoleInformation()->ConsoleLock.OwningThread == NtCurrentTeb()->ClientId.UniqueThread) - #define CONSOLE_STATUS_WAIT 0xC0030001 #define CONSOLE_STATUS_READ_COMPLETE 0xC0030002 #define CONSOLE_STATUS_WAIT_NO_BLOCK 0xC0030003 diff --git a/src/host/sources.inc b/src/host/sources.inc index 173dae000cb7..bf3b16bcae9d 100644 --- a/src/host/sources.inc +++ b/src/host/sources.inc @@ -46,7 +46,6 @@ SOURCES = \ ..\scrolling.cpp \ ..\cmdline.cpp \ ..\CursorBlinker.cpp \ - ..\popup.cpp \ ..\alias.cpp \ ..\history.cpp \ ..\VtIo.cpp \ @@ -89,10 +88,6 @@ SOURCES = \ ..\conareainfo.cpp \ ..\conimeinfo.cpp \ ..\ConsoleArguments.cpp \ - ..\CommandNumberPopup.cpp \ - ..\CommandListPopup.cpp \ - ..\CopyFromCharPopup.cpp \ - ..\CopyToCharPopup.cpp \ ..\VtApiRoutines.cpp \ diff --git a/src/host/stream.cpp b/src/host/stream.cpp index c98ef9eaf08d..823cc099c597 100644 --- a/src/host/stream.cpp +++ b/src/host/stream.cpp @@ -179,93 +179,6 @@ using Microsoft::Console::Interactivity::ServiceLocator; } } -// Routine Description: -// - This routine returns the total number of screen spaces the characters up to the specified character take up. -til::CoordType RetrieveTotalNumberOfSpaces(const til::CoordType sOriginalCursorPositionX, - _In_reads_(ulCurrentPosition) const WCHAR* const pwchBuffer, - _In_ size_t ulCurrentPosition) -{ - auto XPosition = sOriginalCursorPositionX; - til::CoordType NumSpaces = 0; - - for (size_t i = 0; i < ulCurrentPosition; i++) - { - const auto Char = pwchBuffer[i]; - - til::CoordType NumSpacesForChar; - if (Char == UNICODE_TAB) - { - NumSpacesForChar = NUMBER_OF_SPACES_IN_TAB(XPosition); - } - else if (IS_CONTROL_CHAR(Char)) - { - NumSpacesForChar = 2; - } - else if (IsGlyphFullWidth(Char)) - { - NumSpacesForChar = 2; - } - else - { - NumSpacesForChar = 1; - } - XPosition += NumSpacesForChar; - NumSpaces += NumSpacesForChar; - } - - return NumSpaces; -} - -// Routine Description: -// - This routine returns the number of screen spaces the specified character takes up. -til::CoordType RetrieveNumberOfSpaces(_In_ til::CoordType sOriginalCursorPositionX, - _In_reads_(ulCurrentPosition + 1) const WCHAR* const pwchBuffer, - _In_ size_t ulCurrentPosition) -{ - auto Char = pwchBuffer[ulCurrentPosition]; - if (Char == UNICODE_TAB) - { - til::CoordType NumSpaces = 0; - auto XPosition = sOriginalCursorPositionX; - - for (size_t i = 0; i <= ulCurrentPosition; i++) - { - Char = pwchBuffer[i]; - if (Char == UNICODE_TAB) - { - NumSpaces = NUMBER_OF_SPACES_IN_TAB(XPosition); - } - else if (IS_CONTROL_CHAR(Char)) - { - NumSpaces = 2; - } - else if (IsGlyphFullWidth(Char)) - { - NumSpaces = 2; - } - else - { - NumSpaces = 1; - } - XPosition += NumSpaces; - } - - return NumSpaces; - } - else if (IS_CONTROL_CHAR(Char)) - { - return 2; - } - else if (IsGlyphFullWidth(Char)) - { - return 2; - } - else - { - return 1; - } -} - // Routine Description: // - if we have leftover input, copy as much fits into the user's // buffer and return. we may have multi line input, if a macro @@ -380,7 +293,7 @@ NT_CATCH_RETURN() gci.SetCookedReadData(cookedReadData.get()); bytesRead = buffer.size_bytes(); // This parameter on the way in is the size to read, on the way out, it will be updated to what is actually read. - if (CONSOLE_STATUS_WAIT == cookedReadData->Read(unicode, bytesRead, controlKeyState)) + if (!cookedReadData->Read(unicode, bytesRead, controlKeyState)) { // memory will be cleaned up by wait queue waiter.reset(cookedReadData.release()); diff --git a/src/host/stream.h b/src/host/stream.h index b4c37d82a45a..a796bc43e7a7 100644 --- a/src/host/stream.h +++ b/src/host/stream.h @@ -35,16 +35,4 @@ Revision History: INPUT_READ_HANDLE_DATA& readHandleState, const bool unicode); -// Routine Description: -// - This routine returns the total number of screen spaces the characters up to the specified character take up. -til::CoordType RetrieveTotalNumberOfSpaces(const til::CoordType sOriginalCursorPositionX, - _In_reads_(ulCurrentPosition) const WCHAR* const pwchBuffer, - const size_t ulCurrentPosition); - -// Routine Description: -// - This routine returns the number of screen spaces the specified character takes up. -til::CoordType RetrieveNumberOfSpaces(_In_ til::CoordType sOriginalCursorPositionX, - _In_reads_(ulCurrentPosition + 1) const WCHAR* const pwchBuffer, - _In_ size_t ulCurrentPosition); - VOID UnblockWriteConsole(const DWORD dwReason); diff --git a/src/host/tracing.cpp b/src/host/tracing.cpp index bf54edbed997..998410cf247e 100644 --- a/src/host/tracing.cpp +++ b/src/host/tracing.cpp @@ -173,16 +173,17 @@ void Tracing::s_TraceInputRecord(const INPUT_RECORD& inputRecord) } } -void Tracing::s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, _In_reads_(cchCookedBufferLength) const wchar_t* pwchCookedBuffer, _In_ ULONG cchCookedBufferLength) +void Tracing::s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, const std::wstring_view& text) { if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, TraceKeywords::CookedRead)) { + const auto length = ::base::saturated_cast(text.size()); TraceLoggingWrite( g_hConhostV2EventTraceProvider, "CookedRead", TraceLoggingPid(pConsoleProcessHandle->dwProcessId, "AttachedProcessId"), - TraceLoggingCountedWideString(pwchCookedBuffer, cchCookedBufferLength, "ReadBuffer"), - TraceLoggingULong(cchCookedBufferLength, "ReadBufferLength"), + TraceLoggingCountedWideString(text.data(), length, "ReadBuffer"), + TraceLoggingULong(length, "ReadBufferLength"), TraceLoggingFileTime(pConsoleProcessHandle->GetProcessCreationTime(), "AttachedProcessCreationTime"), TraceLoggingKeyword(TIL_KEYWORD_TRACE), TraceLoggingKeyword(TraceKeywords::CookedRead)); diff --git a/src/host/tracing.hpp b/src/host/tracing.hpp index 8be4bf21b82f..03cf423f4d75 100644 --- a/src/host/tracing.hpp +++ b/src/host/tracing.hpp @@ -51,7 +51,7 @@ class Tracing static void s_TraceWindowMessage(const MSG& msg); static void s_TraceInputRecord(const INPUT_RECORD& inputRecord); - static void s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, _In_reads_(cchCookedBufferLength) const wchar_t* pwchCookedBuffer, _In_ ULONG cchCookedBufferLength); + static void s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, const std::wstring_view& text); static void s_TraceConsoleAttachDetach(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, _In_ bool bIsAttach); static void __stdcall TraceFailure(const wil::FailureInfo& failure) noexcept; diff --git a/src/host/ut_host/AliasTests.cpp b/src/host/ut_host/AliasTests.cpp index 6443dd22f63b..cf499bea89e2 100644 --- a/src/host/ut_host/AliasTests.cpp +++ b/src/host/ut_host/AliasTests.cpp @@ -111,167 +111,48 @@ class AliasTests // and match to our expected values. std::wstring alias(aliasName); std::wstring exe(exeName); + std::wstring original(originalString); std::wstring target; std::wstring expected; _RetrieveTargetExpectedPair(target, expected); - auto linesExpected = _ReplacePercentWithCRLF(expected); - - std::wstring original(originalString); + const auto linesExpected = _ReplacePercentWithCRLF(expected); Alias::s_TestAddAlias(exe, alias, target); - // Fill classic wchar_t[] buffer for interfacing with the MatchAndCopyAlias function - const auto bufferSize = 160ui16; - auto buffer = std::make_unique(bufferSize); - wcscpy_s(buffer.get(), bufferSize, original.data()); - - const auto cbBuffer = bufferSize * sizeof(wchar_t); - size_t bufferUsed = 0; - DWORD linesActual = 0; - // Run the match and copy function. - Alias::s_MatchAndCopyAliasLegacy(buffer.get(), - wcslen(buffer.get()) * sizeof(wchar_t), - buffer.get(), - cbBuffer, - bufferUsed, - exe, - linesActual); - - // Null terminate buffer for comparison - buffer[bufferUsed / sizeof(wchar_t)] = L'\0'; - - Log::Comment(String().Format(L"Expected: '%s'", expected.data())); - Log::Comment(String().Format(L"Actual : '%s'", buffer.get())); - - VERIFY_ARE_EQUAL(WEX::Common::String(expected.data()), WEX::Common::String(buffer.get())); + size_t linesActual = 0; + const auto actual = Alias::s_MatchAndCopyAlias(original, exe, linesActual); + VERIFY_ARE_EQUAL(expected, actual); VERIFY_ARE_EQUAL(linesExpected, linesActual); } - TEST_METHOD(TestMatchAndCopyTrailingCRLF) - { - const auto pwszSource = L"SourceWithoutCRLF\r\n"; - const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t); - - const size_t cchTarget = 60; - auto rgwchTarget = std::make_unique(cchTarget); - const auto cbTarget = cchTarget * sizeof(wchar_t); - wcscpy_s(rgwchTarget.get(), cchTarget, L"testtesttesttesttest"); - auto rgwchTargetBefore = std::make_unique(cchTarget); - wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); - - size_t cbTargetUsed = 0; - DWORD dwLines = 0; - - // Register the wrong alias name before we try. - std::wstring exe(L"exe.exe"); - std::wstring sourceWithoutCRLF(L"SourceWithoutCRLF"); - std::wstring target(L"someTarget"); - Alias::s_TestAddAlias(exe, sourceWithoutCRLF, target); - - const auto targetExpected = target + L"\r\n"; - const auto cbTargetExpected = targetExpected.size() * sizeof(wchar_t); // +2 for \r\n that will be added on replace. - - Alias::s_MatchAndCopyAliasLegacy(pwszSource, - cbSource, - rgwchTarget.get(), - cbTarget, - cbTargetUsed, - exe, - dwLines); - - // Terminate target buffer with \0 for comparison - rgwchTarget[cbTargetUsed] = L'\0'; - - VERIFY_ARE_EQUAL(cbTargetExpected, cbTargetUsed, L"Target bytes should be filled with target size."); - VERIFY_ARE_EQUAL(String(targetExpected.data()), String(rgwchTarget.get(), gsl::narrow(cbTargetUsed / sizeof(wchar_t))), L"Target string should be filled with target data."); - VERIFY_ARE_EQUAL(1u, dwLines, L"Line count should be 1."); - } - TEST_METHOD(TestMatchAndCopyInvalidExeName) { const auto pwszSource = L"Source"; - const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t); - - const size_t cchTarget = 12; - auto rgwchTarget = std::make_unique(cchTarget); - wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); - auto rgwchTargetBefore = std::make_unique(cchTarget); - wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); - - const auto cbTarget = cchTarget * sizeof(wchar_t); - auto cbTargetUsed = cbTarget; - - DWORD dwLines = 0; - const auto dwLinesBefore = dwLines; - + size_t dwLines = 1; std::wstring exeName; - - Alias::s_MatchAndCopyAliasLegacy(pwszSource, - cbSource, - rgwchTarget.get(), - cbTarget, - cbTargetUsed, - exeName, - dwLines); - - VERIFY_ARE_EQUAL(cbTarget, cbTargetUsed, L"Byte count shouldn't have changed with failure."); - VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count shouldn't have changed with failure."); - VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string shouldn't have changed with failure."); + const auto buffer = Alias::s_MatchAndCopyAlias(pwszSource, exeName, dwLines); + VERIFY_IS_TRUE(buffer.empty()); + VERIFY_ARE_EQUAL(1u, dwLines); } TEST_METHOD(TestMatchAndCopyExeNotFound) { const auto pwszSource = L"Source"; - const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t); - - const size_t cchTarget = 12; - auto rgwchTarget = std::make_unique(cchTarget); - const auto cbTarget = cchTarget * sizeof(wchar_t); - wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); - auto rgwchTargetBefore = std::make_unique(cchTarget); - wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); - size_t cbTargetUsed = 0; - const auto cbTargetUsedBefore = cbTargetUsed; - - std::wstring exeName(L"exe.exe"); - - DWORD dwLines = 0; - const auto dwLinesBefore = dwLines; - - Alias::s_MatchAndCopyAliasLegacy(pwszSource, - cbSource, - rgwchTarget.get(), - cbTarget, - cbTargetUsed, - exeName, // we didn't pre-set-up the exe name - dwLines); - - VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"No bytes should have been written."); - VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string should be unmodified."); - VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count should pass through."); + size_t dwLines = 1; + const std::wstring exeName(L"exe.exe"); + const auto buffer = Alias::s_MatchAndCopyAlias(pwszSource, exeName, dwLines); + VERIFY_IS_TRUE(buffer.empty()); + VERIFY_ARE_EQUAL(1u, dwLines); } TEST_METHOD(TestMatchAndCopyAliasNotFound) { const auto pwszSource = L"Source"; - const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t); - - const size_t cchTarget = 12; - auto rgwchTarget = std::make_unique(cchTarget); - const auto cbTarget = cchTarget * sizeof(wchar_t); - wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); - auto rgwchTargetBefore = std::make_unique(cchTarget); - wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); - - size_t cbTargetUsed = 0; - const auto cbTargetUsedBefore = cbTargetUsed; - - DWORD dwLines = 0; - const auto dwLinesBefore = dwLines; + size_t dwLines = 1; // Register the wrong alias name before we try. std::wstring exe(L"exe.exe"); @@ -279,71 +160,15 @@ class AliasTests std::wstring target(L"someTarget"); Alias::s_TestAddAlias(exe, badSource, target); - Alias::s_MatchAndCopyAliasLegacy(pwszSource, - cbSource, - rgwchTarget.get(), - cbTarget, - cbTargetUsed, - exe, - dwLines); - - VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"No bytes should be used if nothing was found."); - VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string should be unmodified."); - VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count should pass through."); - } - - TEST_METHOD(TestMatchAndCopyTargetTooSmall) - { - const auto pwszSource = L"Source"; - const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t); - - const size_t cchTarget = 12; - auto rgwchTarget = std::make_unique(cchTarget); - wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); - auto rgwchTargetBefore = std::make_unique(cchTarget); - wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); - - size_t cbTargetUsed = 0; - const auto cbTargetUsedBefore = cbTargetUsed; - - DWORD dwLines = 0; - const auto dwLinesBefore = dwLines; - - // Register the correct alias name before we try. - std::wstring exe(L"exe.exe"); - std::wstring source(pwszSource); - std::wstring target(L"someTarget"); - Alias::s_TestAddAlias(exe, source, target); - - Alias::s_MatchAndCopyAliasLegacy(pwszSource, - cbSource, - rgwchTarget.get(), - 1, // Make the target size too small - cbTargetUsed, - exe, - dwLines); - - VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"Byte count shouldn't have changed with failure."); - VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count shouldn't have changed with failure."); - VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string shouldn't have changed with failure."); + const auto buffer = Alias::s_MatchAndCopyAlias(pwszSource, exe, dwLines); + VERIFY_IS_TRUE(buffer.empty()); + VERIFY_ARE_EQUAL(1u, dwLines); } TEST_METHOD(TestMatchAndCopyLeadingSpaces) { const auto pwszSource = L" Source"; - const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t); - - const size_t cchTarget = 12; - auto rgwchTarget = std::make_unique(cchTarget); - const auto cbTarget = cchTarget * sizeof(wchar_t); - wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc"); - auto rgwchTargetBefore = std::make_unique(cchTarget); - wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get()); - size_t cbTargetUsed = 0; - const auto cbTargetUsedBefore = cbTargetUsed; - - DWORD dwLines = 0; - const auto dwLinesBefore = dwLines; + size_t dwLines = 1; // Register the correct alias name before we try. std::wstring exe(L"exe.exe"); @@ -352,40 +177,9 @@ class AliasTests Alias::s_TestAddAlias(exe, source, target); // Leading spaces should bypass the alias. This should not match anything. - Alias::s_MatchAndCopyAliasLegacy(pwszSource, - cbSource, - rgwchTarget.get(), - cbTarget, - cbTargetUsed, - exe, - dwLines); - - VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"No bytes should be used if nothing was found."); - VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string should be unmodified."); - VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count should pass through."); - } - - TEST_METHOD(TrimTrailing) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", - L"{" - L"bar%=bar," // The character % will be turned into an \r\n - L"bar=bar" - L"}") - END_TEST_METHOD_PROPERTIES() - - std::wstring target; - std::wstring expected; - _RetrieveTargetExpectedPair(target, expected); - - // Substitute %s from metadata into \r\n (since metadata can't hold \r\n) - _ReplacePercentWithCRLF(target); - _ReplacePercentWithCRLF(expected); - - Alias::s_TrimTrailingCrLf(target); - - VERIFY_ARE_EQUAL(String(expected.data()), String(target.data())); + const auto buffer = Alias::s_MatchAndCopyAlias(pwszSource, exe, dwLines); + VERIFY_IS_TRUE(buffer.empty()); + VERIFY_ARE_EQUAL(1u, dwLines); } TEST_METHOD(Tokenize) diff --git a/src/host/ut_host/CommandLineTests.cpp b/src/host/ut_host/CommandLineTests.cpp deleted file mode 100644 index de09dd2e2414..000000000000 --- a/src/host/ut_host/CommandLineTests.cpp +++ /dev/null @@ -1,539 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "CommonState.hpp" - -#include "../../interactivity/inc/ServiceLocator.hpp" - -#include "../cmdline.h" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using Microsoft::Console::Interactivity::ServiceLocator; - -constexpr size_t PROMPT_SIZE = 512; - -class CommandLineTests -{ - std::unique_ptr m_state; - CommandHistory* m_pHistory; - - TEST_CLASS(CommandLineTests); - - TEST_CLASS_SETUP(ClassSetup) - { - m_state = std::make_unique(); - m_state->PrepareGlobalFont(); - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - m_state->CleanupGlobalFont(); - return true; - } - - TEST_METHOD_SETUP(MethodSetup) - { - m_state->PrepareGlobalInputBuffer(); - m_state->PrepareGlobalScreenBuffer(); - m_state->PrepareReadHandle(); - m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr); - if (!m_pHistory) - { - return false; - } - // History must be prepared before COOKED_READ (as it uses s_Find to get at it) - m_state->PrepareCookedReadData(); - return true; - } - - TEST_METHOD_CLEANUP(MethodCleanup) - { - CommandHistory::s_Free(nullptr); - m_pHistory = nullptr; - m_state->CleanupCookedReadData(); - m_state->CleanupReadHandle(); - m_state->CleanupGlobalInputBuffer(); - m_state->CleanupGlobalScreenBuffer(); - return true; - } - - void VerifyPromptText(COOKED_READ_DATA& cookedReadData, const std::wstring wstr) - { - const auto span = cookedReadData.SpanWholeBuffer(); - VERIFY_ARE_EQUAL(cookedReadData._bytesRead, wstr.size() * sizeof(wchar_t)); - VERIFY_ARE_EQUAL(wstr, (std::wstring_view{ span.data(), cookedReadData._bytesRead / sizeof(wchar_t) })); - } - - void InitCookedReadData(COOKED_READ_DATA& cookedReadData, - CommandHistory* pHistory, - wchar_t* pBuffer, - const size_t cchBuffer) - { - cookedReadData._commandHistory = pHistory; - cookedReadData._userBuffer = reinterpret_cast(pBuffer); - cookedReadData._userBufferSize = cchBuffer * sizeof(wchar_t); - cookedReadData._bufferSize = cchBuffer * sizeof(wchar_t); - cookedReadData._backupLimit = pBuffer; - cookedReadData._bufPtr = pBuffer; - cookedReadData._exeName = L"cmd.exe"; - cookedReadData.OriginalCursorPosition() = { 0, 0 }; - } - - void SetPrompt(COOKED_READ_DATA& cookedReadData, const std::wstring text) - { - std::copy(text.begin(), text.end(), cookedReadData._backupLimit); - cookedReadData._bytesRead = text.size() * sizeof(wchar_t); - cookedReadData._currentPosition = text.size(); - cookedReadData._bufPtr += text.size(); - cookedReadData._visibleCharCount = text.size(); - } - - void MoveCursor(COOKED_READ_DATA& cookedReadData, const size_t column) - { - cookedReadData._currentPosition = column; - cookedReadData._bufPtr = cookedReadData._backupLimit + column; - } - - TEST_METHOD(CanCycleCommandHistory) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE); - - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 3", false)); - - auto& commandLine = CommandLine::Instance(); - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next); - // should not have anything on the prompt - VERIFY_ARE_EQUAL(cookedReadData._bytesRead, 0u); - - // go back one history item - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous); - VerifyPromptText(cookedReadData, L"echo 3"); - - // try to go to the next history item, prompt shouldn't change - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next); - VerifyPromptText(cookedReadData, L"echo 3"); - - // go back another - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous); - VerifyPromptText(cookedReadData, L"echo 2"); - - // go forward - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next); - VerifyPromptText(cookedReadData, L"echo 3"); - - // go back two - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous); - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous); - VerifyPromptText(cookedReadData, L"echo 1"); - - // make sure we can't go back further - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous); - VerifyPromptText(cookedReadData, L"echo 1"); - - // can still go forward - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next); - VerifyPromptText(cookedReadData, L"echo 2"); - } - - TEST_METHOD(CanSetPromptToOldestHistory) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE); - - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 3", false)); - - auto& commandLine = CommandLine::Instance(); - commandLine._setPromptToOldestCommand(cookedReadData); - VerifyPromptText(cookedReadData, L"echo 1"); - - // change prompt and go back to oldest - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next); - commandLine._setPromptToOldestCommand(cookedReadData); - VerifyPromptText(cookedReadData, L"echo 1"); - } - - TEST_METHOD(CanSetPromptToNewestHistory) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE); - - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 3", false)); - - auto& commandLine = CommandLine::Instance(); - commandLine._setPromptToNewestCommand(cookedReadData); - VerifyPromptText(cookedReadData, L"echo 3"); - - // change prompt and go back to newest - commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous); - commandLine._setPromptToNewestCommand(cookedReadData); - VerifyPromptText(cookedReadData, L"echo 3"); - } - - TEST_METHOD(CanDeletePromptAfterCursor) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE); - - auto expected = L"test word blah"; - SetPrompt(cookedReadData, expected); - VerifyPromptText(cookedReadData, expected); - - auto& commandLine = CommandLine::Instance(); - // set current cursor position somewhere in the middle of the prompt - MoveCursor(cookedReadData, 4); - commandLine.DeletePromptAfterCursor(cookedReadData); - VerifyPromptText(cookedReadData, L"test"); - } - - TEST_METHOD(CanDeletePromptBeforeCursor) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE); - - auto expected = L"test word blah"; - SetPrompt(cookedReadData, expected); - VerifyPromptText(cookedReadData, expected); - - // set current cursor position somewhere in the middle of the prompt - MoveCursor(cookedReadData, 5); - auto& commandLine = CommandLine::Instance(); - const auto cursorPos = commandLine._deletePromptBeforeCursor(cookedReadData); - cookedReadData._currentPosition = cursorPos.x; - VerifyPromptText(cookedReadData, L"word blah"); - } - - TEST_METHOD(CanMoveCursorToEndOfPrompt) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE); - - auto expected = L"test word blah"; - SetPrompt(cookedReadData, expected); - VerifyPromptText(cookedReadData, expected); - - // make sure the cursor is not at the start of the prompt - VERIFY_ARE_NOT_EQUAL(cookedReadData._currentPosition, 0u); - VERIFY_ARE_NOT_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit); - - // save current position for later checking - const auto expectedCursorPos = cookedReadData._currentPosition; - const auto expectedBufferPos = cookedReadData._bufPtr; - - MoveCursor(cookedReadData, 0); - - auto& commandLine = CommandLine::Instance(); - const auto cursorPos = commandLine._moveCursorToEndOfPrompt(cookedReadData); - VERIFY_ARE_EQUAL(cursorPos.x, gsl::narrow(expectedCursorPos)); - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, expectedCursorPos); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, expectedBufferPos); - } - - TEST_METHOD(CanMoveCursorToStartOfPrompt) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE); - - auto expected = L"test word blah"; - SetPrompt(cookedReadData, expected); - VerifyPromptText(cookedReadData, expected); - - // make sure the cursor is not at the start of the prompt - VERIFY_ARE_NOT_EQUAL(cookedReadData._currentPosition, 0u); - VERIFY_ARE_NOT_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit); - - auto& commandLine = CommandLine::Instance(); - const auto cursorPos = commandLine._moveCursorToStartOfPrompt(cookedReadData); - VERIFY_ARE_EQUAL(cursorPos.x, 0); - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit); - } - - TEST_METHOD(CanMoveCursorLeftByWord) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE); - - auto expected = L"test word blah"; - SetPrompt(cookedReadData, expected); - VerifyPromptText(cookedReadData, expected); - - auto& commandLine = CommandLine::Instance(); - // cursor position at beginning of "blah" - til::CoordType expectedPos = 10; - auto cursorPos = commandLine._moveCursorLeftByWord(cookedReadData); - VERIFY_ARE_EQUAL(cursorPos.x, expectedPos); - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow(expectedPos)); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos); - - // move again - expectedPos = 5; // before "word" - cursorPos = commandLine._moveCursorLeftByWord(cookedReadData); - VERIFY_ARE_EQUAL(cursorPos.x, expectedPos); - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow(expectedPos)); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos); - - // move again - expectedPos = 0; // before "test" - cursorPos = commandLine._moveCursorLeftByWord(cookedReadData); - VERIFY_ARE_EQUAL(cursorPos.x, expectedPos); - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow(expectedPos)); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos); - - // try to move again, nothing should happen - cursorPos = commandLine._moveCursorLeftByWord(cookedReadData); - VERIFY_ARE_EQUAL(cursorPos.x, expectedPos); - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow(expectedPos)); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos); - } - - TEST_METHOD(CanMoveCursorLeft) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE); - - const std::wstring expected = L"test word blah"; - SetPrompt(cookedReadData, expected); - VerifyPromptText(cookedReadData, expected); - - // move left from end of prompt text to the beginning of the prompt - auto& commandLine = CommandLine::Instance(); - for (auto it = expected.crbegin(); it != expected.crend(); ++it) - { - const auto cursorPos = commandLine._moveCursorLeft(cookedReadData); - VERIFY_ARE_EQUAL(*cookedReadData._bufPtr, *it); - } - // should now be at the start of the prompt - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit); - - // try to move left a final time, nothing should change - const auto cursorPos = commandLine._moveCursorLeft(cookedReadData); - VERIFY_ARE_EQUAL(cursorPos.x, 0); - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit); - } - - /* - // TODO MSFT:11285829 tcome back and turn these on once the system cursor isn't needed - TEST_METHOD(CanMoveCursorRightByWord) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE); - - auto expected = L"test word blah"; - SetPrompt(cookedReadData, expected); - VerifyPromptText(cookedReadData, expected); - - // save current position for later checking - const auto endCursorPos = cookedReadData._currentPosition; - const auto endBufferPos = cookedReadData._bufPtr; - // NOTE: need to initialize the actually cursor and keep it up to date with the changes here. remove - once functions are fixed - // try to move right, nothing should happen - auto expectedPos = endCursorPos; - auto cursorPos = MoveCursorRightByWord(cookedReadData); - VERIFY_ARE_EQUAL(cursorPos.x, expectedPos); - VERIFY_ARE_EQUAL(cookedReadData._currentPosition, expectedPos); - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, endBufferPos); - - // move to beginning of prompt and walk to the right by word - } - - TEST_METHOD(CanMoveCursorRight) - { - } - - TEST_METHOD(CanDeleteFromRightOfCursor) - { - } - - */ - - TEST_METHOD(CanInsertCtrlZ) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE); - - auto& commandLine = CommandLine::Instance(); - commandLine._insertCtrlZ(cookedReadData); - VerifyPromptText(cookedReadData, L"\x1a"); // ctrl-z - } - - TEST_METHOD(CanDeleteCommandHistory) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE); - - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 3", false)); - - auto& commandLine = CommandLine::Instance(); - commandLine._deleteCommandHistory(cookedReadData); - VERIFY_ARE_EQUAL(m_pHistory->GetNumberOfCommands(), 0); - } - - TEST_METHOD(CanFillPromptWithPreviousCommandFragment) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE); - - VERIFY_SUCCEEDED(m_pHistory->Add(L"I'm a little teapot", false)); - SetPrompt(cookedReadData, L"short and stout"); - - auto& commandLine = CommandLine::Instance(); - commandLine._fillPromptWithPreviousCommandFragment(cookedReadData); - VerifyPromptText(cookedReadData, L"short and stoutapot"); - } - - TEST_METHOD(CanCycleMatchingCommandHistory) - { - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - - auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData(); - InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE); - - VERIFY_SUCCEEDED(m_pHistory->Add(L"I'm a little teapot", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"short and stout", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"inflammable", false)); - VERIFY_SUCCEEDED(m_pHistory->Add(L"Indestructible", false)); - - SetPrompt(cookedReadData, L"I"); - - auto& commandLine = CommandLine::Instance(); - commandLine._cycleMatchingCommandHistoryToPrompt(cookedReadData); - VerifyPromptText(cookedReadData, L"Indestructible"); - - // make sure we skip to the next "I" history item - commandLine._cycleMatchingCommandHistoryToPrompt(cookedReadData); - VerifyPromptText(cookedReadData, L"I'm a little teapot"); - - // should cycle back to the start of the command history - commandLine._cycleMatchingCommandHistoryToPrompt(cookedReadData); - VerifyPromptText(cookedReadData, L"Indestructible"); - } - - TEST_METHOD(CmdlineCtrlHomeFullwidthChars) - { - Log::Comment(L"Set up buffers, create cooked read data, get screen information."); - auto buffer = std::make_unique(PROMPT_SIZE); - VERIFY_IS_NOT_NULL(buffer.get()); - auto& consoleInfo = ServiceLocator::LocateGlobals().getConsoleInformation(); - auto& screenInfo = consoleInfo.GetActiveOutputBuffer(); - auto& cookedReadData = consoleInfo.CookedReadData(); - InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE); - - Log::Comment(L"Create Japanese text string and calculate the distance we expect the cursor to move."); - const std::wstring text(L"\x30ab\x30ac\x30ad\x30ae\x30af"); // katakana KA GA KI GI KU - const auto bufferSize = screenInfo.GetBufferSize(); - const auto cursorBefore = screenInfo.GetTextBuffer().GetCursor().GetPosition(); - auto cursorAfterExpected = cursorBefore; - for (size_t i = 0; i < text.length() * 2; i++) - { - bufferSize.IncrementInBounds(cursorAfterExpected); - } - - Log::Comment(L"Write the text into the buffer using the cooked read structures as if it came off of someone's input."); - const auto written = cookedReadData.Write(text); - VERIFY_ARE_EQUAL(text.length(), written); - - Log::Comment(L"Retrieve the position of the cursor after insertion and check that it moved as far as we expected."); - const auto cursorAfter = screenInfo.GetTextBuffer().GetCursor().GetPosition(); - VERIFY_ARE_EQUAL(cursorAfterExpected, cursorAfter); - - Log::Comment(L"Walk through the screen buffer data and ensure that the text we wrote filled the cells up as we expected (2 cells per fullwidth char)"); - { - auto cellIterator = screenInfo.GetCellDataAt(cursorBefore); - for (size_t i = 0; i < text.length() * 2; i++) - { - // Our original string was 5 wide characters which we expected to take 10 cells. - // Therefore each index of the original string will be used twice ( divide by 2 ). - const auto expectedTextValue = text.at(i / 2); - const String expectedText(&expectedTextValue, 1); - - const auto actualTextValue = cellIterator->Chars(); - const String actualText(actualTextValue.data(), gsl::narrow(actualTextValue.size())); - - VERIFY_ARE_EQUAL(expectedText, actualText); - cellIterator++; - } - } - - Log::Comment(L"Now perform the command that is triggered with Ctrl+Home keys normally to erase the entire edit line."); - auto& commandLine = CommandLine::Instance(); - commandLine._deletePromptBeforeCursor(cookedReadData); - - Log::Comment(L"Check that the entire span of the buffer where we had the fullwidth text is now cleared out and full of blanks (nothing left behind)."); - { - auto cursorPos = cursorBefore; - auto cellIterator = screenInfo.GetCellDataAt(cursorPos); - - while (Utils::s_CompareCoords(cursorPos, cursorAfter) < 0) - { - const String expectedText(L"\x20"); // unicode space character - - const auto actualTextValue = cellIterator->Chars(); - const String actualText(actualTextValue.data(), gsl::narrow(actualTextValue.size())); - - VERIFY_ARE_EQUAL(expectedText, actualText); - cellIterator++; - - bufferSize.IncrementInBounds(cursorPos); - } - } - } -}; diff --git a/src/host/ut_host/CommandListPopupTests.cpp b/src/host/ut_host/CommandListPopupTests.cpp deleted file mode 100644 index d47f3bbfa315..000000000000 --- a/src/host/ut_host/CommandListPopupTests.cpp +++ /dev/null @@ -1,538 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "CommonState.hpp" - -#include "../../interactivity/inc/ServiceLocator.hpp" - -#include "../CommandListPopup.hpp" -#include "PopupTestHelper.hpp" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using Microsoft::Console::Interactivity::ServiceLocator; -static constexpr size_t BUFFER_SIZE = 256; -static constexpr UINT s_NumberOfHistoryBuffers = 4; -static constexpr UINT s_HistoryBufferSize = 50; - -class CommandListPopupTests -{ - TEST_CLASS(CommandListPopupTests); - - std::unique_ptr m_state; - CommandHistory* m_pHistory; - - TEST_CLASS_SETUP(ClassSetup) - { - m_state = std::make_unique(); - m_state->PrepareGlobalFont(); - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.SetNumberOfHistoryBuffers(s_NumberOfHistoryBuffers); - gci.SetHistoryBufferSize(s_HistoryBufferSize); - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - m_state->CleanupGlobalFont(); - return true; - } - - TEST_METHOD_SETUP(MethodSetup) - { - m_state->PrepareGlobalInputBuffer(); - m_state->PrepareGlobalScreenBuffer(); - m_state->PrepareReadHandle(); - m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr); - // resize command history storage to 50 items so that we don't cycle on accident - // when PopupTestHelper::InitLongHistory() is called. - CommandHistory::s_ResizeAll(50); - if (!m_pHistory) - { - return false; - } - // History must be prepared before COOKED_READ (as it uses s_Find to get at it) - m_state->PrepareCookedReadData(); - return true; - } - - TEST_METHOD_CLEANUP(MethodCleanup) - { - CommandHistory::s_Free(nullptr); - m_pHistory = nullptr; - m_state->CleanupCookedReadData(); - m_state->CleanupReadHandle(); - m_state->CleanupGlobalInputBuffer(); - m_state->CleanupGlobalScreenBuffer(); - return true; - } - - void InitReadData(COOKED_READ_DATA& cookedReadData, - wchar_t* const pBuffer, - const size_t cchBuffer, - const size_t cursorPosition) - { - cookedReadData._bufferSize = cchBuffer * sizeof(wchar_t); - cookedReadData._bufPtr = pBuffer + cursorPosition; - cookedReadData._backupLimit = pBuffer; - cookedReadData.OriginalCursorPosition() = { 0, 0 }; - cookedReadData._bytesRead = cursorPosition * sizeof(wchar_t); - cookedReadData._currentPosition = cursorPosition; - cookedReadData.VisibleCharCount() = cursorPosition; - } - - TEST_METHOD(CanDismiss) - { - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - modifiers = 0; - wch = VK_ESCAPE; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - const std::wstring testString = L"hello world"; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // the buffer should not be changed - const std::wstring resultString(buffer, buffer + testString.size()); - VERIFY_ARE_EQUAL(testString, resultString); - VERIFY_ARE_EQUAL(cookedReadData._bytesRead, testString.size() * sizeof(wchar_t)); - - // popup has been dismissed - VERIFY_IS_FALSE(CommandLine::Instance().HasPopup()); - } - - TEST_METHOD(UpMovesSelection) - { - // function to simulate user pressing up arrow - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto firstTime = true; - if (firstTime) - { - wch = VK_UP; - firstTime = false; - } - else - { - wch = VK_ESCAPE; - } - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - const auto commandNumberBefore = popup._currentCommand; - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - // selection should have moved up one line - VERIFY_ARE_EQUAL(commandNumberBefore - 1, popup._currentCommand); - } - - TEST_METHOD(DownMovesSelection) - { - // function to simulate user pressing down arrow - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto firstTime = true; - if (firstTime) - { - wch = VK_DOWN; - firstTime = false; - } - else - { - wch = VK_ESCAPE; - } - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - // set the current command selection to the top of the list - popup._currentCommand = 0; - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - const auto commandNumberBefore = popup._currentCommand; - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - // selection should have moved down one line - VERIFY_ARE_EQUAL(commandNumberBefore + 1, popup._currentCommand); - } - - TEST_METHOD(EndMovesSelectionToEnd) - { - // function to simulate user pressing end key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto firstTime = true; - if (firstTime) - { - wch = VK_END; - firstTime = false; - } - else - { - wch = VK_ESCAPE; - } - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - // set the current command selection to the top of the list - popup._currentCommand = 0; - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - // selection should have moved to the bottom line - VERIFY_ARE_EQUAL(m_pHistory->GetNumberOfCommands() - 1, gsl::narrow(popup._currentCommand)); - } - - TEST_METHOD(HomeMovesSelectionToStart) - { - // function to simulate user pressing home key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto firstTime = true; - if (firstTime) - { - wch = VK_HOME; - firstTime = false; - } - else - { - wch = VK_ESCAPE; - } - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - // selection should have moved to the bottom line - VERIFY_ARE_EQUAL(0, popup._currentCommand); - } - - TEST_METHOD(PageUpMovesSelection) - { - // function to simulate user pressing page up key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto firstTime = true; - if (firstTime) - { - wch = VK_PRIOR; - firstTime = false; - } - else - { - wch = VK_ESCAPE; - } - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitLongHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - // selection should have moved up a page - VERIFY_ARE_EQUAL(static_cast(m_pHistory->GetNumberOfCommands()) - popup.Height() - 1, popup._currentCommand); - } - - TEST_METHOD(PageDownMovesSelection) - { - // function to simulate user pressing page down key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto firstTime = true; - if (firstTime) - { - wch = VK_NEXT; - firstTime = false; - } - else - { - wch = VK_ESCAPE; - } - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitLongHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - // set the current command selection to the top of the list - popup._currentCommand = 0; - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - // selection should have moved up a page - VERIFY_ARE_EQUAL(popup.Height(), popup._currentCommand); - } - - TEST_METHOD(SideArrowsFillsPrompt) - { - // function to simulate user pressing right arrow key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - wch = VK_RIGHT; - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - // set the current command selection to the top of the list - popup._currentCommand = 0; - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - // prompt should have history item in prompt - const auto historyItem = m_pHistory->GetLastCommand(); - const std::wstring_view resultText{ buffer, historyItem.size() }; - VERIFY_ARE_EQUAL(historyItem, resultText); - } - - TEST_METHOD(CanLaunchCommandNumberPopup) - { - // function to simulate user pressing F9 - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - wch = VK_F9; - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - auto& commandLine = CommandLine::Instance(); - VERIFY_IS_FALSE(commandLine.HasPopup()); - // should spawn a CommandNumberPopup - auto scopeExit = wil::scope_exit([&]() { commandLine.EndAllPopups(); }); - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT)); - VERIFY_IS_TRUE(commandLine.HasPopup()); - } - - TEST_METHOD(CanDeleteFromCommandHistory) - { - // function to simulate user pressing the delete key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto firstTime = true; - if (firstTime) - { - wch = VK_DELETE; - firstTime = false; - } - else - { - wch = VK_ESCAPE; - } - popupKey = true; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - const auto startHistorySize = m_pHistory->GetNumberOfCommands(); - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - VERIFY_ARE_EQUAL(m_pHistory->GetNumberOfCommands(), startHistorySize - 1); - } - - TEST_METHOD(CanReorderHistoryUp) - { - // function to simulate user pressing shift + up arrow - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto firstTime = true; - if (firstTime) - { - wch = VK_UP; - firstTime = false; - modifiers = SHIFT_PRESSED; - } - else - { - wch = VK_ESCAPE; - modifiers = 0; - } - popupKey = true; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my spout"); - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my handle"); - VERIFY_ARE_EQUAL(m_pHistory->GetNth(2), L"here is my spout"); - } - - TEST_METHOD(CanReorderHistoryDown) - { - // function to simulate user pressing the up arrow, then shift + down arrow, then escape - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static unsigned int count = 0; - if (count == 0) - { - wch = VK_UP; - modifiers = 0; - } - else if (count == 1) - { - wch = VK_DOWN; - modifiers = SHIFT_PRESSED; - } - else - { - wch = VK_ESCAPE; - modifiers = 0; - } - popupKey = true; - ++count; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - PopupTestHelper::InitHistory(*m_pHistory); - CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my spout"); - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my handle"); - VERIFY_ARE_EQUAL(m_pHistory->GetNth(2), L"here is my spout"); - } -}; diff --git a/src/host/ut_host/CommandNumberPopupTests.cpp b/src/host/ut_host/CommandNumberPopupTests.cpp deleted file mode 100644 index 006c36207bdd..000000000000 --- a/src/host/ut_host/CommandNumberPopupTests.cpp +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "CommonState.hpp" -#include "PopupTestHelper.hpp" - -#include "../../interactivity/inc/ServiceLocator.hpp" - -#include "../CommandNumberPopup.hpp" -#include "../CommandListPopup.hpp" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using Microsoft::Console::Interactivity::ServiceLocator; - -static constexpr size_t BUFFER_SIZE = 256; - -class CommandNumberPopupTests -{ - TEST_CLASS(CommandNumberPopupTests); - - std::unique_ptr m_state; - CommandHistory* m_pHistory; - - TEST_CLASS_SETUP(ClassSetup) - { - m_state = std::make_unique(); - m_state->PrepareGlobalFont(); - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - m_state->CleanupGlobalFont(); - return true; - } - - TEST_METHOD_SETUP(MethodSetup) - { - m_state->PrepareGlobalInputBuffer(); - m_state->PrepareGlobalScreenBuffer(); - m_state->PrepareReadHandle(); - m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr); - if (!m_pHistory) - { - return false; - } - // History must be prepared before COOKED_READ (as it uses s_Find to get at it) - m_state->PrepareCookedReadData(); - return true; - } - - TEST_METHOD_CLEANUP(MethodCleanup) - { - CommandHistory::s_Free(nullptr); - m_pHistory = nullptr; - m_state->CleanupCookedReadData(); - m_state->CleanupReadHandle(); - m_state->CleanupGlobalInputBuffer(); - m_state->CleanupGlobalScreenBuffer(); - return true; - } - - TEST_METHOD(CanDismiss) - { - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - wch = VK_ESCAPE; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CommandNumberPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - const std::wstring testString = L"hello world"; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // the buffer should not be changed - const std::wstring resultString(buffer, buffer + testString.size()); - VERIFY_ARE_EQUAL(testString, resultString); - VERIFY_ARE_EQUAL(cookedReadData._bytesRead, testString.size() * sizeof(wchar_t)); - - // popup has been dismissed - VERIFY_IS_FALSE(CommandLine::Instance().HasPopup()); - } - - TEST_METHOD(CanDismissAllPopups) - { - Log::Comment(L"that that all popups are dismissed when CommandNumberPopup is dismissed"); - // CommandNumberPopup is the only popup that can act as a 2nd popup. make sure that it dismisses all - // popups when exiting - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - wch = VK_ESCAPE; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // add popups to CommandLine - auto& commandLine = CommandLine::Instance(); - commandLine._popups.emplace_front(std::make_unique(gci.GetActiveOutputBuffer(), *m_pHistory)); - commandLine._popups.emplace_front(std::make_unique(gci.GetActiveOutputBuffer())); - auto& numberPopup = *commandLine._popups.front(); - numberPopup.SetUserInputFunction(fn); - - VERIFY_ARE_EQUAL(commandLine._popups.size(), 2u); - - // prepare cookedReadData - const std::wstring testString = L"hello world"; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(numberPopup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - VERIFY_IS_FALSE(commandLine.HasPopup()); - } - - TEST_METHOD(EmptyInputCountsAsOldestHistory) - { - Log::Comment(L"hitting enter with no input should grab the oldest history item"); - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = false; - wch = UNICODE_CARRIAGERETURN; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CommandNumberPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // the buffer should contain the least recent history item - - const auto expected = m_pHistory->GetLastCommand(); - const std::wstring resultString(buffer, buffer + expected.size()); - VERIFY_ARE_EQUAL(expected, resultString); - } - - TEST_METHOD(CanSelectHistoryItem) - { - PopupTestHelper::InitHistory(*m_pHistory); - for (CommandHistory::Index historyIndex = 0; historyIndex < m_pHistory->GetNumberOfCommands(); ++historyIndex) - { - Popup::UserInputFunction fn = [historyIndex](COOKED_READ_DATA& /*cookedReadData*/, - bool& popupKey, - DWORD& modifiers, - wchar_t& wch) { - static auto needReturn = false; - popupKey = false; - modifiers = 0; - if (!needReturn) - { - const auto str = std::to_string(historyIndex); - wch = str.at(0); - needReturn = true; - } - else - { - wch = UNICODE_CARRIAGERETURN; - needReturn = false; - } - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CommandNumberPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // the buffer should contain the correct nth history item - - const auto expected = m_pHistory->GetNth(gsl::narrow(historyIndex)); - const std::wstring resultString(buffer, buffer + expected.size()); - VERIFY_ARE_EQUAL(expected, resultString); - } - } - - TEST_METHOD(LargeNumberGrabsNewestHistoryItem) - { - Log::Comment(L"entering a number larger than the number of history items should grab the most recent history item"); - - // simulates user pressing 1, 2, 3, 4, 5, enter - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - static auto num = 1; - popupKey = false; - modifiers = 0; - if (num <= 5) - { - const auto str = std::to_string(num); - wch = str.at(0); - ++num; - } - else - { - wch = UNICODE_CARRIAGERETURN; - } - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CommandNumberPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // the buffer should contain the most recent history item - - const auto expected = m_pHistory->GetLastCommand(); - const std::wstring resultString(buffer, buffer + expected.size()); - VERIFY_ARE_EQUAL(expected, resultString); - } - - TEST_METHOD(InputIsLimited) - { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - CommandNumberPopup popup{ gci.GetActiveOutputBuffer() }; - - // input can't delete past zero number input - popup._pop(); - VERIFY_ARE_EQUAL(popup._parse(), 0); - - // input can only be numbers - VERIFY_THROWS_SPECIFIC(popup._push(L'$'), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); - VERIFY_THROWS_SPECIFIC(popup._push(L'A'), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; }); - - // input can't be more than 5 numbers - popup._push(L'1'); - VERIFY_ARE_EQUAL(popup._parse(), 1); - popup._push(L'2'); - VERIFY_ARE_EQUAL(popup._parse(), 12); - popup._push(L'3'); - VERIFY_ARE_EQUAL(popup._parse(), 123); - popup._push(L'4'); - VERIFY_ARE_EQUAL(popup._parse(), 1234); - popup._push(L'5'); - VERIFY_ARE_EQUAL(popup._parse(), 12345); - // this shouldn't affect the parsed number - popup._push(L'6'); - VERIFY_ARE_EQUAL(popup._parse(), 12345); - // make sure we can delete input correctly - popup._pop(); - VERIFY_ARE_EQUAL(popup._parse(), 1234); - } -}; diff --git a/src/host/ut_host/CopyFromCharPopupTests.cpp b/src/host/ut_host/CopyFromCharPopupTests.cpp deleted file mode 100644 index ccc54172a180..000000000000 --- a/src/host/ut_host/CopyFromCharPopupTests.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "CommonState.hpp" -#include "PopupTestHelper.hpp" - -#include "../../interactivity/inc/ServiceLocator.hpp" - -#include "../CopyFromCharPopup.hpp" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using Microsoft::Console::Interactivity::ServiceLocator; - -static constexpr size_t BUFFER_SIZE = 256; - -class CopyFromCharPopupTests -{ - TEST_CLASS(CopyFromCharPopupTests); - - std::unique_ptr m_state; - CommandHistory* m_pHistory; - - TEST_CLASS_SETUP(ClassSetup) - { - m_state = std::make_unique(); - m_state->PrepareGlobalFont(); - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - m_state->CleanupGlobalFont(); - return true; - } - - TEST_METHOD_SETUP(MethodSetup) - { - m_state->PrepareGlobalInputBuffer(); - m_state->PrepareGlobalScreenBuffer(); - m_state->PrepareReadHandle(); - m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr); - if (!m_pHistory) - { - return false; - } - // History must be prepared before COOKED_READ (as it uses s_Find to get at it) - m_state->PrepareCookedReadData(); - return true; - } - - TEST_METHOD_CLEANUP(MethodCleanup) - { - CommandHistory::s_Free(nullptr); - m_pHistory = nullptr; - m_state->CleanupCookedReadData(); - m_state->CleanupReadHandle(); - m_state->CleanupGlobalInputBuffer(); - m_state->CleanupGlobalScreenBuffer(); - return true; - } - - TEST_METHOD(CanDismiss) - { - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - wch = VK_ESCAPE; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CopyFromCharPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - std::wstring testString = L"hello world"; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // the buffer should not be changed - std::wstring resultString(buffer, buffer + testString.size()); - VERIFY_ARE_EQUAL(testString, resultString); - VERIFY_ARE_EQUAL(cookedReadData.BytesRead(), testString.size() * sizeof(wchar_t)); - - // popup has been dismissed - VERIFY_IS_FALSE(CommandLine::Instance().HasPopup()); - } - - TEST_METHOD(DeleteAllWhenCharNotFound) - { - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = false; - wch = L'x'; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CopyFromCharPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - std::wstring testString = L"hello world"; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - // move cursor to beginning of prompt text - cookedReadData.InsertionPoint() = 0; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // all text to the right of the cursor should be gone - VERIFY_ARE_EQUAL(cookedReadData.BytesRead(), 0u); - } - - TEST_METHOD(CanDeletePartialLine) - { - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = false; - wch = L'f'; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CopyFromCharPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - std::wstring testString = L"By the rude bridge that arched the flood"; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - // move cursor to index 12 - const size_t index = 12; - cookedReadData.SetBufferCurrentPtr(buffer + index); - cookedReadData.InsertionPoint() = index; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - std::wstring expectedText = L"By the rude flood"; - VERIFY_ARE_EQUAL(cookedReadData.BytesRead(), expectedText.size() * sizeof(wchar_t)); - std::wstring resultText(buffer, buffer + expectedText.size()); - VERIFY_ARE_EQUAL(resultText, expectedText); - } -}; diff --git a/src/host/ut_host/CopyToCharPopupTests.cpp b/src/host/ut_host/CopyToCharPopupTests.cpp deleted file mode 100644 index 320e0b7841ad..000000000000 --- a/src/host/ut_host/CopyToCharPopupTests.cpp +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "CommonState.hpp" -#include "PopupTestHelper.hpp" - -#include "../../interactivity/inc/ServiceLocator.hpp" - -#include "../CopyToCharPopup.hpp" - -using Microsoft::Console::Interactivity::ServiceLocator; -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; - -static constexpr size_t BUFFER_SIZE = 256; - -class CopyToCharPopupTests -{ - TEST_CLASS(CopyToCharPopupTests); - - std::unique_ptr m_state; - CommandHistory* m_pHistory; - - TEST_CLASS_SETUP(ClassSetup) - { - m_state = std::make_unique(); - m_state->PrepareGlobalFont(); - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - m_state->CleanupGlobalFont(); - return true; - } - - TEST_METHOD_SETUP(MethodSetup) - { - m_state->PrepareGlobalInputBuffer(); - m_state->PrepareGlobalScreenBuffer(); - m_state->PrepareReadHandle(); - m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr); - if (!m_pHistory) - { - return false; - } - // History must be prepared before COOKED_READ (as it uses s_Find to get at it) - m_state->PrepareCookedReadData(); - return true; - } - - TEST_METHOD_CLEANUP(MethodCleanup) - { - CommandHistory::s_Free(nullptr); - m_pHistory = nullptr; - m_state->CleanupCookedReadData(); - m_state->CleanupReadHandle(); - m_state->CleanupGlobalInputBuffer(); - m_state->CleanupGlobalScreenBuffer(); - return true; - } - - TEST_METHOD(CanDismiss) - { - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - wch = VK_ESCAPE; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CopyToCharPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - const std::wstring testString = L"hello world"; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // the buffer should not be changed - const std::wstring resultString(buffer, buffer + testString.size()); - VERIFY_ARE_EQUAL(testString, resultString); - VERIFY_ARE_EQUAL(cookedReadData._bytesRead, testString.size() * sizeof(wchar_t)); - - // popup has been dismissed - VERIFY_IS_FALSE(CommandLine::Instance().HasPopup()); - } - - TEST_METHOD(NothingHappensWhenCharNotFound) - { - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - wch = L'x'; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CopyToCharPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0u); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // the buffer should not be changed - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit); - VERIFY_ARE_EQUAL(cookedReadData._bytesRead, 0u); - } - - TEST_METHOD(CanCopyToEmptyPrompt) - { - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - wch = L's'; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CopyToCharPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0u); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - const std::wstring expectedText = L"here i"; - - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedText.size()); - VERIFY_ARE_EQUAL(cookedReadData._bytesRead, expectedText.size() * sizeof(wchar_t)); - - // make sure that the text matches - const std::wstring resultText(buffer, buffer + expectedText.size()); - VERIFY_ARE_EQUAL(resultText, expectedText); - // make sure that more wasn't copied - VERIFY_ARE_EQUAL(buffer[expectedText.size()], UNICODE_SPACE); - } - - TEST_METHOD(WontCopyTextBeforeCursor) - { - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - wch = L's'; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CopyToCharPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData with a string longer than the previous history - const std::wstring testString = L"Whose woods there are I think I know."; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - const wchar_t* const expectedBufPtr = cookedReadData._bufPtr; - const auto expectedBytesRead = cookedReadData._bytesRead; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - // nothing should have changed - VERIFY_ARE_EQUAL(cookedReadData._bufPtr, expectedBufPtr); - VERIFY_ARE_EQUAL(cookedReadData._bytesRead, expectedBytesRead); - const std::wstring resultText(buffer, buffer + testString.size()); - VERIFY_ARE_EQUAL(resultText, testString); - // make sure that more wasn't copied - VERIFY_ARE_EQUAL(buffer[testString.size()], UNICODE_SPACE); - } - - TEST_METHOD(CanMergeLine) - { - // function to simulate user pressing escape key - Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) { - popupKey = true; - wch = L's'; - modifiers = 0; - return STATUS_SUCCESS; - }; - - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // prepare popup - CopyToCharPopup popup{ gci.GetActiveOutputBuffer() }; - popup.SetUserInputFunction(fn); - - // prepare cookedReadData with a string longer than the previous history - const std::wstring testString = L"fear "; - wchar_t buffer[BUFFER_SIZE]; - std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE); - std::copy(testString.begin(), testString.end(), std::begin(buffer)); - auto& cookedReadData = gci.CookedReadData(); - PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size()); - PopupTestHelper::InitHistory(*m_pHistory); - cookedReadData._commandHistory = m_pHistory; - - VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast(CONSOLE_STATUS_WAIT_NO_BLOCK)); - - const std::wstring expectedText = L"fear is"; - const std::wstring resultText(buffer, buffer + testString.size()); - VERIFY_ARE_EQUAL(resultText, testString); - // make sure that more wasn't copied - VERIFY_ARE_EQUAL(buffer[expectedText.size()], UNICODE_SPACE); - } -}; diff --git a/src/host/ut_host/HistoryTests.cpp b/src/host/ut_host/HistoryTests.cpp index aabdc2448310..d0ab24bbf209 100644 --- a/src/host/ut_host/HistoryTests.cpp +++ b/src/host/ut_host/HistoryTests.cpp @@ -6,8 +6,7 @@ #include "../../inc/consoletaeftemplates.hpp" #include "CommonState.hpp" - -#include "search.h" +#include "history.h" using namespace WEX::Common; using namespace WEX::Logging; @@ -92,7 +91,7 @@ class HistoryTests VERIFY_SUCCEEDED(history->Add(_manyHistoryItems[i], false)); } - VERIFY_ARE_EQUAL(s_BufferSize, history->GetNumberOfCommands(), L"Ensure that it is filled."); + VERIFY_ARE_EQUAL(static_cast(s_BufferSize), history->GetNumberOfCommands(), L"Ensure that it is filled."); Log::Comment(L"Free it and recreate it with the same name."); CommandHistory::s_Free(h); @@ -101,7 +100,7 @@ class HistoryTests history = CommandHistory::s_Allocate(_manyApps[0], _MakeHandle(14)); VERIFY_IS_NOT_NULL(history); - VERIFY_ARE_EQUAL(s_BufferSize, history->GetNumberOfCommands(), L"Ensure that we still have full commands after freeing and reallocating, same app name, different handle ID"); + VERIFY_ARE_EQUAL(static_cast(s_BufferSize), history->GetNumberOfCommands(), L"Ensure that we still have full commands after freeing and reallocating, same app name, different handle ID"); } TEST_METHOD(TooManyAppsDoesNotTakeList) @@ -115,7 +114,7 @@ class HistoryTests { VERIFY_SUCCEEDED(history->Add(_manyHistoryItems[j], false)); } - VERIFY_ARE_EQUAL(s_BufferSize, history->GetNumberOfCommands()); + VERIFY_ARE_EQUAL(static_cast(s_BufferSize), history->GetNumberOfCommands()); } VERIFY_ARE_EQUAL(s_NumberOfBuffers, CommandHistory::s_historyLists.size()); @@ -141,19 +140,19 @@ class HistoryTests { VERIFY_SUCCEEDED(history->Add(_manyHistoryItems[j], false)); } - VERIFY_ARE_EQUAL(s_BufferSize, history->GetNumberOfCommands()); + VERIFY_ARE_EQUAL(static_cast(s_BufferSize), history->GetNumberOfCommands()); Log::Comment(L"Retrieve items/order."); std::vector commandsStored; - for (CommandHistory::Index i = 0; i < history->GetNumberOfCommands(); i++) + for (SHORT i = 0; i < static_cast(history->GetNumberOfCommands()); i++) { commandsStored.emplace_back(history->GetNth(i)); } Log::Comment(L"Reallocate larger and ensure items and order are preserved."); history->Realloc((CommandHistory::Index)_manyHistoryItems.size()); - VERIFY_ARE_EQUAL(s_BufferSize, history->GetNumberOfCommands()); - for (CommandHistory::Index i = 0; i < (CommandHistory::Index)commandsStored.size(); i++) + VERIFY_ARE_EQUAL(static_cast(s_BufferSize), history->GetNumberOfCommands()); + for (SHORT i = 0; i < static_cast(commandsStored.size()); i++) { VERIFY_ARE_EQUAL(String(commandsStored[i].data()), String(history->GetNth(i).data())); } @@ -175,18 +174,18 @@ class HistoryTests { VERIFY_SUCCEEDED(history->Add(_manyHistoryItems[j], false)); } - VERIFY_ARE_EQUAL(s_BufferSize, history->GetNumberOfCommands()); + VERIFY_ARE_EQUAL(static_cast(s_BufferSize), history->GetNumberOfCommands()); Log::Comment(L"Retrieve items/order."); std::vector commandsStored; - for (CommandHistory::Index i = 0; i < history->GetNumberOfCommands(); i++) + for (SHORT i = 0; i < static_cast(history->GetNumberOfCommands()); i++) { commandsStored.emplace_back(history->GetNth(i)); } Log::Comment(L"Reallocate smaller and ensure items and order are preserved. Items at end of list should be trimmed."); history->Realloc(5); - for (CommandHistory::Index i = 0; i < 5; i++) + for (SHORT i = 0; i < 5; i++) { VERIFY_ARE_EQUAL(String(commandsStored[i].data()), String(history->GetNth(i).data())); } @@ -267,7 +266,7 @@ class HistoryTests }; static constexpr UINT s_NumberOfBuffers = 4; - static constexpr CommandHistory::Index s_BufferSize = 10; + static constexpr UINT s_BufferSize = 10; HANDLE _MakeHandle(size_t index) { diff --git a/src/host/ut_host/Host.UnitTests.vcxproj b/src/host/ut_host/Host.UnitTests.vcxproj index 31f0f8e5a954..7ec209116dd3 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj +++ b/src/host/ut_host/Host.UnitTests.vcxproj @@ -15,12 +15,7 @@ - - - - - @@ -97,7 +92,6 @@ - diff --git a/src/host/ut_host/Host.UnitTests.vcxproj.filters b/src/host/ut_host/Host.UnitTests.vcxproj.filters index 2dcabcef1d40..9abf92f2cc73 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj.filters +++ b/src/host/ut_host/Host.UnitTests.vcxproj.filters @@ -78,27 +78,12 @@ Source Files - - Source Files - - - Source Files - - - Source Files - - - Source Files - Source Files Source Files - - Source Files - Source Files @@ -119,9 +104,6 @@ Header Files - - Header Files - diff --git a/src/host/ut_host/PopupTestHelper.hpp b/src/host/ut_host/PopupTestHelper.hpp deleted file mode 100644 index e427cd9624b8..000000000000 --- a/src/host/ut_host/PopupTestHelper.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/*++ - -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- PopupTestHelper.hpp - -Abstract: -- helper functions for unit testing the various popups - -Author(s): -- Austin Diviness (AustDi) 06-Sep-2018 - ---*/ - -#pragma once - -#include "../history.h" -#include "../readDataCooked.hpp" - -class PopupTestHelper final -{ -public: - static void InitReadData(COOKED_READ_DATA& cookedReadData, - wchar_t* const pBuffer, - const size_t cchBuffer, - const size_t cursorPosition) noexcept - { - cookedReadData._bufferSize = cchBuffer * sizeof(wchar_t); - cookedReadData._bufPtr = pBuffer + cursorPosition; - cookedReadData._backupLimit = pBuffer; - cookedReadData.OriginalCursorPosition() = { 0, 0 }; - cookedReadData._bytesRead = cursorPosition * sizeof(wchar_t); - cookedReadData._currentPosition = cursorPosition; - cookedReadData.VisibleCharCount() = cursorPosition; - } - - static void InitHistory(CommandHistory& history) noexcept - { - history.Empty(); - history.Flags |= CommandHistory::CLE_ALLOCATED; - VERIFY_SUCCEEDED(history.Add(L"I'm a little teapot", false)); - VERIFY_SUCCEEDED(history.Add(L"hear me shout", false)); - VERIFY_SUCCEEDED(history.Add(L"here is my handle", false)); - VERIFY_SUCCEEDED(history.Add(L"here is my spout", false)); - VERIFY_ARE_EQUAL(history.GetNumberOfCommands(), 4); - } - - static void InitLongHistory(CommandHistory& history) noexcept - { - history.Empty(); - history.Flags |= CommandHistory::CLE_ALLOCATED; - VERIFY_SUCCEEDED(history.Add(L"Because I could not stop for Death", false)); - VERIFY_SUCCEEDED(history.Add(L"He kindly stopped for me", false)); - VERIFY_SUCCEEDED(history.Add(L"The carriage held but just Ourselves", false)); - VERIFY_SUCCEEDED(history.Add(L"And Immortality", false)); - VERIFY_SUCCEEDED(history.Add(L"~", false)); - VERIFY_SUCCEEDED(history.Add(L"We slowly drove - He knew no haste", false)); - VERIFY_SUCCEEDED(history.Add(L"And I had put away", false)); - VERIFY_SUCCEEDED(history.Add(L"My labor and my leisure too", false)); - VERIFY_SUCCEEDED(history.Add(L"For His Civility", false)); - VERIFY_SUCCEEDED(history.Add(L"~", false)); - VERIFY_SUCCEEDED(history.Add(L"We passed the School, where Children strove", false)); - VERIFY_SUCCEEDED(history.Add(L"At Recess - in the Ring", false)); - VERIFY_SUCCEEDED(history.Add(L"We passed the Fields of Gazing Grain", false)); - VERIFY_SUCCEEDED(history.Add(L"We passed the Setting Sun", false)); - VERIFY_SUCCEEDED(history.Add(L"~", false)); - VERIFY_SUCCEEDED(history.Add(L"Or rather - He passed us,", false)); - VERIFY_SUCCEEDED(history.Add(L"The Dews drew quivering and chill,", false)); - VERIFY_SUCCEEDED(history.Add(L"For only Gossamer, my Gown,", false)); - VERIFY_SUCCEEDED(history.Add(L"My Tippet - only Tulle", false)); - VERIFY_SUCCEEDED(history.Add(L"~", false)); - VERIFY_SUCCEEDED(history.Add(L"We paused before a House that seemed", false)); - VERIFY_SUCCEEDED(history.Add(L"A Swelling of the Ground -", false)); - VERIFY_SUCCEEDED(history.Add(L"The Roof was scarcely visible -", false)); - VERIFY_SUCCEEDED(history.Add(L"The Cornice - in the Ground -", false)); - VERIFY_SUCCEEDED(history.Add(L"~", false)); - VERIFY_SUCCEEDED(history.Add(L"Since then - 'tis Centuries - and yet", false)); - VERIFY_SUCCEEDED(history.Add(L"Feels shorter than the Day", false)); - VERIFY_SUCCEEDED(history.Add(L"~ Emily Dickinson", false)); - VERIFY_ARE_EQUAL(history.GetNumberOfCommands(), 28); - } -}; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index b26801be0a1a..da584473f19e 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -2895,15 +2895,11 @@ void ScreenBufferTests::BackspaceDefaultAttrsWriteCharsLegacy() { BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:writeSingly", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:writeCharsLegacyMode", L"{0, 1, 2}") END_TEST_METHOD_PROPERTIES(); bool writeSingly; VERIFY_SUCCEEDED(TestData::TryGetValue(L"writeSingly", writeSingly), L"Write one at a time = true, all at the same time = false"); - DWORD writeCharsLegacyMode; - VERIFY_SUCCEEDED(TestData::TryGetValue(L"writeCharsLegacyMode", writeCharsLegacyMode), L""); - // Created for MSFT:19735050. // Kinda the same as above, but with WriteCharsLegacy instead. // The variable that really breaks this scenario @@ -2931,18 +2927,13 @@ void ScreenBufferTests::BackspaceDefaultAttrsWriteCharsLegacy() if (writeSingly) { - auto str = L"X"; - size_t seqCb = 2; - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, writeCharsLegacyMode, nullptr)); - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, writeCharsLegacyMode, nullptr)); - str = L"\x08"; - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, writeCharsLegacyMode, nullptr)); + WriteCharsLegacy(si, L"X", false, nullptr); + WriteCharsLegacy(si, L"X", false, nullptr); + WriteCharsLegacy(si, L"\x08", false, nullptr); } else { - const auto str = L"XX\x08"; - size_t seqCb = 6; - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, writeCharsLegacyMode, nullptr)); + WriteCharsLegacy(si, L"XX\x08", false, nullptr); } TextAttribute expectedDefaults{}; @@ -7191,8 +7182,7 @@ void ScreenBufferTests::UpdateVirtualBottomWhenCursorMovesBelowIt() Log::Comment(L"Now write several lines of content using WriteCharsLegacy"); const auto content = L"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; - auto numBytes = wcslen(content) * sizeof(wchar_t); - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, content, content, content, &numBytes, nullptr, 0, 0, nullptr)); + WriteCharsLegacy(si, content, false, nullptr); Log::Comment(L"Confirm that the cursor position has moved down 10 lines"); const auto newCursorPos = til::point{ initialCursorPos.x, initialCursorPos.y + 10 }; diff --git a/src/host/ut_host/SelectionTests.cpp b/src/host/ut_host/SelectionTests.cpp index 99193049816d..f33889992bbb 100644 --- a/src/host/ut_host/SelectionTests.cpp +++ b/src/host/ut_host/SelectionTests.cpp @@ -10,7 +10,6 @@ #include "globals.h" #include "selection.hpp" -#include "cmdline.h" #include "../interactivity/inc/ServiceLocator.hpp" @@ -382,89 +381,6 @@ class SelectionInputTests return true; } - TEST_METHOD(TestGetInputLineBoundaries) - { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - // 80x80 box - const til::CoordType sRowWidth = 80; - - til::inclusive_rect srectEdges; - srectEdges.left = srectEdges.top = 0; - srectEdges.right = srectEdges.bottom = sRowWidth - 1; - - // false when no cooked read data exists - VERIFY_IS_FALSE(gci.HasPendingCookedRead()); - - auto fResult = Selection::s_GetInputLineBoundaries(nullptr, nullptr); - VERIFY_IS_FALSE(fResult); - - // prepare some read data - m_state->PrepareReadHandle(); - auto cleanupReadHandle = wil::scope_exit([&]() { m_state->CleanupReadHandle(); }); - - m_state->PrepareCookedReadData(); - // set up to clean up read data later - auto cleanupCookedRead = wil::scope_exit([&]() { m_state->CleanupCookedReadData(); }); - - auto& readData = gci.CookedReadData(); - - // backup text info position over remainder of text execution duration - auto& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer(); - til::point coordOldTextInfoPos; - coordOldTextInfoPos.x = textBuffer.GetCursor().GetPosition().x; - coordOldTextInfoPos.y = textBuffer.GetCursor().GetPosition().y; - - // set various cursor positions - readData.OriginalCursorPosition().x = 15; - readData.OriginalCursorPosition().y = 3; - - readData.VisibleCharCount() = 200; - - textBuffer.GetCursor().SetXPosition(35); - textBuffer.GetCursor().SetYPosition(35); - - // try getting boundaries with no pointers. parameters should be fully optional. - fResult = Selection::s_GetInputLineBoundaries(nullptr, nullptr); - VERIFY_IS_TRUE(fResult); - - // now let's get some actual data - til::point coordStart; - til::point coordEnd; - - fResult = Selection::s_GetInputLineBoundaries(&coordStart, &coordEnd); - VERIFY_IS_TRUE(fResult); - - // starting position/boundary should always be where the input line started - VERIFY_ARE_EQUAL(coordStart.x, readData.OriginalCursorPosition().x); - VERIFY_ARE_EQUAL(coordStart.y, readData.OriginalCursorPosition().y); - - // ending position can vary. it's in one of two spots - // 1. If the original cooked cursor was valid (which it was this first time), it's NumberOfVisibleChars ahead. - til::point coordFinalPos; - - const auto cCharsToAdjust = ((til::CoordType)readData.VisibleCharCount() - 1); // then -1 to be on the last piece of text, not past it - - coordFinalPos.x = (readData.OriginalCursorPosition().x + cCharsToAdjust) % sRowWidth; - coordFinalPos.y = readData.OriginalCursorPosition().y + ((readData.OriginalCursorPosition().x + cCharsToAdjust) / sRowWidth); - - VERIFY_ARE_EQUAL(coordEnd.x, coordFinalPos.x); - VERIFY_ARE_EQUAL(coordEnd.y, coordFinalPos.y); - - // 2. if the original cooked cursor is invalid, then it's the text info cursor position - readData.OriginalCursorPosition().x = -1; - readData.OriginalCursorPosition().y = -1; - - fResult = Selection::s_GetInputLineBoundaries(nullptr, &coordEnd); - VERIFY_IS_TRUE(fResult); - - VERIFY_ARE_EQUAL(coordEnd.x, textBuffer.GetCursor().GetPosition().x - 1); // -1 to be on the last piece of text, not past it - VERIFY_ARE_EQUAL(coordEnd.y, textBuffer.GetCursor().GetPosition().y); - - // restore text buffer info position - textBuffer.GetCursor().SetXPosition(coordOldTextInfoPos.x); - textBuffer.GetCursor().SetYPosition(coordOldTextInfoPos.y); - } - TEST_METHOD(TestWordByWordPrevious) { BEGIN_TEST_METHOD_PROPERTIES() diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index bcb4cec4a995..338d60468c23 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -1554,60 +1554,20 @@ void TextBufferTests::TestBackspaceStringsAPI() // should be the same. std::unique_ptr waiter; - size_t aCb = 2; - VERIFY_SUCCEEDED(DoWriteConsole(L"a", &aCb, si, false, waiter)); - - size_t seqCb = 6; Log::Comment(NoThrowString().Format( L"Using WriteCharsLegacy, write \\b \\b as a single string.")); - { - const auto str = L"\b \b"; - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr)); - - VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0); - VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0); - - Log::Comment(NoThrowString().Format( - L"Using DoWriteConsole, write \\b \\b as a single string.")); - VERIFY_SUCCEEDED(DoWriteConsole(L"a", &aCb, si, false, waiter)); - - VERIFY_SUCCEEDED(DoWriteConsole(str, &seqCb, si, false, waiter)); - VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0); - VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0); - } + size_t aCb = 2; + size_t seqCb = 6; + VERIFY_SUCCEEDED(DoWriteConsole(L"a", &aCb, si, false, waiter)); + VERIFY_SUCCEEDED(DoWriteConsole(L"\b \b", &seqCb, si, false, waiter)); + VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0); + VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0); seqCb = 2; - - Log::Comment(NoThrowString().Format( - L"Using DoWriteConsole, write \\b \\b as separate strings.")); - VERIFY_SUCCEEDED(DoWriteConsole(L"a", &seqCb, si, false, waiter)); VERIFY_SUCCEEDED(DoWriteConsole(L"\b", &seqCb, si, false, waiter)); VERIFY_SUCCEEDED(DoWriteConsole(L" ", &seqCb, si, false, waiter)); VERIFY_SUCCEEDED(DoWriteConsole(L"\b", &seqCb, si, false, waiter)); - - VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0); - VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0); - - Log::Comment(NoThrowString().Format( - L"Using WriteCharsLegacy, write \\b \\b as separate strings.")); - { - const auto str = L"a"; - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr)); - } - { - const auto str = L"\b"; - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr)); - } - { - const auto str = L" "; - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr)); - } - { - const auto str = L"\b"; - VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr)); - } - VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0); VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0); } diff --git a/src/host/ut_host/sources b/src/host/ut_host/sources index 161939e3c73c..9b47dded30dd 100644 --- a/src/host/ut_host/sources +++ b/src/host/ut_host/sources @@ -37,11 +37,6 @@ SOURCES = \ ConptyOutputTests.cpp \ ViewportTests.cpp \ ConsoleArgumentsTests.cpp \ - CommandLineTests.cpp \ - CommandListPopupTests.cpp \ - CommandNumberPopupTests.cpp \ - CopyFromCharPopupTests.cpp \ - CopyToCharPopupTests.cpp \ ObjectTests.cpp \ DefaultResource.rc \ diff --git a/src/interactivity/win32/menu.cpp b/src/interactivity/win32/menu.cpp index 205a01354980..7b59887daafe 100644 --- a/src/interactivity/win32/menu.cpp +++ b/src/interactivity/win32/menu.cpp @@ -507,13 +507,7 @@ void Menu::s_PropertiesUpdate(PCONSOLE_STATE_INFO pStateInfo) if (coordBuffer.width != coordScreenBufferSize.width || coordBuffer.height != coordScreenBufferSize.height) { - const auto pCommandLine = &CommandLine::Instance(); - - pCommandLine->Hide(FALSE); - LOG_IF_FAILED(ScreenInfo.ResizeScreenBuffer(coordBuffer, TRUE)); - - pCommandLine->Show(); } // Finally, restrict window size to the maximum possible size for the given buffer now that it's processed. diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 5b882feeeefc..f37c16e47959 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -383,7 +383,7 @@ void HandleKeyEvent(const HWND hWnd, } } // we need to check if there is an active popup because otherwise they won't be able to receive shift+key events - if (pSelection->s_IsValidKeyboardLineSelection(&inputKeyInfo) && IsInProcessedInputMode() && gci.PopupCount.load() == 0) + if (pSelection->s_IsValidKeyboardLineSelection(&inputKeyInfo) && IsInProcessedInputMode() && !gci.HasPendingPopup()) { if (!bKeyDown || pSelection->HandleKeyboardLineSelectionEvent(&inputKeyInfo)) {