Compare commits

...

22 commits

Author SHA1 Message Date
Christophe Pichaud 53d93be6f8 Convert CommandHistory to a std::shared_ptr (#1161)
* Changes for Issue #1058 are done in VS2017. Works fine.

* calculatePopupSize use a const CommandHistory& and is called from a shared_ptr.

* Some shared_ptr are converted into refrences.
Better appropriated.

* Add missing const stuff.
Remove a comment.

* cookedReadData.History() returns a ref. Callers adapted. OK.
2019-06-19 11:06:40 -07:00
Austin Diviness 09e56d1fd9 aliases work with CookedRead 2019-05-14 14:58:14 -07:00
Austin Diviness 3a247fa195 popups work and pass tests 2019-05-14 14:58:14 -07:00
Austin Diviness 934bae9e3f can fill prompt with previous command fragment 2019-05-14 14:58:13 -07:00
Austin Diviness 470c01b7ea can cycle through command history 2019-05-14 14:58:13 -07:00
Austin Diviness da61b9d3b9 cmdline can set prompt from up and down arrows 2019-05-14 14:58:13 -07:00
Austin Diviness dcc8a98a24 can delete codepoint to the right of the insertion point 2019-05-14 14:58:13 -07:00
Austin Diviness b812ec8a21 can delete text to the left or right of the insertion point 2019-05-14 14:58:13 -07:00
Austin Diviness 2ad4103726 can set history to newest command 2019-05-14 14:58:13 -07:00
Austin Diviness 79b6d3854b can set prompt to oldest command 2019-05-14 14:58:12 -07:00
Austin Diviness 87f1ca432e can insert ctrl+z into prompt 2019-05-14 14:58:12 -07:00
Austin Diviness 2ea9ebba2f can use backspace to delete a character 2019-05-14 14:58:12 -07:00
Austin Diviness 14cbd1335b can move right by word 2019-05-14 14:58:12 -07:00
Austin Diviness 4da9094dd6 can move left by word 2019-05-14 14:58:12 -07:00
Austin Diviness 387186d280 can move cursor to beginning and end of prompt text 2019-05-14 14:58:12 -07:00
Austin Diviness dcb0334724 fixed bug with storing command history with newlines 2019-05-14 14:58:11 -07:00
Austin Diviness f6550b1028 added command history storage on enter, moving right by character 2019-05-14 14:58:11 -07:00
Austin Diviness b8b87262c0 added doc comments, function to move insertion index to the right 2019-05-14 14:58:11 -07:00
Austin Diviness f1d29097ba can move the cursor left 2019-05-14 14:58:03 -07:00
Austin Diviness 033814c2bc can insert characters into middle of prompt 2019-05-14 14:57:23 -07:00
Austin Diviness 4ae0dd1500 turned CookedRead into a state machine 2019-05-14 14:57:23 -07:00
Austin Diviness 899dccfec9 can type emoji and run commands 2019-05-14 14:57:23 -07:00
36 changed files with 1705 additions and 1150 deletions

View file

@ -60,7 +60,7 @@ CommandListPopup::CommandListPopup(SCREEN_INFORMATION& screenInfo, const Command
}
[[nodiscard]]
NTSTATUS CommandListPopup::_handlePopupKeys(COOKED_READ_DATA& cookedReadData, const wchar_t wch, const DWORD modifiers) noexcept
NTSTATUS CommandListPopup::_handlePopupKeys(CookedRead& cookedReadData, const wchar_t wch, const DWORD modifiers) noexcept
{
try
{
@ -147,7 +147,7 @@ void CommandListPopup::_setBottomIndex()
}
[[nodiscard]]
NTSTATUS CommandListPopup::_deleteSelection(COOKED_READ_DATA& cookedReadData) noexcept
NTSTATUS CommandListPopup::_deleteSelection(CookedRead& cookedReadData) noexcept
{
try
{
@ -177,7 +177,7 @@ NTSTATUS CommandListPopup::_deleteSelection(COOKED_READ_DATA& cookedReadData) no
// Arguments:
// - cookedReadData - the read wait object to operate upon
[[nodiscard]]
NTSTATUS CommandListPopup::_swapUp(COOKED_READ_DATA& cookedReadData) noexcept
NTSTATUS CommandListPopup::_swapUp(CookedRead& cookedReadData) noexcept
{
try
{
@ -200,7 +200,7 @@ NTSTATUS CommandListPopup::_swapUp(COOKED_READ_DATA& cookedReadData) noexcept
// Arguments:
// - cookedReadData - the read wait object to operate upon
[[nodiscard]]
NTSTATUS CommandListPopup::_swapDown(COOKED_READ_DATA& cookedReadData) noexcept
NTSTATUS CommandListPopup::_swapDown(CookedRead& cookedReadData) noexcept
{
try
{
@ -218,57 +218,15 @@ NTSTATUS CommandListPopup::_swapDown(COOKED_READ_DATA& cookedReadData) noexcept
return STATUS_SUCCESS;
}
void CommandListPopup::_handleReturn(COOKED_READ_DATA& cookedReadData)
void CommandListPopup::_handleReturn(CookedRead& cookedReadData)
{
short Index = 0;
NTSTATUS Status = STATUS_SUCCESS;
DWORD LineCount = 1;
Index = _currentCommand;
const short index = _currentCommand;
CommandLine::Instance().EndCurrentPopup();
SetCurrentCommandLine(cookedReadData, (SHORT)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 size_t 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);
SetCurrentCommandLine(cookedReadData, index);
cookedReadData.BufferInput(UNICODE_CARRIAGERETURN);
}
void CommandListPopup::_cycleSelectionToMatchingCommands(COOKED_READ_DATA& cookedReadData, const wchar_t wch)
void CommandListPopup::_cycleSelectionToMatchingCommands(CookedRead& cookedReadData, const wchar_t wch)
{
short Index = 0;
if (cookedReadData.History().FindMatchingCommand({ &wch, 1 },
@ -286,7 +244,7 @@ void CommandListPopup::_cycleSelectionToMatchingCommands(COOKED_READ_DATA& cooke
// - 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
NTSTATUS CommandListPopup::Process(CookedRead& cookedReadData) noexcept
{
NTSTATUS Status = STATUS_SUCCESS;

View file

@ -24,7 +24,7 @@ public:
CommandListPopup(SCREEN_INFORMATION& screenInfo, const CommandHistory& history);
[[nodiscard]]
NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override;
NTSTATUS Process(CookedRead& cookedReadData) noexcept override;
protected:
void _DrawContent() override;
@ -34,17 +34,17 @@ private:
void _update(const SHORT delta, const bool wrap = false);
void _updateHighlight(const SHORT oldCommand, const SHORT newCommand);
void _handleReturn(COOKED_READ_DATA& cookedReadData);
void _cycleSelectionToMatchingCommands(COOKED_READ_DATA& cookedReadData, const wchar_t wch);
void _handleReturn(CookedRead& cookedReadData);
void _cycleSelectionToMatchingCommands(CookedRead& cookedReadData, const wchar_t wch);
void _setBottomIndex();
[[nodiscard]]
NTSTATUS _handlePopupKeys(COOKED_READ_DATA& cookedReadData, const wchar_t wch, const DWORD modifiers) noexcept;
NTSTATUS _handlePopupKeys(CookedRead& cookedReadData, const wchar_t wch, const DWORD modifiers) noexcept;
[[nodiscard]]
NTSTATUS _deleteSelection(COOKED_READ_DATA& cookedReadData) noexcept;
NTSTATUS _deleteSelection(CookedRead& cookedReadData) noexcept;
[[nodiscard]]
NTSTATUS _swapUp(COOKED_READ_DATA& cookedReadData) noexcept;
NTSTATUS _swapUp(CookedRead& cookedReadData) noexcept;
[[nodiscard]]
NTSTATUS _swapDown(COOKED_READ_DATA& cookedReadData) noexcept;
NTSTATUS _swapDown(CookedRead& cookedReadData) noexcept;
SHORT _currentCommand;
SHORT _bottomIndex; // number of command displayed on last line of popup

View file

@ -27,7 +27,7 @@ CommandNumberPopup::CommandNumberPopup(SCREEN_INFORMATION& screenInfo) :
// Arguments:
// - cookedReadData - read data to operate on
// - wch - digit to handle
void CommandNumberPopup::_handleNumber(COOKED_READ_DATA& cookedReadData, const wchar_t wch) noexcept
void CommandNumberPopup::_handleNumber(CookedRead& cookedReadData, const wchar_t wch) noexcept
{
if (_userInput.size() < COMMAND_NUMBER_LENGTH)
{
@ -41,7 +41,7 @@ void CommandNumberPopup::_handleNumber(COOKED_READ_DATA& cookedReadData, const w
&wch,
&CharsToWrite,
&NumSpaces,
cookedReadData.OriginalCursorPosition().X,
cookedReadData.PromptStartLocation().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
nullptr));
cookedReadData.ScreenInfo().SetAttributes(realAttributes);
@ -57,7 +57,7 @@ void CommandNumberPopup::_handleNumber(COOKED_READ_DATA& cookedReadData, const w
// - 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
void CommandNumberPopup::_handleBackspace(CookedRead& cookedReadData) noexcept
{
if (_userInput.size() > 0)
{
@ -72,7 +72,7 @@ void CommandNumberPopup::_handleBackspace(COOKED_READ_DATA& cookedReadData) noex
&backspace,
&CharsToWrite,
&NumSpaces,
cookedReadData.OriginalCursorPosition().X,
cookedReadData.PromptStartLocation().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
nullptr));
cookedReadData.ScreenInfo().SetAttributes(realAttributes);
@ -84,7 +84,7 @@ void CommandNumberPopup::_handleBackspace(COOKED_READ_DATA& cookedReadData) noex
// - handles escape user input. cancels the popup
// Arguments:
// - cookedReadData - read data to operate on
void CommandNumberPopup::_handleEscape(COOKED_READ_DATA& cookedReadData) noexcept
void CommandNumberPopup::_handleEscape(CookedRead& cookedReadData) noexcept
{
CommandLine::Instance().EndAllPopups();
@ -92,14 +92,14 @@ void CommandNumberPopup::_handleEscape(COOKED_READ_DATA& cookedReadData) noexcep
// 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));
LOG_IF_FAILED(cookedReadData.ScreenInfo().SetCursorPosition(cookedReadData.BeforePopupCursorPosition(), 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
void CommandNumberPopup::_handleReturn(CookedRead& cookedReadData) noexcept
{
const short commandNumber = gsl::narrow<short>(std::min(static_cast<size_t>(_parse()),
cookedReadData.History().GetNumberOfCommands() - 1));
@ -114,7 +114,7 @@ void CommandNumberPopup::_handleReturn(COOKED_READ_DATA& cookedReadData) noexcep
// - 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
NTSTATUS CommandNumberPopup::Process(CookedRead& cookedReadData) noexcept
{
NTSTATUS Status = STATUS_SUCCESS;
WCHAR wch = UNICODE_NULL;

View file

@ -24,7 +24,7 @@ public:
CommandNumberPopup(SCREEN_INFORMATION& screenInfo);
[[nodiscard]]
NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override;
NTSTATUS Process(CookedRead& cookedReadData) noexcept override;
protected:
void _DrawContent() override;
@ -32,10 +32,10 @@ protected:
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 _handleNumber(CookedRead& cookedReadData, const wchar_t wch) noexcept;
void _handleBackspace(CookedRead& cookedReadData) noexcept;
void _handleEscape(CookedRead& cookedReadData) noexcept;
void _handleReturn(CookedRead& cookedReadData) noexcept;
void _push(const wchar_t wch);
void _pop() noexcept;

View file

@ -20,7 +20,7 @@ CopyFromCharPopup::CopyFromCharPopup(SCREEN_INFORMATION& screenInfo) :
// - 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
NTSTATUS CopyFromCharPopup::Process(CookedRead& cookedReadData) noexcept
{
// get user input
WCHAR Char = UNICODE_NULL;
@ -39,7 +39,7 @@ NTSTATUS CopyFromCharPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept
return CONSOLE_STATUS_WAIT_NO_BLOCK;
}
const auto span = cookedReadData.SpanAtPointer();
const auto span = cookedReadData.PromptFromInsertionIndex();
const auto foundLocation = std::find(std::next(span.begin()), span.end(), Char);
if (foundLocation == span.end())
{

View file

@ -23,7 +23,7 @@ public:
CopyFromCharPopup(SCREEN_INFORMATION& screenInfo);
[[nodiscard]]
NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override;
NTSTATUS Process(CookedRead& cookedReadData) noexcept override;
protected:
void _DrawContent() override;

View file

@ -22,15 +22,15 @@ CopyToCharPopup::CopyToCharPopup(SCREEN_INFORMATION& screenInfo) :
// - 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)
void CopyToCharPopup::_copyToChar(CookedRead& 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())
if (cookedReadData.InsertionIndex() >= LastCommand.size())
{
return;
}
const auto searchStart = std::next(LastCommand.cbegin(), cookedReadData.InsertionPoint() + 1);
const auto searchStart = std::next(LastCommand.cbegin(), cookedReadData.InsertionIndex() + 1);
auto location = std::find(searchStart, LastCommand.cend(), wch);
// didn't find wch so copy nothing
@ -39,10 +39,10 @@ void CopyToCharPopup::_copyToChar(COOKED_READ_DATA& cookedReadData, const std::w
return;
}
const auto startIt = std::next(LastCommand.cbegin(), cookedReadData.InsertionPoint());
const auto endIt = location;
const std::wstring_view::const_iterator startIt = std::next(LastCommand.cbegin(), cookedReadData.InsertionIndex());
const std::wstring_view::const_iterator endIt = location;
cookedReadData.Write({ &*startIt, gsl::narrow<size_t>(std::distance(startIt, endIt)) });
cookedReadData.Overwrite(startIt, endIt);
}
// Routine Description:
@ -51,7 +51,7 @@ void CopyToCharPopup::_copyToChar(COOKED_READ_DATA& cookedReadData, const std::w
// - 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
NTSTATUS CopyToCharPopup::Process(CookedRead& cookedReadData) noexcept
{
wchar_t wch = UNICODE_NULL;
bool popupKey = false;

View file

@ -23,10 +23,10 @@ public:
CopyToCharPopup(SCREEN_INFORMATION& screenInfo);
[[nodiscard]]
NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override;
NTSTATUS Process(CookedRead& cookedReadData) noexcept override;
protected:
void _DrawContent() override;
private:
void _copyToChar(COOKED_READ_DATA& cookedReadData, const std::wstring_view LastCommand, const wchar_t wch);
void _copyToChar(CookedRead& cookedReadData, const std::wstring_view LastCommand, const wchar_t wch);
};

View file

@ -41,13 +41,13 @@ struct case_insensitive_equality
}
};
std::unordered_map<std::wstring,
std::unordered_map<std::wstring,
std::wstring,
case_insensitive_hash,
case_insensitive_equality>,
case_insensitive_hash,
case_insensitive_equality> g_aliasData;
static std::unordered_map<std::wstring,
std::unordered_map<std::wstring,
std::wstring,
case_insensitive_hash,
case_insensitive_equality>,
case_insensitive_hash,
case_insensitive_equality> g_aliasData;
// Routine Description:
// - Adds a command line alias to the global set.
@ -318,7 +318,7 @@ HRESULT GetConsoleAliasesLengthWImplHelper(const std::wstring_view exeName,
{
// Ensure output variables are initialized
bufferRequired = 0;
try
{
const std::wstring exeNameString(exeName);

View file

@ -221,109 +221,28 @@ void CommandLine::EndAllPopups()
}
}
void DeleteCommandLine(COOKED_READ_DATA& cookedReadData, const bool fUpdateFields)
void DeleteCommandLine(CookedRead& cookedReadData, const bool fUpdateFields)
{
size_t CharsToWrite = cookedReadData.VisibleCharCount();
COORD coordOriginalCursor = cookedReadData.OriginalCursorPosition();
const COORD 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.X * 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.X - 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));
else
{
cookedReadData.Hide();
}
}
void RedrawCommandLine(COOKED_READ_DATA& cookedReadData)
void RedrawCommandLine(CookedRead& cookedReadData)
{
if (cookedReadData.IsEchoInput())
{
// Draw the command line
cookedReadData.OriginalCursorPosition() = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition();
SHORT ScrollY = 0;
#pragma prefast(suppress:28931, "Status is not unused. It's used in debug assertions.")
NTSTATUS Status = WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferStartPtr(),
&cookedReadData.BytesRead(),
&cookedReadData.VisibleCharCount(),
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&ScrollY);
FAIL_FAST_IF_NTSTATUS_FAILED(Status);
cookedReadData.OriginalCursorPosition().Y += ScrollY;
// Move the cursor back to the right position
COORD CursorPosition = cookedReadData.OriginalCursorPosition();
CursorPosition.X += (SHORT)RetrieveTotalNumberOfSpaces(cookedReadData.OriginalCursorPosition().X,
cookedReadData.BufferStartPtr(),
cookedReadData.InsertionPoint());
if (CheckBisectStringW(cookedReadData.BufferStartPtr(),
cookedReadData.InsertionPoint(),
cookedReadData.ScreenInfo().GetBufferSize().Width() - cookedReadData.OriginalCursorPosition().X))
{
CursorPosition.X++;
}
Status = AdjustCursorPosition(cookedReadData.ScreenInfo(), CursorPosition, TRUE, nullptr);
FAIL_FAST_IF_NTSTATUS_FAILED(Status);
}
cookedReadData.Show();
}
// Routine Description:
// - This routine copies the commandline specified by Index into the cooked read buffer
void SetCurrentCommandLine(COOKED_READ_DATA& cookedReadData, _In_ SHORT Index) // index, not command number
void SetCurrentCommandLine(CookedRead& cookedReadData, _In_ SHORT 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())
{
SHORT ScrollY = 0;
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr(),
&cookedReadData.BytesRead(),
&cookedReadData.VisibleCharCount(),
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&ScrollY));
cookedReadData.OriginalCursorPosition().Y += ScrollY;
}
size_t const CharsToWrite = cookedReadData.BytesRead() / sizeof(WCHAR);
cookedReadData.InsertionPoint() = CharsToWrite;
cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CharsToWrite);
cookedReadData.SetPromptToCommand(gsl::narrow<size_t>(Index));
}
// Routine Description:
@ -332,7 +251,7 @@ void SetCurrentCommandLine(COOKED_READ_DATA& cookedReadData, _In_ SHORT Index) /
// - 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)
NTSTATUS CommandLine::_startCommandListPopup(CookedRead& cookedReadData)
{
if (cookedReadData.HasHistory() &&
cookedReadData.History().GetNumberOfCommands())
@ -358,7 +277,7 @@ NTSTATUS CommandLine::_startCommandListPopup(COOKED_READ_DATA& cookedReadData)
// - 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)
NTSTATUS CommandLine::_startCopyFromCharPopup(CookedRead& cookedReadData)
{
// Delete the current command from cursor position to the
// letter specified by the user. The user is prompted via
@ -386,7 +305,7 @@ NTSTATUS CommandLine::_startCopyFromCharPopup(COOKED_READ_DATA& cookedReadData)
// - 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)
NTSTATUS CommandLine::_startCopyToCharPopup(CookedRead& cookedReadData)
{
// copy the previous command to the current command, up to but
// not including the character specified by the user. the user
@ -414,7 +333,7 @@ NTSTATUS CommandLine::_startCopyToCharPopup(COOKED_READ_DATA& cookedReadData)
// - 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)
HRESULT CommandLine::StartCommandNumberPopup(CookedRead& cookedReadData)
{
if (cookedReadData.HasHistory() &&
cookedReadData.History().GetNumberOfCommands() &&
@ -426,7 +345,7 @@ HRESULT CommandLine::StartCommandNumberPopup(COOKED_READ_DATA& cookedReadData)
popup.Draw();
// Save the original cursor position in case the user cancels out of the dialog
cookedReadData.BeforeDialogCursorPosition() = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition();
//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 COORD CursorPosition = popup.GetCursorPosition();
@ -451,10 +370,9 @@ HRESULT CommandLine::StartCommandNumberPopup(COOKED_READ_DATA& cookedReadData)
// - searchDirection - Direction in history to search
// Note:
// - May throw exceptions
void CommandLine::_processHistoryCycling(COOKED_READ_DATA& cookedReadData,
void CommandLine::_processHistoryCycling(CookedRead& 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())
@ -472,28 +390,7 @@ void CommandLine::_processHistoryCycling(COOKED_READ_DATA& cookedReadData,
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())
{
short ScrollY = 0;
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr(),
&cookedReadData.BytesRead(),
&cookedReadData.VisibleCharCount(),
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&ScrollY));
cookedReadData.OriginalCursorPosition().Y += ScrollY;
}
const size_t CharsToWrite = cookedReadData.BytesRead() / sizeof(WCHAR);
cookedReadData.InsertionPoint() = CharsToWrite;
cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CharsToWrite);
cookedReadData.SetPromptToCommand(searchDirection);
}
// Routine Description:
@ -502,34 +399,9 @@ void CommandLine::_processHistoryCycling(COOKED_READ_DATA& cookedReadData,
// - cookedReadData - The cooked read data to operate on
// Note:
// - May throw exceptions
void CommandLine::_setPromptToOldestCommand(COOKED_READ_DATA& cookedReadData)
void CommandLine::_setPromptToOldestCommand(CookedRead& 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())
{
short ScrollY = 0;
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr(),
&cookedReadData.BytesRead(),
&cookedReadData.VisibleCharCount(),
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&ScrollY));
cookedReadData.OriginalCursorPosition().Y += ScrollY;
}
size_t CharsToWrite = cookedReadData.BytesRead() / sizeof(WCHAR);
cookedReadData.InsertionPoint() = CharsToWrite;
cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CharsToWrite);
}
cookedReadData.SetPromptToOldestCommand();
}
// Routine Description:
@ -538,56 +410,18 @@ void CommandLine::_setPromptToOldestCommand(COOKED_READ_DATA& cookedReadData)
// - cookedReadData - The cooked read data to operate on
// Note:
// - May throw exceptions
void CommandLine::_setPromptToNewestCommand(COOKED_READ_DATA& cookedReadData)
void CommandLine::_setPromptToNewestCommand(CookedRead& cookedReadData)
{
DeleteCommandLine(cookedReadData, true);
if (cookedReadData.HasHistory() && cookedReadData.History().GetNumberOfCommands())
{
const short 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())
{
short ScrollY = 0;
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr(),
&cookedReadData.BytesRead(),
&cookedReadData.VisibleCharCount(),
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&ScrollY));
cookedReadData.OriginalCursorPosition().Y += ScrollY;
}
size_t CharsToWrite = cookedReadData.BytesRead() / sizeof(WCHAR);
cookedReadData.InsertionPoint() = CharsToWrite;
cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + CharsToWrite);
}
cookedReadData.SetPromptToNewestCommand();
}
// 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
void CommandLine::DeletePromptAfterCursor(CookedRead& 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_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
nullptr));
}
cookedReadData.DeletePromptAfterInsertionIndex();
}
// Routine Description:
@ -596,26 +430,13 @@ void CommandLine::DeletePromptAfterCursor(COOKED_READ_DATA& cookedReadData) noex
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - The new cursor position
COORD CommandLine::_deletePromptBeforeCursor(COOKED_READ_DATA& cookedReadData) noexcept
COORD CommandLine::_deletePromptBeforeCursor(CookedRead& 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_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
nullptr));
}
return cookedReadData.OriginalCursorPosition();
COORD cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition();
const size_t cellsMoved = cookedReadData.DeletePromptBeforeInsertionIndex();
// the cursor is adjusted to be within the bounds of the screen later, don't need to worry about it here
cursorPosition.X -= gsl::narrow<short>(cellsMoved);
return cursorPosition;
}
// Routine Description:
@ -624,24 +445,12 @@ COORD CommandLine::_deletePromptBeforeCursor(COOKED_READ_DATA& cookedReadData) n
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - The new cursor position
COORD CommandLine::_moveCursorToEndOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept
COORD CommandLine::_moveCursorToEndOfPrompt(CookedRead& cookedReadData) noexcept
{
cookedReadData.InsertionPoint() = cookedReadData.BytesRead() / sizeof(WCHAR);
cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr() + cookedReadData.InsertionPoint());
COORD cursorPosition{ 0, 0 };
cursorPosition.X = (SHORT)(cookedReadData.OriginalCursorPosition().X + cookedReadData.VisibleCharCount());
cursorPosition.Y = cookedReadData.OriginalCursorPosition().Y;
const SHORT sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width();
if (CheckBisectProcessW(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.InsertionPoint(),
sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().X,
cookedReadData.OriginalCursorPosition().X,
true))
{
cursorPosition.X++;
}
COORD cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition();
const size_t cellsMoved = cookedReadData.MoveInsertionIndexToEnd();
// the cursor is adjusted to be within the bounds of the screen later, don't need to worry about it here
cursorPosition.X += gsl::narrow<short>(cellsMoved);
return cursorPosition;
}
@ -651,11 +460,10 @@ COORD CommandLine::_moveCursorToEndOfPrompt(COOKED_READ_DATA& cookedReadData) no
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - The new cursor position
COORD CommandLine::_moveCursorToStartOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept
COORD CommandLine::_moveCursorToStartOfPrompt(CookedRead& cookedReadData) noexcept
{
cookedReadData.InsertionPoint() = 0;
cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferStartPtr());
return cookedReadData.OriginalCursorPosition();
cookedReadData.MoveInsertionIndexToStart();
return cookedReadData.PromptStartLocation();
}
// Routine Description:
@ -664,82 +472,12 @@ COORD CommandLine::_moveCursorToStartOfPrompt(COOKED_READ_DATA& cookedReadData)
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - New cursor position
COORD CommandLine::_moveCursorLeftByWord(COOKED_READ_DATA& cookedReadData) noexcept
COORD CommandLine::_moveCursorLeftByWord(CookedRead& cookedReadData) noexcept
{
PWCHAR LastWord;
COORD 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 expeced
// insertion point.
++LastWord;
}
cookedReadData.SetBufferCurrentPtr(LastWord);
}
cookedReadData.InsertionPoint() = (ULONG)(cookedReadData.BufferCurrentPtr() - cookedReadData.BufferStartPtr());
cursorPosition = cookedReadData.OriginalCursorPosition();
cursorPosition.X = (SHORT)(cursorPosition.X +
RetrieveTotalNumberOfSpaces(cookedReadData.OriginalCursorPosition().X,
cookedReadData.BufferStartPtr(),
cookedReadData.InsertionPoint()));
const SHORT sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width();
if (CheckBisectStringW(cookedReadData.BufferStartPtr(),
cookedReadData.InsertionPoint() + 1,
sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().X))
{
cursorPosition.X++;
}
}
const size_t cellsMoved = cookedReadData.MoveInsertionIndexLeftByWord();
// the cursor is adjusted to be within the bounds of the screen later, don't need to worry about it here
cursorPosition.X -= gsl::narrow<short>(cellsMoved);
return cursorPosition;
}
@ -749,33 +487,12 @@ COORD CommandLine::_moveCursorLeftByWord(COOKED_READ_DATA& cookedReadData) noexc
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - New cursor position
COORD CommandLine::_moveCursorLeft(COOKED_READ_DATA& cookedReadData)
COORD CommandLine::_moveCursorLeft(CookedRead& cookedReadData)
{
COORD 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 = (SHORT)(cursorPosition.X -
RetrieveNumberOfSpaces(cookedReadData.OriginalCursorPosition().X,
cookedReadData.BufferStartPtr(),
cookedReadData.InsertionPoint()));
const SHORT 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--;
}
}
}
const size_t cellsMoved = cookedReadData.MoveInsertionIndexLeft();
// the cursor is adjusted to be within the bounds of the screen later, don't need to worry about it here
cursorPosition.X -= gsl::narrow<short>(cellsMoved);
return cursorPosition;
}
@ -785,70 +502,12 @@ COORD CommandLine::_moveCursorLeft(COOKED_READ_DATA& cookedReadData)
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - The new cursor position
COORD CommandLine::_moveCursorRightByWord(COOKED_READ_DATA& cookedReadData) noexcept
COORD CommandLine::_moveCursorRightByWord(CookedRead& cookedReadData) noexcept
{
COORD cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition();
if (cookedReadData.InsertionPoint() < (cookedReadData.BytesRead() / sizeof(WCHAR)))
{
PWCHAR NextWord = cookedReadData.BufferCurrentPtr();
// A bit better word skipping.
PWCHAR 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;
}
++NextWord;
}
}
else
{
// Skip the body part.
bool fStartFromDelim = IsWordDelim(*NextWord);
while (++NextWord < BufLast)
{
if (fStartFromDelim != IsWordDelim(*NextWord))
{
break;
}
}
// Skip the space block.
if (NextWord < BufLast && *NextWord == L' ')
{
while (++NextWord < BufLast)
{
if (*NextWord != L' ')
{
break;
}
}
}
}
cookedReadData.SetBufferCurrentPtr(NextWord);
cookedReadData.InsertionPoint() = (ULONG)(cookedReadData.BufferCurrentPtr() - cookedReadData.BufferStartPtr());
cursorPosition = cookedReadData.OriginalCursorPosition();
cursorPosition.X = (SHORT)(cursorPosition.X +
RetrieveTotalNumberOfSpaces(cookedReadData.OriginalCursorPosition().X,
cookedReadData.BufferStartPtr(),
cookedReadData.InsertionPoint()));
const SHORT sScreenBufferSizeX = cookedReadData.ScreenInfo().GetBufferSize().Width();
if (CheckBisectStringW(cookedReadData.BufferStartPtr(),
cookedReadData.InsertionPoint() + 1,
sScreenBufferSizeX - cookedReadData.OriginalCursorPosition().X))
{
cursorPosition.X++;
}
}
const size_t cellsMoved = cookedReadData.MoveInsertionIndexRightByWord();
// the cursor is adjusted to be within the bounds of the screen later, don't need to worry about it here
cursorPosition.X += gsl::narrow<short>(cellsMoved);
return cursorPosition;
}
@ -858,71 +517,12 @@ COORD CommandLine::_moveCursorRightByWord(COOKED_READ_DATA& cookedReadData) noex
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - The new cursor position
COORD CommandLine::_moveCursorRight(COOKED_READ_DATA& cookedReadData) noexcept
COORD CommandLine::_moveCursorRight(CookedRead& cookedReadData) noexcept
{
COORD cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition();
const SHORT 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 = (SHORT)(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))
{
if (cursorPosition.X == (sScreenBufferSizeX - 1))
cursorPosition.X++;
}
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())
{
short ScrollY = 0;
size_t CharsToWrite = sizeof(WCHAR);
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr(),
&CharsToWrite,
&NumSpaces,
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&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);
}
}
const size_t cellsMoved = cookedReadData.MoveInsertionIndexRight();
// the cursor is adjusted to be within the bounds of the screen later, don't need to worry about it here
cursorPosition.X += gsl::narrow<short>(cellsMoved);
return cursorPosition;
}
@ -930,37 +530,16 @@ COORD CommandLine::_moveCursorRight(COOKED_READ_DATA& cookedReadData) noexcept
// - 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
void CommandLine::_insertCtrlZ(CookedRead& cookedReadData) noexcept
{
size_t NumSpaces = 0;
*cookedReadData.BufferCurrentPtr() = (WCHAR)0x1a; // ctrl-z
cookedReadData.BytesRead() += sizeof(WCHAR);
cookedReadData.InsertionPoint()++;
if (cookedReadData.IsEchoInput())
{
short ScrollY = 0;
size_t CharsToWrite = sizeof(WCHAR);
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr(),
&CharsToWrite,
&NumSpaces,
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&ScrollY));
cookedReadData.OriginalCursorPosition().Y += ScrollY;
cookedReadData.VisibleCharCount() += NumSpaces;
}
cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferCurrentPtr() + 1);
cookedReadData.InsertCtrlZ();
}
// 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
void CommandLine::_deleteCommandHistory(CookedRead& cookedReadData) noexcept
{
if (cookedReadData.HasHistory())
{
@ -973,39 +552,9 @@ void CommandLine::_deleteCommandHistory(COOKED_READ_DATA& cookedReadData) noexce
// - 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
void CommandLine::_fillPromptWithPreviousCommandFragment(CookedRead& 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())
{
short ScrollY = 0;
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr(),
&cchCount,
&NumSpaces,
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&ScrollY));
cookedReadData.OriginalCursorPosition().Y += ScrollY;
cookedReadData.VisibleCharCount() += NumSpaces;
}
cookedReadData.SetBufferCurrentPtr(cookedReadData.BufferCurrentPtr() + cchCount / sizeof(WCHAR));
}
}
cookedReadData.FillPromptWithPreviousCommandFragment();
}
// Routine Description:
@ -1014,50 +563,9 @@ void CommandLine::_fillPromptWithPreviousCommandFragment(COOKED_READ_DATA& cooke
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - The new cursor position
COORD CommandLine::_cycleMatchingCommandHistoryToPrompt(COOKED_READ_DATA& cookedReadData)
void CommandLine::_cycleMatchingCommandHistoryToPrompt(CookedRead& cookedReadData)
{
COORD cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition();
if (cookedReadData.HasHistory())
{
SHORT index;
if (cookedReadData.History().FindMatchingCommand({ cookedReadData.BufferStartPtr(), cookedReadData.InsertionPoint() },
cookedReadData.History().LastDisplayed,
index,
CommandHistory::MatchOptions::None))
{
SHORT CurrentPos;
// save cursor position
CurrentPos = (SHORT)cookedReadData.InsertionPoint();
DeleteCommandLine(cookedReadData, true);
THROW_IF_FAILED(cookedReadData.History().RetrieveNth((SHORT)index,
cookedReadData.SpanWholeBuffer(),
cookedReadData.BytesRead()));
FAIL_FAST_IF(!(cookedReadData.BufferStartPtr() == cookedReadData.BufferCurrentPtr()));
if (cookedReadData.IsEchoInput())
{
short ScrollY = 0;
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
cookedReadData.BufferStartPtr(),
cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr(),
&cookedReadData.BytesRead(),
&cookedReadData.VisibleCharCount(),
cookedReadData.OriginalCursorPosition().X,
WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
&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;
cookedReadData.SetPromptToMatchingHistoryCommand();
}
// Routine Description:
@ -1066,55 +574,9 @@ COORD CommandLine::_cycleMatchingCommandHistoryToPrompt(COOKED_READ_DATA& cooked
// - cookedReadData - The cooked read data to operate on
// Return Value:
// - The new cursor position
COORD CommandLine::DeleteFromRightOfCursor(COOKED_READ_DATA& cookedReadData) noexcept
void CommandLine::DeleteFromRightOfCursor(CookedRead& cookedReadData) noexcept
{
// save cursor position
COORD cursorPosition = cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().GetPosition();
if (!cookedReadData.AtEol())
{
// Delete commandline.
#pragma prefast(suppress:__WARNING_BUFFER_OVERFLOW, "Not sure why prefast is getting confused here")
DeleteCommandLine(cookedReadData, false);
// Delete char.
cookedReadData.BytesRead() -= sizeof(WCHAR);
memmove(cookedReadData.BufferCurrentPtr(),
cookedReadData.BufferCurrentPtr() + 1,
cookedReadData.BytesRead() - (cookedReadData.InsertionPoint() * sizeof(WCHAR)));
{
PWCHAR 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_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_ECHO,
nullptr));
}
// restore cursor position
const SHORT 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;
cookedReadData.DeleteFromRightOfInsertionIndex();
}
// TODO: [MSFT:4586207] Clean up this mess -- needs helpers. http://osgvsowi/4586207
@ -1125,7 +587,7 @@ COORD CommandLine::DeleteFromRightOfCursor(COOKED_READ_DATA& cookedReadData) noe
// - CONSOLE_STATUS_READ_COMPLETE - user hit <enter> in CommandListPopup
// - STATUS_SUCCESS - everything's cool
[[nodiscard]]
NTSTATUS CommandLine::ProcessCommandLine(COOKED_READ_DATA& cookedReadData,
NTSTATUS CommandLine::ProcessCommandLine(CookedRead& cookedReadData,
_In_ WCHAR wch,
const DWORD dwKeyState)
{
@ -1281,6 +743,7 @@ NTSTATUS CommandLine::ProcessCommandLine(COOKED_READ_DATA& cookedReadData,
if (!ctrlPressed && !altPressed)
{
Status = _startCommandListPopup(cookedReadData);
return Status;
}
else if (altPressed)
{
@ -1291,8 +754,7 @@ NTSTATUS CommandLine::ProcessCommandLine(COOKED_READ_DATA& cookedReadData,
case VK_F8:
try
{
cursorPosition = _cycleMatchingCommandHistoryToPrompt(cookedReadData);
UpdateCursorPosition = true;
_cycleMatchingCommandHistoryToPrompt(cookedReadData);
}
catch (...)
{
@ -1324,8 +786,7 @@ NTSTATUS CommandLine::ProcessCommandLine(COOKED_READ_DATA& cookedReadData,
cookedReadData.ScreenInfo().SetCursorDBMode(cookedReadData.IsInsertMode() != gci.GetInsertMode());
break;
case VK_DELETE:
cursorPosition = DeleteFromRightOfCursor(cookedReadData);
UpdateCursorPosition = true;
DeleteFromRightOfCursor(cookedReadData);
break;
default:
FAIL_FAST_HR(E_NOTIMPL);

View file

@ -60,8 +60,8 @@ Notes:
#include "history.h"
#include "alias.h"
#include "readDataCooked.hpp"
#include "popup.h"
#include "cookedRead.hpp"
class CommandLine
@ -77,12 +77,12 @@ public:
bool IsVisible() const noexcept;
[[nodiscard]]
NTSTATUS ProcessCommandLine(COOKED_READ_DATA& cookedReadData,
NTSTATUS ProcessCommandLine(CookedRead& cookedReadData,
_In_ WCHAR wch,
const DWORD dwKeyState);
[[nodiscard]]
HRESULT StartCommandNumberPopup(COOKED_READ_DATA& cookedReadData);
HRESULT StartCommandNumberPopup(CookedRead& cookedReadData);
bool HasPopup() const noexcept;
Popup& GetPopup();
@ -95,8 +95,8 @@ public:
void EndCurrentPopup();
void EndAllPopups();
void DeletePromptAfterCursor(COOKED_READ_DATA& cookedReadData) noexcept;
COORD DeleteFromRightOfCursor(COOKED_READ_DATA& cookedReadData) noexcept;
void DeletePromptAfterCursor(CookedRead& cookedReadData) noexcept;
void DeleteFromRightOfCursor(CookedRead& cookedReadData) noexcept;
protected:
CommandLine();
@ -105,26 +105,26 @@ protected:
CommandLine& operator=(CommandLine const&) = delete;
[[nodiscard]]
NTSTATUS CommandLine::_startCommandListPopup(COOKED_READ_DATA& cookedReadData);
NTSTATUS CommandLine::_startCommandListPopup(CookedRead& cookedReadData);
[[nodiscard]]
NTSTATUS CommandLine::_startCopyFromCharPopup(COOKED_READ_DATA& cookedReadData);
NTSTATUS CommandLine::_startCopyFromCharPopup(CookedRead& cookedReadData);
[[nodiscard]]
NTSTATUS CommandLine::_startCopyToCharPopup(COOKED_READ_DATA& cookedReadData);
NTSTATUS CommandLine::_startCopyToCharPopup(CookedRead& cookedReadData);
void _processHistoryCycling(COOKED_READ_DATA& cookedReadData, const CommandHistory::SearchDirection searchDirection);
void _setPromptToOldestCommand(COOKED_READ_DATA& cookedReadData);
void _setPromptToNewestCommand(COOKED_READ_DATA& cookedReadData);
COORD _deletePromptBeforeCursor(COOKED_READ_DATA& cookedReadData) noexcept;
COORD _moveCursorToEndOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept;
COORD _moveCursorToStartOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept;
COORD _moveCursorLeftByWord(COOKED_READ_DATA& cookedReadData) noexcept;
COORD _moveCursorLeft(COOKED_READ_DATA& cookedReadData);
COORD _moveCursorRightByWord(COOKED_READ_DATA& cookedReadData) noexcept;
COORD _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;
COORD _cycleMatchingCommandHistoryToPrompt(COOKED_READ_DATA& cookedReadData);
void _processHistoryCycling(CookedRead& cookedReadData, const CommandHistory::SearchDirection searchDirection);
void _setPromptToOldestCommand(CookedRead& cookedReadData);
void _setPromptToNewestCommand(CookedRead& cookedReadData);
COORD _deletePromptBeforeCursor(CookedRead& cookedReadData) noexcept;
COORD _moveCursorToEndOfPrompt(CookedRead& cookedReadData) noexcept;
COORD _moveCursorToStartOfPrompt(CookedRead& cookedReadData) noexcept;
COORD _moveCursorLeftByWord(CookedRead& cookedReadData) noexcept;
COORD _moveCursorLeft(CookedRead& cookedReadData);
COORD _moveCursorRightByWord(CookedRead& cookedReadData) noexcept;
COORD _moveCursorRight(CookedRead& cookedReadData) noexcept;
void _insertCtrlZ(CookedRead& cookedReadData) noexcept;
void _deleteCommandHistory(CookedRead& cookedReadData) noexcept;
void _fillPromptWithPreviousCommandFragment(CookedRead& cookedReadData) noexcept;
void _cycleMatchingCommandHistoryToPrompt(CookedRead& cookedReadData);
#ifdef UNIT_TESTING
@ -139,9 +139,9 @@ private:
};
void DeleteCommandLine(COOKED_READ_DATA& cookedReadData, const bool fUpdateFields);
void DeleteCommandLine(CookedRead& cookedReadData, const bool fUpdateFields);
void RedrawCommandLine(COOKED_READ_DATA& cookedReadData);
void RedrawCommandLine(CookedRead& cookedReadData);
// Values for WriteChars(), WriteCharsLegacy() dwFlags
#define WC_DESTRUCTIVE_BACKSPACE 0x01
@ -166,4 +166,4 @@ HRESULT DoSrvSetConsoleTitleW(const std::wstring_view title) noexcept;
bool IsValidStringBuffer(_In_ bool Unicode, _In_reads_bytes_(Size) PVOID Buffer, _In_ ULONG Size, _In_ ULONG Count, ...);
void SetCurrentCommandLine(COOKED_READ_DATA& cookedReadData, _In_ SHORT Index);
void SetCurrentCommandLine(CookedRead& cookedReadData, _In_ SHORT Index);

View file

@ -163,17 +163,17 @@ bool CONSOLE_INFORMATION::HasPendingCookedRead() const noexcept
return _cookedReadData != nullptr;
}
const COOKED_READ_DATA& CONSOLE_INFORMATION::CookedReadData() const noexcept
const CookedRead& CONSOLE_INFORMATION::CookedReadData() const noexcept
{
return *_cookedReadData;
}
COOKED_READ_DATA& CONSOLE_INFORMATION::CookedReadData() noexcept
CookedRead& CONSOLE_INFORMATION::CookedReadData() noexcept
{
return *_cookedReadData;
}
void CONSOLE_INFORMATION::SetCookedReadData(COOKED_READ_DATA* readData) noexcept
void CONSOLE_INFORMATION::SetCookedReadData(CookedRead* readData) noexcept
{
_cookedReadData = readData;
}

1112
src/host/cookedRead.cpp Normal file

File diff suppressed because it is too large Load diff

172
src/host/cookedRead.hpp Normal file
View file

@ -0,0 +1,172 @@
#pragma once
#include "readData.hpp"
#include "screenInfo.hpp"
#include "history.h"
class CookedRead final : public ReadData
{
public:
CookedRead(InputBuffer* const pInputBuffer,
INPUT_READ_HANDLE_DATA* const pInputReadHandleData,
SCREEN_INFORMATION& screenInfo,
std::shared_ptr<CommandHistory> pCommandHistory,
wchar_t* userBuffer,
const size_t cchUserBuffer,
const ULONG ctrlWakeupMask,
const std::wstring_view exeName
);
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);
[[nodiscard]]
NTSTATUS Read(const bool isUnicode,
size_t& numBytes,
ULONG& controlKeyState) noexcept;
void Erase();
void Hide();
void Show();
bool IsInsertMode() const noexcept;
void SetInsertMode(const bool mode) noexcept;
bool IsEchoInput() const noexcept;
COORD PromptStartLocation() const noexcept;
COORD BeforePopupCursorPosition() const noexcept;
size_t VisibleCharCount() const;
size_t MoveInsertionIndexLeft();
size_t MoveInsertionIndexRight();
void MoveInsertionIndexToStart() noexcept;
size_t MoveInsertionIndexToEnd();
size_t MoveInsertionIndexLeftByWord();
size_t MoveInsertionIndexRightByWord();
void SetPromptToOldestCommand();
void SetPromptToNewestCommand();
void SetPromptToCommand(const size_t index);
void SetPromptToCommand(const CommandHistory::SearchDirection searchDirection);
void SetPromptToMatchingHistoryCommand();
void FillPromptWithPreviousCommandFragment();
void Overwrite(const std::wstring_view::const_iterator startIt, const std::wstring_view::const_iterator endIt);
size_t DeletePromptBeforeInsertionIndex();
void DeletePromptAfterInsertionIndex();
void DeleteFromRightOfInsertionIndex();
void InsertCtrlZ();
SCREEN_INFORMATION& ScreenInfo();
CommandHistory& History() noexcept;
bool HasHistory() const noexcept;
void BufferInput(const wchar_t wch);
std::wstring_view Prompt();
std::wstring_view PromptFromInsertionIndex();
size_t InsertionIndex() const noexcept;
private:
enum class ReadState
{
Ready, // ready to read more user input
Wait, // need to wait for more input
Error, // something went wrong
GotChar, // read a character from the user
Complete, // the prompt has been completed (user pressed enter)
CommandKey, // the user pressed a command editing key
Popup
};
static bool _isSurrogatePairAt(const std::wstring_view wstrView, const size_t index);
static size_t _visibleCharCountOf(const std::wstring_view wstrView);
bool _isTailSurrogatePair() const;
bool _isSurrogatePairAt(const size_t index) const;
bool _isCtrlWakeupMaskTriggered(const wchar_t wch) const noexcept;
bool _insertMode;
// the buffer that the prompt data will go into in order to finish the ReadConsole api call
wchar_t* _userBuffer;
size_t _cchUserBuffer;
SCREEN_INFORMATION& _screenInfo;
// storage for the prompt text data
std::wstring _prompt;
// the location of where the interactive portion of the prompt starts
COORD _promptStartLocation;
// the location of the cursor before a popup is launched
COORD _beforePopupCursorPosition;
std::shared_ptr<CommandHistory> _pCommandHistory;
// mask of control keys that if pressed will end the cooked read early
const ULONG _ctrlWakeupMask;
// current state of the CookedRead
ReadState _state;
NTSTATUS _status;
// index of _prompt for character insertion
size_t _insertionIndex;
// used for special command line keys
bool _commandLineEditingKeys;
DWORD _keyState;
wchar_t _commandKeyChar;
bool _echoInput;
std::deque<wchar_t> _bufferedInput;
// name of the application that requested the read. used for alias transformation.
std::wstring _exeName;
void _readChar(std::deque<wchar_t>& unprocessedChars);
void _processChars(std::deque<wchar_t>& unprocessedChars);
void _error();
void _wait();
void _complete(size_t& numBytes);
void _commandKey();
void _popup();
void _backspace();
void _clearPromptCells();
void _writeToPrompt(std::deque<wchar_t>& unprocessedChars);
void _writeToScreen(const bool resetCursor);
size_t _calculatePromptCellLength(const bool wholePrompt) const;
bool _isInsertionIndexAtPromptBegin();
bool _isInsertionIndexAtPromptEnd();
void _adjustCursorToInsertionIndex();
#if UNIT_TESTING
friend class CommandLineTests;
friend class CopyFromCharPopupTests;
friend class CopyToCharPopupTests;
friend class CommandNumberPopupTests;
friend class CommandListPopupTests;
friend class PopupTestHelper;
public:
inline void SetPromptStartLocation(const COORD location)
{
_promptStartLocation = location;
}
#endif
};

View file

@ -19,6 +19,7 @@
#include "ApiRoutines.h"
#include "..\interactivity\inc\ServiceLocator.hpp"
#include "cookedRead.hpp"
#pragma hdrstop
@ -347,6 +348,7 @@ HRESULT ApiRoutines::SetConsoleInputModeImpl(InputBuffer& context, const ULONG m
if (gci.HasPendingCookedRead())
{
gci.CookedReadData().SetInsertMode(gci.GetInsertMode());
;
}
}
}

View file

@ -28,16 +28,16 @@
// (where other collections like deque do not.)
// If CommandHistory::s_Allocate and friends stop shuffling elements
// for maintaining LRU, then this datatype can be changed.
std::list<CommandHistory> CommandHistory::s_historyLists;
std::list<std::shared_ptr<CommandHistory>> CommandHistory::s_historyLists;
CommandHistory* CommandHistory::s_Find(const HANDLE processHandle)
std::shared_ptr<CommandHistory> CommandHistory::s_Find(const HANDLE processHandle)
{
for (auto& historyList : s_historyLists)
for (auto historyList : s_historyLists)
{
if (historyList._processHandle == processHandle)
if (historyList->_processHandle == processHandle)
{
FAIL_FAST_IF(WI_IsFlagClear(historyList.Flags, CLE_ALLOCATED));
return &historyList;
FAIL_FAST_IF(WI_IsFlagClear(historyList->Flags, CLE_ALLOCATED));
return historyList;
}
}
@ -50,7 +50,7 @@ CommandHistory* CommandHistory::s_Find(const HANDLE processHandle)
// - processHandle - handle to client process.
void CommandHistory::s_Free(const HANDLE processHandle)
{
CommandHistory* const History = CommandHistory::s_Find(processHandle);
const std::shared_ptr<CommandHistory>& History = CommandHistory::s_Find(processHandle);
if (History)
{
WI_ClearFlag(History->Flags, CLE_ALLOCATED);
@ -64,9 +64,9 @@ void CommandHistory::s_ResizeAll(const size_t commands)
FAIL_FAST_IF(commands > SHORT_MAX);
gci.SetHistoryBufferSize(gsl::narrow<UINT>(commands));
for (auto& historyList : s_historyLists)
for (auto historyList : s_historyLists)
{
historyList.Realloc(commands);
historyList->Realloc(commands);
}
}
@ -297,10 +297,10 @@ void CommandHistory::s_ReallocExeToFront(const std::wstring_view appName, const
{
for (auto it = s_historyLists.begin(); it != s_historyLists.end(); it++)
{
if (WI_IsFlagSet(it->Flags, CLE_ALLOCATED) && it->IsAppNameMatch(appName))
if (WI_IsFlagSet((*it)->Flags, CLE_ALLOCATED) && (*it)->IsAppNameMatch(appName))
{
CommandHistory backup = *it;
backup.Realloc(commands);
std::shared_ptr<CommandHistory> backup = *it;
backup->Realloc(commands);
s_historyLists.erase(it);
s_historyLists.push_front(backup);
@ -310,13 +310,13 @@ void CommandHistory::s_ReallocExeToFront(const std::wstring_view appName, const
}
}
CommandHistory* CommandHistory::s_FindByExe(const std::wstring_view appName)
std::shared_ptr<CommandHistory> CommandHistory::s_FindByExe(const std::wstring_view appName)
{
for (auto& historyList : s_historyLists)
for (auto historyList : s_historyLists)
{
if (WI_IsFlagSet(historyList.Flags, CLE_ALLOCATED) && historyList.IsAppNameMatch(appName))
if (WI_IsFlagSet(historyList->Flags, CLE_ALLOCATED) && historyList->IsAppNameMatch(appName))
{
return &historyList;
return historyList;
}
}
return nullptr;
@ -333,20 +333,20 @@ size_t CommandHistory::s_CountOfHistories()
// - Console - pointer to console.
// Return Value:
// - Pointer to command history buffer. if none are available, returns nullptr.
CommandHistory* CommandHistory::s_Allocate(const std::wstring_view appName, const HANDLE processHandle)
std::shared_ptr<CommandHistory> CommandHistory::s_Allocate(const std::wstring_view appName, const HANDLE processHandle)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// Reuse a history buffer. The buffer must be !CLE_ALLOCATED.
// If possible, the buffer should have the same app name.
std::optional<CommandHistory> BestCandidate;
std::shared_ptr<CommandHistory> BestCandidate = nullptr;
bool SameApp = false;
for (auto it = s_historyLists.cbegin(); it != s_historyLists.cend(); it++)
{
if (WI_IsFlagClear(it->Flags, CLE_ALLOCATED))
if (WI_IsFlagClear((*it)->Flags, CLE_ALLOCATED))
{
// use LRU history buffer with same app name
if (it->IsAppNameMatch(appName))
if ((*it)->IsAppNameMatch(appName))
{
BestCandidate = *it;
SameApp = true;
@ -360,21 +360,21 @@ CommandHistory* CommandHistory::s_Allocate(const std::wstring_view appName, cons
// command history buffers hasn't been allocated, allocate a new one.
if (!SameApp && s_historyLists.size() < gci.GetNumberOfHistoryBuffers())
{
CommandHistory History;
std::shared_ptr<CommandHistory> History = std::make_shared<CommandHistory>();
History._appName = appName;
History.Flags = CLE_ALLOCATED;
History.LastDisplayed = -1;
History._maxCommands = gsl::narrow<SHORT>(gci.GetHistoryBufferSize());
History._processHandle = processHandle;
return &s_historyLists.emplace_front(History);
History->_appName = appName;
History->Flags = CLE_ALLOCATED;
History->LastDisplayed = -1;
History->_maxCommands = gsl::narrow<SHORT>(gci.GetHistoryBufferSize());
History->_processHandle = processHandle;
return s_historyLists.emplace_front(History);
}
else if (!BestCandidate.has_value() && s_historyLists.size() > 0)
else if (BestCandidate == nullptr && s_historyLists.size() > 0)
{
// If we have no candidate already and we need one, take the LRU (which is the back/last one) which isn't allocated.
for (auto it = s_historyLists.crbegin(); it != s_historyLists.crend(); it++)
{
if (WI_IsFlagClear(it->Flags, CLE_ALLOCATED))
if (WI_IsFlagClear((*it)->Flags, CLE_ALLOCATED))
{
BestCandidate = *it;
s_historyLists.erase(std::next(it).base()); // trickery to turn reverse iterator into forward iterator for erase.
@ -385,7 +385,7 @@ CommandHistory* CommandHistory::s_Allocate(const std::wstring_view appName, cons
}
// If the app name doesn't match, copy in the new app name and free the old commands.
if (BestCandidate.has_value())
if (BestCandidate)
{
if (!SameApp)
{
@ -397,7 +397,7 @@ CommandHistory* CommandHistory::s_Allocate(const std::wstring_view appName, cons
BestCandidate->_processHandle = processHandle;
WI_SetFlag(BestCandidate->Flags, CLE_ALLOCATED);
return &s_historyLists.emplace_front(BestCandidate.value());
return s_historyLists.emplace_front(BestCandidate);
}
return nullptr;
@ -678,7 +678,7 @@ HRESULT GetConsoleCommandHistoryLengthImplHelper(const std::wstring_view exeName
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
CommandHistory* const pCommandHistory = CommandHistory::s_FindByExe(exeName);
const std::shared_ptr<CommandHistory> pCommandHistory = CommandHistory::s_FindByExe(exeName);
if (nullptr != pCommandHistory)
{
size_t cchNeeded = 0;
@ -783,7 +783,7 @@ HRESULT GetConsoleCommandHistoryWImplHelper(const std::wstring_view exeName,
historyBuffer.at(0) = UNICODE_NULL;
}
CommandHistory* const CommandHistory = CommandHistory::s_FindByExe(exeName);
const std::shared_ptr<CommandHistory> CommandHistory = CommandHistory::s_FindByExe(exeName);
if (nullptr != CommandHistory)
{

View file

@ -19,9 +19,9 @@ Abstract:
class CommandHistory
{
public:
static CommandHistory* s_Allocate(const std::wstring_view appName, const HANDLE processHandle);
static CommandHistory* s_Find(const HANDLE processHandle);
static CommandHistory* s_FindByExe(const std::wstring_view appName);
static std::shared_ptr<CommandHistory> s_Allocate(const std::wstring_view appName, const HANDLE processHandle);
static std::shared_ptr<CommandHistory> s_Find(const HANDLE processHandle);
static std::shared_ptr<CommandHistory> s_FindByExe(const std::wstring_view appName);
static void s_ReallocExeToFront(const std::wstring_view appName, const size_t commands);
static void s_Free(const HANDLE processHandle);
static void s_ResizeAll(const size_t commands);
@ -93,7 +93,7 @@ private:
std::wstring _appName;
HANDLE _processHandle;
static std::list<CommandHistory> s_historyLists;
static std::list<std::shared_ptr<CommandHistory>> s_historyLists;
public:
DWORD Flags;

View file

@ -10,7 +10,7 @@
<ClCompile Include="..\conattrs.cpp" />
<ClCompile Include="..\ConsoleArguments.cpp" />
<ClCompile Include="..\CursorBlinker.cpp" />
<ClCompile Include="..\readDataCooked.cpp" />
<ClCompile Include="..\cookedRead.cpp" />
<ClCompile Include="..\conareainfo.cpp" />
<ClCompile Include="..\conimeinfo.cpp" />
<ClCompile Include="..\consoleInformation.cpp" />
@ -95,7 +95,7 @@
<ClInclude Include="..\popup.h" />
<ClInclude Include="..\precomp.h" />
<ClInclude Include="..\readData.hpp" />
<ClInclude Include="..\readDataCooked.hpp" />
<ClInclude Include="..\cookedRead.hpp" />
<ClInclude Include="..\rectreadDataDirect.hpp" />
<ClInclude Include="..\readDataRaw.hpp" />
<ClInclude Include="..\registry.hpp" />

View file

@ -333,7 +333,7 @@ void Popup::SetUserInputFunction(UserInputFunction function) noexcept
// - wch - on completion, the char read from the user
// Return Value:
// - relevant NTSTATUS
NTSTATUS Popup::_getUserInput(COOKED_READ_DATA& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept
NTSTATUS Popup::_getUserInput(CookedRead& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept
{
return _userInputFunction(cookedReadData, popupKey, modifiers, wch);
}
@ -346,7 +346,7 @@ NTSTATUS Popup::_getUserInput(COOKED_READ_DATA& cookedReadData, bool& popupKey,
// - wch - on completion, the char read from the user
// Return Value:
// - relevant NTSTATUS
NTSTATUS Popup::_getUserInputInternal(COOKED_READ_DATA& cookedReadData,
NTSTATUS Popup::_getUserInputInternal(CookedRead& cookedReadData,
bool& popupKey,
DWORD& modifiers,
wchar_t& wch) noexcept
@ -360,7 +360,7 @@ NTSTATUS Popup::_getUserInputInternal(COOKED_READ_DATA& cookedReadData,
&modifiers);
if (!NT_SUCCESS(Status) && Status != CONSOLE_STATUS_WAIT)
{
cookedReadData.BytesRead() = 0;
cookedReadData.Erase();
}
return Status;
}

View file

@ -20,7 +20,7 @@ Revision History:
#pragma once
#include "readDataCooked.hpp"
#include "cookedRead.hpp"
#include "screenInfo.hpp"
#include "readDataCooked.hpp"
@ -34,12 +34,12 @@ class Popup
{
public:
using UserInputFunction = std::function<NTSTATUS(COOKED_READ_DATA&, bool&, DWORD&, wchar_t&)>;
using UserInputFunction = std::function<NTSTATUS(CookedRead&, bool&, DWORD&, wchar_t&)>;
Popup(SCREEN_INFORMATION& screenInfo, const COORD proposedSize);
virtual ~Popup();
[[nodiscard]]
virtual NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept = 0;
virtual NTSTATUS Process(CookedRead& cookedReadData) noexcept = 0;
void Draw();
@ -66,7 +66,7 @@ protected:
friend class CommandListPopupTests;
#endif
NTSTATUS _getUserInput(COOKED_READ_DATA& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept;
NTSTATUS _getUserInput(CookedRead& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept;
void _DrawPrompt(const UINT id);
virtual void _DrawContent() = 0;
@ -81,7 +81,7 @@ private:
void _DrawBorder();
static NTSTATUS _getUserInputInternal(COOKED_READ_DATA& cookedReadData,
static NTSTATUS _getUserInputInternal(CookedRead& cookedReadData,
bool& popupKey,
DWORD& modifiers,
wchar_t& wch) noexcept;

View file

@ -4,6 +4,7 @@
#include "precomp.h"
#include "search.h"
#include "cookedRead.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/convert.hpp"
@ -954,8 +955,8 @@ bool Selection::s_GetInputLineBoundaries(_Out_opt_ COORD* const pcoordInputStart
}
const auto& cookedRead = gci.CookedReadData();
const COORD coordStart = cookedRead.OriginalCursorPosition();
COORD coordEnd = cookedRead.OriginalCursorPosition();
const COORD coordStart = cookedRead.PromptStartLocation();
COORD coordEnd = coordStart;
if (coordEnd.X < 0 && coordEnd.Y < 0)
{

View file

@ -69,7 +69,7 @@ Revision History:
#define CONSOLE_SUSPENDED (CONSOLE_OUTPUT_SUSPENDED)
class COOKED_READ_DATA;
class CookedRead;
class CommandHistory;
class CONSOLE_INFORMATION :
@ -125,9 +125,9 @@ public:
bool IsInVtIoMode() const;
bool HasPendingCookedRead() const noexcept;
const COOKED_READ_DATA& CookedReadData() const noexcept;
COOKED_READ_DATA& CookedReadData() noexcept;
void SetCookedReadData(COOKED_READ_DATA* readData) noexcept;
const CookedRead& CookedReadData() const noexcept;
CookedRead& CookedReadData() noexcept;
void SetCookedReadData(CookedRead* readData) noexcept;
COLORREF GetDefaultForeground() const noexcept;
COLORREF GetDefaultBackground() const noexcept;
@ -160,7 +160,7 @@ private:
std::wstring _OriginalTitle;
std::wstring _LinkTitle; // Path to .lnk file
SCREEN_INFORMATION* pCurrentScreenBuffer;
COOKED_READ_DATA* _cookedReadData; // non-ownership pointer
CookedRead* _cookedReadData; // non-ownership pointer
Microsoft::Console::VirtualTerminal::VtIo _vtIo;
Microsoft::Console::CursorBlinker _blinker;

View file

@ -17,6 +17,8 @@
#include "..\interactivity\inc\ServiceLocator.hpp"
#include "cookedRead.hpp"
#pragma hdrstop
// Routine Description:
@ -445,7 +447,7 @@ static NTSTATUS _ReadPendingInput(InputBuffer& inputBuffer,
// - bytesRead - on output, the number of bytes read into pwchBuffer
// - controlKeyState - set by a cooked read
// - initialData - text of initial data found in the read message
// - ctrlWakeupMask - used by COOKED_READ_DATA
// - ctrlWakeupMask - used by COOKED_READ_DATA to trigger an early return of reading if masked key is pressed
// - readHandleState - input read handle data associated with this read operation
// - exeName - name of the exe requesting the read
// - unicode - true if read should be unicode, false otherwise
@ -461,7 +463,7 @@ static HRESULT _ReadLineInput(InputBuffer& inputBuffer,
gsl::span<char> buffer,
size_t& bytesRead,
DWORD& controlKeyState,
const std::string_view initialData,
const std::string_view /*initialData*/,
const DWORD ctrlWakeupMask,
INPUT_READ_HANDLE_DATA& readHandleState,
const std::wstring_view exeName,
@ -472,22 +474,22 @@ static HRESULT _ReadLineInput(InputBuffer& inputBuffer,
RETURN_HR_IF(E_FAIL, !gci.HasActiveOutputBuffer());
SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();
CommandHistory* const pCommandHistory = CommandHistory::s_Find(processData);
const std::shared_ptr<CommandHistory> pCommandHistory = CommandHistory::s_Find(processData);
try
{
auto cookedReadData = std::make_unique<COOKED_READ_DATA>(&inputBuffer, // pInputBuffer
&readHandleState, // pInputReadHandleData
screenInfo, // pScreenInfo
buffer.size_bytes(), // UserBufferSize
reinterpret_cast<wchar_t*>(buffer.data()), // UserBuffer
ctrlWakeupMask, // CtrlWakeupMask
pCommandHistory, // CommandHistory
exeName, // exe name
initialData);
// TODO hook up other arguments to CookedRead
auto cookedReadData = std::make_unique<CookedRead>(&inputBuffer,
&readHandleState,
screenInfo,
pCommandHistory,
reinterpret_cast<wchar_t*>(buffer.data()),
buffer.size_bytes() / sizeof(wchar_t),
ctrlWakeupMask,
exeName);
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.
// bytesRead on the way in is the size to read, on the way out, it will be updated to what is actually read.
bytesRead = buffer.size_bytes();
if (CONSOLE_STATUS_WAIT == cookedReadData->Read(unicode, bytesRead, controlKeyState))
{
// memory will be cleaned up by wait queue
@ -497,6 +499,7 @@ static HRESULT _ReadLineInput(InputBuffer& inputBuffer,
{
gci.SetCookedReadData(nullptr);
}
}
CATCH_RETURN();

View file

@ -21,7 +21,7 @@ constexpr size_t PROMPT_SIZE = 512;
class CommandLineTests
{
std::unique_ptr<CommonState> m_state;
CommandHistory* m_pHistory;
std::shared_ptr<CommandHistory> m_pHistory;
TEST_CLASS(CommandLineTests);
@ -49,6 +49,7 @@ class CommandLineTests
{
return false;
}
VERIFY_ARE_EQUAL(m_pHistory->GetNumberOfCommands(), 0u);
return true;
}
@ -56,6 +57,7 @@ class CommandLineTests
{
CommandHistory::s_Free((HANDLE)0);
m_pHistory = nullptr;
CommandHistory::s_ClearHistoryListStorage();
m_state->CleanupCookedReadData();
m_state->CleanupReadHandle();
m_state->CleanupGlobalInputBuffer();
@ -63,53 +65,37 @@ class CommandLineTests
return true;
}
void VerifyPromptText(COOKED_READ_DATA& cookedReadData, const std::wstring wstr)
void VerifyPromptText(CookedRead& cookedReadData, const std::wstring wstr)
{
const auto span = cookedReadData.SpanWholeBuffer();
VERIFY_ARE_EQUAL(cookedReadData._bytesRead, wstr.size() * sizeof(wchar_t));
for (size_t i = 0; i < wstr.size(); ++i)
{
VERIFY_ARE_EQUAL(span.at(i), wstr.at(i));
}
const auto prompt = cookedReadData.Prompt();
VERIFY_ARE_EQUAL(prompt, wstr);
}
void InitCookedReadData(COOKED_READ_DATA& cookedReadData,
CommandHistory* pHistory,
wchar_t* pBuffer,
const size_t cchBuffer)
void InitCookedReadData(CookedRead& cookedReadData,
std::shared_ptr<CommandHistory> pHistory,
const std::wstring prompt)
{
cookedReadData._commandHistory = pHistory;
cookedReadData._userBuffer = 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 };
cookedReadData._pCommandHistory = pHistory;
cookedReadData._prompt = prompt;
cookedReadData._promptStartLocation = { 0, 0 };
cookedReadData._insertionIndex = 0;
}
void SetPrompt(COOKED_READ_DATA& cookedReadData, const std::wstring text)
void SetPrompt(CookedRead& 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();
cookedReadData._prompt = text;
cookedReadData._insertionIndex = cookedReadData._prompt.size();
}
void MoveCursor(COOKED_READ_DATA& cookedReadData, const size_t column)
void MoveCursor(CookedRead& cookedReadData, const size_t column)
{
cookedReadData._currentPosition = column;
cookedReadData._bufPtr = cookedReadData._backupLimit + column;
cookedReadData._insertionIndex = column;
}
TEST_METHOD(CanCycleCommandHistory)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, m_pHistory, L"");
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false));
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false));
@ -118,7 +104,7 @@ class CommandLineTests
auto& commandLine = CommandLine::Instance();
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next);
// should not have anything on the prompt
VERIFY_ARE_EQUAL(cookedReadData._bytesRead, 0u);
VERIFY_IS_TRUE(cookedReadData._prompt.empty());
// go back one history item
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous);
@ -152,11 +138,8 @@ class CommandLineTests
TEST_METHOD(CanSetPromptToOldestHistory)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, m_pHistory, L"");
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false));
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false));
@ -174,11 +157,8 @@ class CommandLineTests
TEST_METHOD(CanSetPromptToNewestHistory)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, m_pHistory, L"");
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false));
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false));
@ -196,11 +176,8 @@ class CommandLineTests
TEST_METHOD(CanDeletePromptAfterCursor)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, nullptr, L"");
auto expected = L"test word blah";
SetPrompt(cookedReadData, expected);
@ -215,11 +192,8 @@ class CommandLineTests
TEST_METHOD(CanDeletePromptBeforeCursor)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, nullptr, L"");
auto expected = L"test word blah";
SetPrompt(cookedReadData, expected);
@ -229,111 +203,95 @@ class CommandLineTests
MoveCursor(cookedReadData, 5);
auto& commandLine = CommandLine::Instance();
const COORD cursorPos = commandLine._deletePromptBeforeCursor(cookedReadData);
cookedReadData._currentPosition = cursorPos.X;
//cookedReadData._currentPosition = cursorPos.X;
VerifyPromptText(cookedReadData, L"word blah");
}
TEST_METHOD(CanMoveCursorToEndOfPrompt)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, nullptr, L"");
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);
VERIFY_ARE_NOT_EQUAL(cookedReadData._insertionIndex, 0u);
// save current position for later checking
const auto expectedCursorPos = cookedReadData._currentPosition;
const auto expectedBufferPos = cookedReadData._bufPtr;
const auto expectedCursorPos = cookedReadData._insertionIndex;
MoveCursor(cookedReadData, 0);
auto& commandLine = CommandLine::Instance();
const COORD cursorPos = commandLine._moveCursorToEndOfPrompt(cookedReadData);
VERIFY_ARE_EQUAL(cursorPos.X, gsl::narrow<short>(expectedCursorPos));
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, expectedCursorPos);
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, expectedBufferPos);
VERIFY_ARE_EQUAL(cookedReadData._insertionIndex, expectedCursorPos);
}
TEST_METHOD(CanMoveCursorToStartOfPrompt)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, nullptr, L"");
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);
VERIFY_ARE_NOT_EQUAL(cookedReadData._insertionIndex, 0u);
VERIFY_IS_FALSE(cookedReadData._prompt.empty());
auto& commandLine = CommandLine::Instance();
const COORD cursorPos = commandLine._moveCursorToStartOfPrompt(cookedReadData);
VERIFY_ARE_EQUAL(cursorPos.X, 0);
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u);
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
VERIFY_ARE_EQUAL(cookedReadData._insertionIndex, 0u);
}
TEST_METHOD(CanMoveCursorLeftByWord)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, nullptr, L"");
auto expected = L"test word blah";
const std::wstring expected = L"test word blah";
SetPrompt(cookedReadData, expected);
VerifyPromptText(cookedReadData, expected);
auto& commandLine = CommandLine::Instance();
cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().SetPosition({ gsl::narrow<short>(expected.size()), 0 });
// cursor position at beginning of "blah"
short expectedPos = 10;
COORD cursorPos = commandLine._moveCursorLeftByWord(cookedReadData);
VERIFY_ARE_EQUAL(cursorPos.X, expectedPos);
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow<size_t>(expectedPos));
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos);
VERIFY_ARE_EQUAL(cookedReadData._insertionIndex, gsl::narrow<size_t>(expectedPos));
// move again
cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().SetPosition({ expectedPos, 0 });
expectedPos = 5; // before "word"
cursorPos = commandLine._moveCursorLeftByWord(cookedReadData);
VERIFY_ARE_EQUAL(cursorPos.X, expectedPos);
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow<size_t>(expectedPos));
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos);
VERIFY_ARE_EQUAL(cookedReadData._insertionIndex, gsl::narrow<size_t>(expectedPos));
// move again
cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().SetPosition({ expectedPos, 0 });
expectedPos = 0; // before "test"
cursorPos = commandLine._moveCursorLeftByWord(cookedReadData);
VERIFY_ARE_EQUAL(cursorPos.X, expectedPos);
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow<size_t>(expectedPos));
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos);
VERIFY_ARE_EQUAL(cookedReadData._insertionIndex, gsl::narrow<size_t>(expectedPos));
// try to move again, nothing should happen
cookedReadData.ScreenInfo().GetTextBuffer().GetCursor().SetPosition({ expectedPos, 0 });
cursorPos = commandLine._moveCursorLeftByWord(cookedReadData);
VERIFY_ARE_EQUAL(cursorPos.X, expectedPos);
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow<size_t>(expectedPos));
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos);
VERIFY_ARE_EQUAL(cookedReadData._insertionIndex, gsl::narrow<size_t>(expectedPos));
}
TEST_METHOD(CanMoveCursorLeft)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, nullptr, L"");
const std::wstring expected = L"test word blah";
SetPrompt(cookedReadData, expected);
@ -344,28 +302,23 @@ class CommandLineTests
for (auto it = expected.crbegin(); it != expected.crend(); ++it)
{
const COORD cursorPos = commandLine._moveCursorLeft(cookedReadData);
VERIFY_ARE_EQUAL(*cookedReadData._bufPtr, *it);
VERIFY_ARE_EQUAL(cookedReadData._prompt.at(cookedReadData._insertionIndex), *it);
}
// should now be at the start of the prompt
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u);
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
VERIFY_ARE_EQUAL(cookedReadData._insertionIndex, 0u);
// try to move left a final time, nothing should change
const COORD cursorPos = commandLine._moveCursorLeft(cookedReadData);
VERIFY_ARE_EQUAL(cursorPos.X, 0);
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u);
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
VERIFY_ARE_EQUAL(cookedReadData._insertionIndex, 0u);
}
/*
// TODO MSFT:11285829 tcome back and turn these on once the system cursor isn't needed
TEST_METHOD(CanMoveCursorRightByWord)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, nullptr, L"");
auto expected = L"test word blah";
SetPrompt(cookedReadData, expected);
@ -398,11 +351,8 @@ class CommandLineTests
TEST_METHOD(CanInsertCtrlZ)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, nullptr, L"");
auto& commandLine = CommandLine::Instance();
commandLine._insertCtrlZ(cookedReadData);
@ -411,11 +361,8 @@ class CommandLineTests
TEST_METHOD(CanDeleteCommandHistory)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, m_pHistory, L"");
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false));
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false));
@ -428,11 +375,8 @@ class CommandLineTests
TEST_METHOD(CanFillPromptWithPreviousCommandFragment)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, m_pHistory, L"");
VERIFY_SUCCEEDED(m_pHistory->Add(L"I'm a little teapot", false));
SetPrompt(cookedReadData, L"short and stout");
@ -444,11 +388,8 @@ class CommandLineTests
TEST_METHOD(CanCycleMatchingCommandHistory)
{
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
VERIFY_IS_NOT_NULL(buffer.get());
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
InitCookedReadData(cookedReadData, m_pHistory, L"");
VERIFY_SUCCEEDED(m_pHistory->Add(L"I'm a little teapot", false));
VERIFY_SUCCEEDED(m_pHistory->Add(L"short and stout", false));
@ -472,12 +413,10 @@ class CommandLineTests
TEST_METHOD(CmdlineCtrlHomeFullwidthChars)
{
Log::Comment(L"Set up buffers, create cooked read data, get screen information.");
auto buffer = std::make_unique<wchar_t[]>(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);
InitCookedReadData(cookedReadData, m_pHistory, L"");
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
@ -490,8 +429,13 @@ class CommandLineTests
}
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);
for (const auto& wch : text)
{
cookedReadData.BufferInput(wch);
}
size_t numBytes = 0;
ULONG ctrlKeyState = 0;
VERIFY_ARE_EQUAL(cookedReadData.Read(true, numBytes, ctrlKeyState), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT));
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();

View file

@ -25,7 +25,7 @@ class CommandListPopupTests
TEST_CLASS(CommandListPopupTests);
std::unique_ptr<CommonState> m_state;
CommandHistory* m_pHistory;
std::shared_ptr<CommandHistory> m_pHistory;
TEST_CLASS_SETUP(ClassSetup)
{
@ -72,24 +72,10 @@ class CommandListPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
modifiers = 0;
@ -105,19 +91,14 @@ class CommandListPopupTests
// 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;
PopupTestHelper::InitReadData(cookedReadData, testString);
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(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));
// the prompt should not be changed
VERIFY_ARE_EQUAL(testString, cookedReadData._prompt);
// popup has been dismissed
VERIFY_IS_FALSE(CommandLine::Instance().HasPopup());
@ -126,7 +107,7 @@ class CommandListPopupTests
TEST_METHOD(UpMovesSelection)
{
// function to simulate user pressing up arrow
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static bool firstTime = true;
if (firstTime)
@ -150,11 +131,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
const short commandNumberBefore = popup._currentCommand;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
@ -165,7 +144,7 @@ class CommandListPopupTests
TEST_METHOD(DownMovesSelection)
{
// function to simulate user pressing down arrow
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static bool firstTime = true;
if (firstTime)
@ -191,11 +170,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
const short commandNumberBefore = popup._currentCommand;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
@ -206,7 +183,7 @@ class CommandListPopupTests
TEST_METHOD(EndMovesSelectionToEnd)
{
// function to simulate user pressing end key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static bool firstTime = true;
if (firstTime)
@ -232,11 +209,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// selection should have moved to the bottom line
@ -246,7 +221,7 @@ class CommandListPopupTests
TEST_METHOD(HomeMovesSelectionToStart)
{
// function to simulate user pressing home key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static bool firstTime = true;
if (firstTime)
@ -270,11 +245,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// selection should have moved to the bottom line
@ -284,7 +257,7 @@ class CommandListPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static bool firstTime = true;
if (firstTime)
@ -308,11 +281,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// selection should have moved up a page
@ -322,7 +293,7 @@ class CommandListPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static bool firstTime = true;
if (firstTime)
@ -348,11 +319,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// selection should have moved up a page
@ -362,7 +331,7 @@ class CommandListPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
wch = VK_RIGHT;
popupKey = true;
@ -379,23 +348,21 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// prompt should have history item in prompt
const std::wstring_view historyItem = m_pHistory->GetLastCommand();
const std::wstring_view resultText{ buffer, historyItem.size() };
const std::wstring_view resultText{ cookedReadData._prompt.c_str(), cookedReadData._prompt.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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
wch = VK_F9;
popupKey = true;
@ -410,11 +377,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
auto& commandLine = CommandLine::Instance();
VERIFY_IS_FALSE(commandLine.HasPopup());
@ -428,7 +393,7 @@ class CommandListPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static bool firstTime = true;
if (firstTime)
@ -452,11 +417,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
const size_t startHistorySize = m_pHistory->GetNumberOfCommands();
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
@ -466,7 +429,7 @@ class CommandListPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static bool firstTime = true;
if (firstTime)
@ -491,11 +454,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my spout");
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
@ -506,7 +467,7 @@ class CommandListPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static unsigned int count = 0;
if (count == 0)
@ -536,11 +497,9 @@ class CommandListPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my spout");
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));

View file

@ -25,7 +25,7 @@ class CommandNumberPopupTests
TEST_CLASS(CommandNumberPopupTests);
std::unique_ptr<CommonState> m_state;
CommandHistory* m_pHistory;
std::shared_ptr<CommandHistory> m_pHistory;
TEST_CLASS_SETUP(ClassSetup)
{
@ -58,6 +58,7 @@ class CommandNumberPopupTests
{
CommandHistory::s_Free((HANDLE)0);
m_pHistory = nullptr;
CommandHistory::s_ClearHistoryListStorage();
m_state->CleanupCookedReadData();
m_state->CleanupReadHandle();
m_state->CleanupGlobalInputBuffer();
@ -68,7 +69,7 @@ class CommandNumberPopupTests
TEST_METHOD(CanDismiss)
{
// function to simulate user pressing escape key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
wch = VK_ESCAPE;
@ -83,20 +84,15 @@ class CommandNumberPopupTests
// 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::InitReadData(cookedReadData, testString);
PopupTestHelper::InitHistory(*m_pHistory);
cookedReadData._commandHistory = m_pHistory;
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(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));
VERIFY_ARE_EQUAL(testString, cookedReadData._prompt);
// popup has been dismissed
VERIFY_IS_FALSE(CommandLine::Instance().HasPopup());
@ -108,7 +104,7 @@ class CommandNumberPopupTests
// CommanNumberPopup 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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
wch = VK_ESCAPE;
@ -117,6 +113,13 @@ class CommandNumberPopupTests
};
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// prepare cookedReadData
const std::wstring testString = L"hello world";
auto& cookedReadData = gci.CookedReadData();
PopupTestHelper::InitReadData(cookedReadData, testString);
PopupTestHelper::InitHistory(*m_pHistory);
cookedReadData._pCommandHistory = m_pHistory;
// add popups to CommandLine
auto& commandLine = CommandLine::Instance();
commandLine._popups.emplace_front(std::make_unique<CommandListPopup>(gci.GetActiveOutputBuffer(), *m_pHistory));
@ -125,17 +128,6 @@ class CommandNumberPopupTests
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<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
VERIFY_IS_FALSE(commandLine.HasPopup());
@ -144,7 +136,7 @@ class CommandNumberPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = false;
wch = UNICODE_CARRIAGERETURN;
@ -158,20 +150,17 @@ class CommandNumberPopupTests
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::InitReadData(cookedReadData, L"");
PopupTestHelper::InitHistory(*m_pHistory);
cookedReadData._commandHistory = m_pHistory;
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// the buffer should contain the least recent history item
const std::wstring_view expected = m_pHistory->GetLastCommand();
const std::wstring resultString(buffer, buffer + expected.size());
VERIFY_ARE_EQUAL(expected, resultString);
VERIFY_ARE_EQUAL(expected, cookedReadData._prompt);
}
TEST_METHOD(CanSelectHistoryItem)
@ -179,7 +168,7 @@ class CommandNumberPopupTests
PopupTestHelper::InitHistory(*m_pHistory);
for (unsigned int historyIndex = 0; historyIndex < m_pHistory->GetNumberOfCommands(); ++historyIndex)
{
Popup::UserInputFunction fn = [historyIndex](COOKED_READ_DATA& /*cookedReadData*/,
Popup::UserInputFunction fn = [historyIndex](CookedRead& /*cookedReadData*/,
bool& popupKey,
DWORD& modifiers,
wchar_t& wch)
@ -207,19 +196,16 @@ class CommandNumberPopupTests
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;
PopupTestHelper::InitReadData(cookedReadData, L"");
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// the buffer should contain the correct nth history item
const auto expected = m_pHistory->GetNth(gsl::narrow<short>(historyIndex));
const std::wstring resultString(buffer, buffer + expected.size());
VERIFY_ARE_EQUAL(expected, resultString);
VERIFY_ARE_EQUAL(expected, cookedReadData._prompt);
}
}
@ -228,7 +214,7 @@ class CommandNumberPopupTests
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)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
static int num = 1;
popupKey = false;
@ -252,20 +238,17 @@ class CommandNumberPopupTests
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::InitReadData(cookedReadData, L"");
PopupTestHelper::InitHistory(*m_pHistory);
cookedReadData._commandHistory = m_pHistory;
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// the buffer should contain the most recent history item
const std::wstring_view expected = m_pHistory->GetLastCommand();
const std::wstring resultString(buffer, buffer + expected.size());
VERIFY_ARE_EQUAL(expected, resultString);
VERIFY_ARE_EQUAL(expected, cookedReadData._prompt);
}
TEST_METHOD(InputIsLimited)

View file

@ -59,7 +59,7 @@ class CopyFromCharPopupTests
TEST_METHOD(CanDismiss)
{
// function to simulate user pressing escape key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
wch = VK_ESCAPE;
@ -74,18 +74,13 @@ class CopyFromCharPopupTests
// 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());
PopupTestHelper::InitReadData(cookedReadData, testString);
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(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));
VERIFY_ARE_EQUAL(testString, cookedReadData._prompt);
// popup has been dismissed
VERIFY_IS_FALSE(CommandLine::Instance().HasPopup());
@ -93,7 +88,7 @@ class CopyFromCharPopupTests
TEST_METHOD(DeleteAllWhenCharNotFound)
{
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = false;
wch = L'x';
@ -108,23 +103,20 @@ class CopyFromCharPopupTests
// 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());
PopupTestHelper::InitReadData(cookedReadData, testString);
// move cursor to beginning of prompt text
cookedReadData.InsertionPoint() = 0;
cookedReadData.MoveInsertionIndexToStart();
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// all text to the right of the cursor should be gone
VERIFY_ARE_EQUAL(cookedReadData.BytesRead(), 0u);
VERIFY_ARE_EQUAL(cookedReadData._prompt.size(), 0u);
}
TEST_METHOD(CanDeletePartialLine)
{
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = false;
wch = L'f';
@ -139,21 +131,15 @@ class CopyFromCharPopupTests
// 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());
PopupTestHelper::InitReadData(cookedReadData, testString);
// move cursor to index 12
const size_t index = 12;
cookedReadData.SetBufferCurrentPtr(buffer + index);
cookedReadData.InsertionPoint() = index;
cookedReadData._insertionIndex = index;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(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);
VERIFY_ARE_EQUAL(expectedText, cookedReadData._prompt);
}
};

View file

@ -24,7 +24,7 @@ class CopyToCharPopupTests
TEST_CLASS(CopyToCharPopupTests);
std::unique_ptr<CommonState> m_state;
CommandHistory* m_pHistory;
std::shared_ptr<CommandHistory> m_pHistory;
TEST_CLASS_SETUP(ClassSetup)
{
@ -57,6 +57,7 @@ class CopyToCharPopupTests
{
CommandHistory::s_Free((HANDLE)0);
m_pHistory = nullptr;
CommandHistory::s_ClearHistoryListStorage();
m_state->CleanupCookedReadData();
m_state->CleanupReadHandle();
m_state->CleanupGlobalInputBuffer();
@ -67,7 +68,7 @@ class CopyToCharPopupTests
TEST_METHOD(CanDismiss)
{
// function to simulate user pressing escape key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
wch = VK_ESCAPE;
@ -82,20 +83,15 @@ class CopyToCharPopupTests
// 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::InitReadData(cookedReadData, testString);
PopupTestHelper::InitHistory(*m_pHistory);
cookedReadData._commandHistory = m_pHistory;
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(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));
VERIFY_ARE_EQUAL(testString, cookedReadData._prompt);
// popup has been dismissed
VERIFY_IS_FALSE(CommandLine::Instance().HasPopup());
@ -104,7 +100,7 @@ class CopyToCharPopupTests
TEST_METHOD(NothingHappensWhenCharNotFound)
{
// function to simulate user pressing escape key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
wch = L'x';
@ -118,24 +114,20 @@ class CopyToCharPopupTests
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;
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
// the buffer should not be changed
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
VERIFY_ARE_EQUAL(cookedReadData._bytesRead, 0u);
VERIFY_IS_TRUE(cookedReadData._prompt.empty());
}
TEST_METHOD(CanCopyToEmptyPrompt)
{
// function to simulate user pressing escape key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
wch = L's';
@ -149,31 +141,21 @@ class CopyToCharPopupTests
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;
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(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);
VERIFY_ARE_EQUAL(expectedText, cookedReadData._prompt);
}
TEST_METHOD(WontCopyTextBeforeCursor)
{
// function to simulate user pressing escape key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
wch = L's';
@ -187,37 +169,27 @@ class CopyToCharPopupTests
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));
const std::wstring testString = L"Whose woods these are I think I know.";
auto& cookedReadData = gci.CookedReadData();
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
PopupTestHelper::InitReadData(cookedReadData, testString);
PopupTestHelper::InitHistory(*m_pHistory);
cookedReadData._commandHistory = m_pHistory;
cookedReadData._pCommandHistory = m_pHistory;
const wchar_t* const expectedBufPtr = cookedReadData._bufPtr;
const size_t expectedBytesRead = cookedReadData._bytesRead;
const auto beforeText = cookedReadData._prompt;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(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);
VERIFY_ARE_EQUAL(beforeText, cookedReadData._prompt);
}
TEST_METHOD(CanMergeLine)
{
// function to simulate user pressing escape key
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
Popup::UserInputFunction fn = [](CookedRead& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch)
{
popupKey = true;
wch = L's';
wch = L' ';
modifiers = 0;
return STATUS_SUCCESS;
};
@ -229,21 +201,15 @@ class CopyToCharPopupTests
// 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::InitReadData(cookedReadData, testString);
PopupTestHelper::InitHistory(*m_pHistory);
cookedReadData._commandHistory = m_pHistory;
cookedReadData._pCommandHistory = m_pHistory;
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(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);
VERIFY_ARE_EQUAL(expectedText, cookedReadData._prompt);
}
};

View file

@ -7,12 +7,12 @@
<ClCompile Include="AttrRowTests.cpp" />
<ClCompile Include="ClipboardTests.cpp" />
<ClCompile Include="ConsoleArgumentsTests.cpp" />
<ClCompile Include="CommandLineTests.cpp" />
<ClCompile Include="CodepointWidthDetectorTests.cpp" />
<ClCompile Include="CommandListPopupTests.cpp" />
<ClCompile Include="CommandNumberPopupTests.cpp" />
<ClCompile Include="CopyFromCharPopupTests.cpp" />
<ClCompile Include="CopyToCharPopupTests.cpp" />
<ClCompile Include="CommandLineTests.cpp" />
<ClCompile Include="DbcsTests.cpp" />
<ClCompile Include="HistoryTests.cpp" />
<ClCompile Include="InitTests.cpp" />
@ -105,4 +105,4 @@
<Import Project="$(SolutionDir)src\common.build.dll.props" />
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.build.tests.props" />
</Project>
</Project>

View file

@ -18,25 +18,19 @@ Author(s):
#pragma once
#include "../history.h"
#include "../readDataCooked.hpp"
#include "../cookedRead.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
static void InitReadData(CookedRead& cookedReadData,
const std::wstring& wstr) 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;
cookedReadData._insertionIndex = 0;
cookedReadData._prompt = wstr;
cookedReadData.MoveInsertionIndexToEnd();
}
static void InitHistory(CommandHistory& history) noexcept
@ -44,7 +38,7 @@ public:
history.Empty();
history.Flags |= 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"short and stout", 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(), 4u);

View file

@ -443,6 +443,10 @@ class SelectionInputTests
bool fResult = Selection::s_GetInputLineBoundaries(nullptr, nullptr);
VERIFY_IS_FALSE(fResult);
// prepare input buffer
m_state->PrepareGlobalInputBuffer();
auto cleanupInputBuffer = wil::scope_exit([&]() { m_state->CleanupGlobalInputBuffer(); });
// prepare some read data
m_state->PrepareReadHandle();
auto cleanupReadHandle = wil::scope_exit([&]() { m_state->CleanupReadHandle(); });
@ -451,7 +455,7 @@ class SelectionInputTests
// set up to clean up read data later
auto cleanupCookedRead = wil::scope_exit([&]() { m_state->CleanupCookedReadData(); });
COOKED_READ_DATA& readData = gci.CookedReadData();
CookedRead& readData = gci.CookedReadData();
// backup text info position over remainder of text execution duration
TextBuffer& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer();
@ -460,10 +464,13 @@ class SelectionInputTests
coordOldTextInfoPos.Y = textBuffer.GetCursor().GetPosition().Y;
// set various cursor positions
readData.OriginalCursorPosition().X = 15;
readData.OriginalCursorPosition().Y = 3;
readData.SetPromptStartLocation({ 15, 3 });
readData.VisibleCharCount() = 200;
// place some data into the prompt
readData.BufferInput(L't');
size_t numBytes = 0;
ULONG ctrlKeyState = 0;
VERIFY_ARE_EQUAL(readData.Read(true, numBytes, ctrlKeyState), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT));
textBuffer.GetCursor().SetXPosition(35);
textBuffer.GetCursor().SetYPosition(35);
@ -480,8 +487,8 @@ class SelectionInputTests
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);
VERIFY_ARE_EQUAL(coordStart.X, readData.PromptStartLocation().X);
VERIFY_ARE_EQUAL(coordStart.Y, readData.PromptStartLocation().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.
@ -489,15 +496,14 @@ class SelectionInputTests
const short cCharsToAdjust = ((short)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);
coordFinalPos.X = (readData.PromptStartLocation().X + cCharsToAdjust) % sRowWidth;
coordFinalPos.Y = readData.PromptStartLocation().Y + ((readData.PromptStartLocation().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;
readData.SetPromptStartLocation({ -1, -1 });
fResult = Selection::s_GetInputLineBoundaries(nullptr, &coordEnd);
VERIFY_IS_TRUE(fResult);

View file

@ -41,6 +41,7 @@
#include <iomanip>
#include <filesystem>
#include <functional>
#include <numeric>
// WIL

View file

@ -27,6 +27,7 @@ unit testing projects in the codebase without a bunch of overhead.
#include "../host/inputReadHandleData.h"
#include "../buffer/out/CharRow.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../host/cookedRead.hpp"
class CommonState
{
@ -125,15 +126,14 @@ public:
void PrepareCookedReadData()
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto* readData = new COOKED_READ_DATA(gci.pInputBuffer,
m_readHandle.get(),
gci.GetActiveOutputBuffer(),
0,
nullptr,
0,
nullptr,
L"",
{});
auto* readData = new CookedRead(gci.pInputBuffer,
m_readHandle.get(),
gci.GetActiveOutputBuffer(),
nullptr,
nullptr,
0,
0,
{});
gci.SetCookedReadData(readData);
}

View file

@ -52,6 +52,7 @@ const wchar_t UNICODE_BOX_DRAW_LIGHT_VERTICAL = 0x2502;
const wchar_t UNICODE_BOX_DRAW_LIGHT_UP_AND_RIGHT = 0x2514;
const wchar_t UNICODE_BOX_DRAW_LIGHT_UP_AND_LEFT = 0x2518;
const wchar_t UNICODE_INVALID = 0xFFFF;
const wchar_t UNICODE_CTRL_Z = 0x1a;
// This is the "Ctrl+C" character.
// With VKey='C', it generates a CTRL_C_EVENT

View file

@ -15,6 +15,7 @@
#include "..\..\host\server.h"
#include "..\..\host\scrolling.hpp"
#include "..\..\host\telemetry.hpp"
#include "..\..\host\cookedRead.hpp"
#include "..\inc\ServiceLocator.hpp"

View file

@ -73,7 +73,7 @@ std::string ConvertToA(const UINT codepage, const std::wstring_view source)
{
return {};
}
int iSource; // convert to int because Wc2Mb requires it.
THROW_IF_FAILED(SizeTToInt(source.size(), &iSource));
@ -347,7 +347,12 @@ std::deque<std::unique_ptr<KeyEvent>> SynthesizeNumpadEvents(const wchar_t wch,
CodepointWidth GetQuickCharWidth(const wchar_t wch) noexcept
{
// 0x00-0x1F is ambiguous by font
if (0x20 <= wch && wch <= 0x7e)
// ... but we render ctrl+z wide
if (wch == 0x1a)
{
return CodepointWidth::Wide;
}
else if (0x20 <= wch && wch <= 0x7e)
{
/* ASCII */
return CodepointWidth::Narrow;