mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-08 07:11:08 +00:00
Rewrite COOKED_READ_DATA (#15783)
This massive refactoring has two goals: * Enable us to go beyond UCS-2 support for input editing * Bring clarity into `COOKED_READ_DATA`'s inner workings Unfortunately, over time, knowledge about its exact operation was lost. While the new code is still complex it reduces the amount of code by 4x which will make preserving knowledge hopefully significantly easier. The new implementation is simpler and slower than the old one in a way, because every time the input line is modified it's rewritten to the text buffer from scratch. This however massively simplifies the underlying algorithm and the amount of state that needs to be tracked and results in a significant reduction in code size. It also makes it more robust, because there's less code now that can be incorrect. This "optimization laziness" can be afforded due the recent >10x improvements to `TextBuffer`'s text ingestion performance. For short inputs (<1000 characters) I still expect this implementation to outperform the conhost from the past. It has received one optimization already however: While reading text from the `InputBuffer` we'll now defer writing into the `TextBuffer` until we've stopped reading. This improves the overhead of pasting text from O(n^2) to O(n), which is immediately noticeable for inputs >100kB. Resizing the text buffer still ends up corrupting the input line however, which unfortunately cannot be fixed in `COOKED_READ_DATA`. The issue occurs due to bugs in `TextBuffer::Reflow` itself, as it misplaces the cursor if the prompt is on the last line of the buffer. Closes #1377 Closes #1503 Closes #4628 Closes #4975 Closes #5033 Closes #8008 This commit is required to fix #797 ## Validation Steps Performed * ASCII input ✅ * Chinese input (中文維基百科) ❔ * Resizing the window properly wraps/unwraps wide glyphs ❌ Broken due to `TextBuffer::Reflow` bugs * Surrogate pair input (🙂) ❔ * Resizing the window properly wraps/unwraps surrogate pairs ❌ Broken due to `TextBuffer::Reflow` bugs * In cmd.exe * Create 2 file: "a😊b.txt" and "a😟b.txt" * Press tab: Autocompletes "a😊b.txt" ✅ * Navigate the cursor right past the "a" * Press tab twice: Autocompletes "a😟b.txt" ✅ * Backspace deletes preceding glyphs ✅ * Ctrl+Backspace deletes preceding words ✅ * Escape clears input ✅ * Home navigates to start ✅ * Ctrl+Home deletes text between cursor and start ✅ * End navigates to end ✅ * Ctrl+End deletes text between cursor and end ✅ * Left navigates over previous code points ✅ * Ctrl+Left navigates to previous word-starts ✅ * Right and F1 navigate over next code points ✅ * Pressing right at the end of input copies characters from the previous command ✅ * Ctrl+Right navigates to next word-ends ✅ * Insert toggles overwrite mode ✅ * Delete deletes next code point ✅ * Up and F5 cycle through history ✅ * Doesn't crash with no history ✅ * Stops at first entry ✅ * Down cycles through history ✅ * Doesn't crash with no history ✅ * Stops at last entry ✅ * PageUp retrieves the oldest command ✅ * PageDown retrieves the newest command ✅ * F2 starts "copy to char" prompt ✅ * Escape dismisses prompt ✅ * Typing a character copies text from the previous command up until that character into the current buffer (acts identical to F3, but with automatic character search) ✅ * F3 copies the previous command into the current buffer, starting at the current cursor position, for as many characters as possible ✅ * Doesn't erase trailing text if the current buffer is longer than the previous command ✅ * Puts the cursor at the end of the copied text ✅ * F4 starts "copy from char" prompt ✅ * Escape dismisses prompt ✅ * Erases text between the current cursor position and the first instance of a given char (but not including it) ✅ * F6 inserts Ctrl+Z ✅ * F7 without modifiers starts "command list" prompt ✅ * Escape dismisses prompt ✅ * Minimum size of 40x10 characters ✅ * Width expands to fit the widest history command ✅ * Height expands up to 20 rows with longer histories ✅ * F9 starts "command number" prompt ✅ * Left/Right paste replace the buffer with the given command ✅ * And put cursor at the end of the buffer ✅ * Up/Down navigate selection through history ✅ * Stops at start/end with <10 entries ✅ * Stops at start/end with >20 entries ✅ * Wide text rendering during pagination with >20 entries ✅ * Shift+Up/Down moves history items around ✅ * Home navigates to first entry ✅ * End navigates to last entry ✅ * PageUp navigates by 20 items at a time or to first ✅ * PageDown navigates by 20 items at a time or to last ✅ * Alt+F7 clears command history ✅ * F8 cycles through commands that start with the same text as the current buffer up until the current cursor position ✅ * Doesn't crash with no history ✅ * F9 starts "command number" prompt ✅ * Escape dismisses prompt ✅ * Ignores non-ASCII-decimal characters ✅ * Allows entering between 1 and 5 digits ✅ * Pressing Enter fetches the given command from the history ✅ * Alt+F10 clears doskey aliases ✅
This commit is contained in:
1
.github/actions/spelling/expect/alphabet.txt
vendored
1
.github/actions/spelling/expect/alphabet.txt
vendored
@@ -21,6 +21,7 @@ BBBBCCCCC
|
||||
BBGGRR
|
||||
efg
|
||||
EFG
|
||||
efgh
|
||||
EFGh
|
||||
KLMNOQQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJ
|
||||
|
||||
13
.github/actions/spelling/expect/expect.txt
vendored
13
.github/actions/spelling/expect/expect.txt
vendored
@@ -17,7 +17,6 @@ ADDALIAS
|
||||
ADDREF
|
||||
ADDSTRING
|
||||
ADDTOOL
|
||||
AEnd
|
||||
AFew
|
||||
AFill
|
||||
AFX
|
||||
@@ -679,7 +678,7 @@ FSINFOCLASS
|
||||
fte
|
||||
Ftm
|
||||
Fullscreens
|
||||
fullwidth
|
||||
Fullwidth
|
||||
FUNCTIONCALL
|
||||
fuzzer
|
||||
fuzzmain
|
||||
@@ -923,7 +922,6 @@ itermcolors
|
||||
ITerminal
|
||||
itf
|
||||
Ith
|
||||
itoa
|
||||
IUI
|
||||
IUnknown
|
||||
ivalid
|
||||
@@ -1102,6 +1100,7 @@ MDs
|
||||
MEASUREITEM
|
||||
megamix
|
||||
memallocator
|
||||
meme
|
||||
MENUCHAR
|
||||
MENUCONTROL
|
||||
MENUDROPALIGNMENT
|
||||
@@ -1588,7 +1587,6 @@ rgrc
|
||||
rgs
|
||||
rgui
|
||||
rgw
|
||||
rgwch
|
||||
RIGHTALIGN
|
||||
RIGHTBUTTON
|
||||
riid
|
||||
@@ -1783,7 +1781,6 @@ STDMETHODCALLTYPE
|
||||
STDMETHODIMP
|
||||
STGM
|
||||
stl
|
||||
stoutapot
|
||||
Stri
|
||||
Stringable
|
||||
STRINGTABLE
|
||||
@@ -1838,7 +1835,6 @@ TBM
|
||||
tchar
|
||||
TCHFORMAT
|
||||
TCI
|
||||
tcome
|
||||
tcommandline
|
||||
tcommands
|
||||
Tdd
|
||||
@@ -1865,8 +1861,7 @@ testname
|
||||
TESTNULL
|
||||
testpass
|
||||
testpasses
|
||||
testtestabc
|
||||
testtesttesttesttest
|
||||
testtimeout
|
||||
TEXCOORD
|
||||
texel
|
||||
TExpected
|
||||
@@ -2083,7 +2078,6 @@ vtseq
|
||||
vtterm
|
||||
vttest
|
||||
VWX
|
||||
waaay
|
||||
waitable
|
||||
WANSUNG
|
||||
WANTARROWS
|
||||
@@ -2201,7 +2195,6 @@ wprp
|
||||
wprpi
|
||||
wregex
|
||||
writeback
|
||||
writechar
|
||||
WRITECONSOLE
|
||||
WRITECONSOLEINPUT
|
||||
WRITECONSOLEOUTPUT
|
||||
|
||||
@@ -416,6 +416,64 @@ size_t TextBuffer::GraphemePrev(const std::wstring_view& chars, size_t position)
|
||||
return til::utf16_iterate_prev(chars, position);
|
||||
}
|
||||
|
||||
// Pretend as if `position` is a regular cursor in the TextBuffer.
|
||||
// This function will then pretend as if you pressed the left/right arrow
|
||||
// keys `distance` amount of times (negative = left, positive = right).
|
||||
til::point TextBuffer::NavigateCursor(til::point position, til::CoordType distance) const
|
||||
{
|
||||
const til::CoordType maxX = _width - 1;
|
||||
const til::CoordType maxY = _height - 1;
|
||||
auto x = std::clamp(position.x, 0, maxX);
|
||||
auto y = std::clamp(position.y, 0, maxY);
|
||||
auto row = &GetRowByOffset(y);
|
||||
|
||||
if (distance < 0)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (x > 0)
|
||||
{
|
||||
x = row->NavigateToPrevious(x);
|
||||
}
|
||||
else if (y <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
--y;
|
||||
row = &GetRowByOffset(y);
|
||||
x = row->GetReadableColumnCount() - 1;
|
||||
}
|
||||
} while (++distance != 0);
|
||||
}
|
||||
else if (distance > 0)
|
||||
{
|
||||
auto rowWidth = row->GetReadableColumnCount();
|
||||
|
||||
do
|
||||
{
|
||||
if (x < rowWidth)
|
||||
{
|
||||
x = row->NavigateToNext(x);
|
||||
}
|
||||
else if (y >= maxY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
++y;
|
||||
row = &GetRowByOffset(y);
|
||||
rowWidth = row->GetReadableColumnCount();
|
||||
x = 0;
|
||||
}
|
||||
} while (--distance != 0);
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
// This function is intended for writing regular "lines" of text as it'll set the wrap flag on the given row.
|
||||
// You can continue calling the function on the same row as long as state.columnEnd < state.columnLimit.
|
||||
void TextBuffer::Write(til::CoordType row, const TextAttribute& attributes, RowWriteState& state)
|
||||
|
||||
@@ -138,6 +138,8 @@ public:
|
||||
static size_t GraphemeNext(const std::wstring_view& chars, size_t position) noexcept;
|
||||
static size_t GraphemePrev(const std::wstring_view& chars, size_t position) noexcept;
|
||||
|
||||
til::point NavigateCursor(til::point position, til::CoordType distance) const;
|
||||
|
||||
// Text insertion functions
|
||||
void Write(til::CoordType row, const TextAttribute& attributes, RowWriteState& state);
|
||||
void FillRect(const til::rect& rect, const std::wstring_view& fill, const TextAttribute& attributes);
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "../host/readDataCooked.hpp"
|
||||
#include "../host/output.h"
|
||||
#include "../host/_stream.h" // For WriteCharsLegacy
|
||||
#include "../host/cmdline.h" // For WC_INTERACTIVE
|
||||
#include "test/CommonState.hpp"
|
||||
|
||||
#include "../cascadia/TerminalCore/Terminal.hpp"
|
||||
@@ -3165,20 +3164,6 @@ void ConptyRoundtripTests::NewLinesAtBottomWithBackground()
|
||||
verifyBuffer(*termTb, term->_mutableViewport.ToExclusive());
|
||||
}
|
||||
|
||||
void doWriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view string, DWORD flags = 0)
|
||||
{
|
||||
auto dwNumBytes = string.size() * sizeof(wchar_t);
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(screenInfo,
|
||||
string.data(),
|
||||
string.data(),
|
||||
string.data(),
|
||||
&dwNumBytes,
|
||||
nullptr,
|
||||
screenInfo.GetTextBuffer().GetCursor().GetPosition().x,
|
||||
flags,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::WrapNewLineAtBottom()
|
||||
{
|
||||
// The actual bug case is
|
||||
@@ -3220,11 +3205,6 @@ void ConptyRoundtripTests::WrapNewLineAtBottom()
|
||||
return;
|
||||
}
|
||||
|
||||
// I've tested this with 0x0, 0x4, 0x80, 0x84, and 0-8, and none of these
|
||||
// flags seem to make a difference. So we're just assuming 0 here, so we
|
||||
// don't test a bunch of redundant cases.
|
||||
const auto writeCharsLegacyMode = 0;
|
||||
|
||||
// This test was originally written for
|
||||
// https://github.com/microsoft/terminal/issues/5691
|
||||
//
|
||||
@@ -3263,7 +3243,7 @@ void ConptyRoundtripTests::WrapNewLineAtBottom()
|
||||
}
|
||||
else if (writingMethod == PrintWithWriteCharsLegacy)
|
||||
{
|
||||
doWriteCharsLegacy(si, str, writeCharsLegacyMode);
|
||||
WriteCharsLegacy(si, str, false, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3421,7 +3401,7 @@ void ConptyRoundtripTests::WrapNewLineAtBottomLikeMSYS()
|
||||
}
|
||||
else if (writingMethod == PrintWithWriteCharsLegacy)
|
||||
{
|
||||
doWriteCharsLegacy(si, str, WC_INTERACTIVE);
|
||||
WriteCharsLegacy(si, str, true, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,549 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "CommandListPopup.hpp"
|
||||
#include "stream.h"
|
||||
#include "_stream.h"
|
||||
#include "cmdline.h"
|
||||
#include "misc.h"
|
||||
#include "_output.h"
|
||||
#include "dbcs.h"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
static constexpr size_t COMMAND_NUMBER_SIZE = 8; // size of command number buffer
|
||||
|
||||
// Routine Description:
|
||||
// - Calculates what the proposed size of the popup should be, based on the commands in the history
|
||||
// Arguments:
|
||||
// - history - the history to look through to measure command sizes
|
||||
// Return Value:
|
||||
// - the proposed size of the popup with the history list taken into account
|
||||
static til::size calculatePopupSize(const CommandHistory& history)
|
||||
{
|
||||
// this is the historical size of the popup, so it is now used as a minimum
|
||||
const til::size minSize = { 40, 10 };
|
||||
|
||||
// padding is for the command number listing before a command is printed to the window.
|
||||
// ex: |10: echo blah
|
||||
// ^^^^ <- these are the cells that are being accounted for by padding
|
||||
const size_t padding = 4;
|
||||
|
||||
// find the widest command history item and use it for the width
|
||||
size_t width = minSize.width;
|
||||
for (CommandHistory::Index i = 0; i < history.GetNumberOfCommands(); ++i)
|
||||
{
|
||||
const auto& historyItem = history.GetNth(i);
|
||||
width = std::max(width, historyItem.size() + padding);
|
||||
}
|
||||
if (width > SHRT_MAX)
|
||||
{
|
||||
width = SHRT_MAX;
|
||||
}
|
||||
|
||||
// calculate height, it can range up to 20 rows
|
||||
auto height = std::clamp(gsl::narrow<til::CoordType>(history.GetNumberOfCommands()), minSize.height, 20);
|
||||
|
||||
return { gsl::narrow_cast<til::CoordType>(width), height };
|
||||
}
|
||||
|
||||
CommandListPopup::CommandListPopup(SCREEN_INFORMATION& screenInfo, const CommandHistory& history) :
|
||||
Popup(screenInfo, calculatePopupSize(history)),
|
||||
_history{ history },
|
||||
_currentCommand{ std::min(history.LastDisplayed, history.GetNumberOfCommands() - 1) }
|
||||
{
|
||||
FAIL_FAST_IF(_currentCommand < 0);
|
||||
_setBottomIndex();
|
||||
}
|
||||
|
||||
[[nodiscard]] NTSTATUS CommandListPopup::_handlePopupKeys(COOKED_READ_DATA& cookedReadData,
|
||||
const wchar_t wch,
|
||||
const DWORD modifiers) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
CommandHistory::Index Index = 0;
|
||||
const auto shiftPressed = WI_IsFlagSet(modifiers, SHIFT_PRESSED);
|
||||
switch (wch)
|
||||
{
|
||||
case VK_F9:
|
||||
{
|
||||
const auto hr = CommandLine::Instance().StartCommandNumberPopup(cookedReadData);
|
||||
if (S_FALSE == hr)
|
||||
{
|
||||
// If we couldn't make the popup, break and go around to read another input character.
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
case VK_ESCAPE:
|
||||
CommandLine::Instance().EndCurrentPopup();
|
||||
return CONSOLE_STATUS_WAIT_NO_BLOCK;
|
||||
case VK_UP:
|
||||
if (shiftPressed)
|
||||
{
|
||||
return _swapUp(cookedReadData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_update(-1);
|
||||
}
|
||||
break;
|
||||
case VK_DOWN:
|
||||
if (shiftPressed)
|
||||
{
|
||||
return _swapDown(cookedReadData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_update(1);
|
||||
}
|
||||
break;
|
||||
case VK_END:
|
||||
// Move waaay forward, UpdateCommandListPopup() can handle it.
|
||||
_update(cookedReadData.History().GetNumberOfCommands());
|
||||
break;
|
||||
case VK_HOME:
|
||||
// Move waaay back, UpdateCommandListPopup() can handle it.
|
||||
_update(-cookedReadData.History().GetNumberOfCommands());
|
||||
break;
|
||||
case VK_PRIOR:
|
||||
_update(-Height());
|
||||
break;
|
||||
case VK_NEXT:
|
||||
_update(Height());
|
||||
break;
|
||||
case VK_DELETE:
|
||||
return _deleteSelection(cookedReadData);
|
||||
case VK_LEFT:
|
||||
case VK_RIGHT:
|
||||
Index = _currentCommand;
|
||||
CommandLine::Instance().EndCurrentPopup();
|
||||
SetCurrentCommandLine(cookedReadData, Index);
|
||||
return CONSOLE_STATUS_WAIT_NO_BLOCK;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void CommandListPopup::_setBottomIndex()
|
||||
{
|
||||
if (_currentCommand < _history.GetNumberOfCommands() - Height())
|
||||
{
|
||||
_bottomIndex = std::max(_currentCommand, Height() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_bottomIndex = _history.GetNumberOfCommands() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] NTSTATUS CommandListPopup::_deleteSelection(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& history = cookedReadData.History();
|
||||
history.Remove(_currentCommand);
|
||||
_setBottomIndex();
|
||||
|
||||
if (history.GetNumberOfCommands() == 0)
|
||||
{
|
||||
// close the popup
|
||||
return CONSOLE_STATUS_READ_COMPLETE;
|
||||
}
|
||||
else if (_currentCommand >= history.GetNumberOfCommands())
|
||||
{
|
||||
_currentCommand = history.GetNumberOfCommands() - 1;
|
||||
_bottomIndex = _currentCommand;
|
||||
}
|
||||
|
||||
_drawList();
|
||||
}
|
||||
CATCH_LOG();
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - moves the selected history item up in the history list
|
||||
// Arguments:
|
||||
// - cookedReadData - the read wait object to operate upon
|
||||
[[nodiscard]] NTSTATUS CommandListPopup::_swapUp(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& history = cookedReadData.History();
|
||||
|
||||
if (history.GetNumberOfCommands() <= 1 || _currentCommand == 0)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
history.Swap(_currentCommand, _currentCommand - 1);
|
||||
_update(-1);
|
||||
_drawList();
|
||||
}
|
||||
CATCH_LOG();
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - moves the selected history item down in the history list
|
||||
// Arguments:
|
||||
// - cookedReadData - the read wait object to operate upon
|
||||
[[nodiscard]] NTSTATUS CommandListPopup::_swapDown(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& history = cookedReadData.History();
|
||||
|
||||
if (history.GetNumberOfCommands() <= 1 || _currentCommand == history.GetNumberOfCommands() - 1)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
history.Swap(_currentCommand, _currentCommand + 1);
|
||||
_update(1);
|
||||
_drawList();
|
||||
}
|
||||
CATCH_LOG();
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void CommandListPopup::_handleReturn(COOKED_READ_DATA& cookedReadData)
|
||||
{
|
||||
CommandHistory::Index Index = 0;
|
||||
auto Status = STATUS_SUCCESS;
|
||||
DWORD LineCount = 1;
|
||||
Index = _currentCommand;
|
||||
CommandLine::Instance().EndCurrentPopup();
|
||||
SetCurrentCommandLine(cookedReadData, Index);
|
||||
cookedReadData.ProcessInput(UNICODE_CARRIAGERETURN, 0, Status);
|
||||
// complete read
|
||||
if (cookedReadData.IsEchoInput())
|
||||
{
|
||||
// check for alias
|
||||
cookedReadData.ProcessAliases(LineCount);
|
||||
}
|
||||
|
||||
Status = STATUS_SUCCESS;
|
||||
size_t NumBytes;
|
||||
if (cookedReadData.BytesRead() > cookedReadData.UserBufferSize() || LineCount > 1)
|
||||
{
|
||||
if (LineCount > 1)
|
||||
{
|
||||
const wchar_t* Tmp;
|
||||
for (Tmp = cookedReadData.BufferStartPtr(); *Tmp != UNICODE_LINEFEED; Tmp++)
|
||||
{
|
||||
FAIL_FAST_IF(!(Tmp < (cookedReadData.BufferStartPtr() + cookedReadData.BytesRead())));
|
||||
}
|
||||
NumBytes = (Tmp - cookedReadData.BufferStartPtr() + 1) * sizeof(*Tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
NumBytes = cookedReadData.UserBufferSize();
|
||||
}
|
||||
|
||||
// Copy what we can fit into the user buffer
|
||||
const auto bytesWritten = cookedReadData.SavePromptToUserBuffer(NumBytes / sizeof(wchar_t));
|
||||
|
||||
// Store all of the remaining as pending until the next read operation.
|
||||
cookedReadData.SavePendingInput(NumBytes / sizeof(wchar_t), LineCount > 1);
|
||||
NumBytes = bytesWritten;
|
||||
}
|
||||
else
|
||||
{
|
||||
NumBytes = cookedReadData.BytesRead();
|
||||
NumBytes = cookedReadData.SavePromptToUserBuffer(NumBytes / sizeof(wchar_t));
|
||||
}
|
||||
|
||||
cookedReadData.SetReportedByteCount(NumBytes);
|
||||
}
|
||||
|
||||
void CommandListPopup::_cycleSelectionToMatchingCommands(COOKED_READ_DATA& cookedReadData, const wchar_t wch)
|
||||
{
|
||||
CommandHistory::Index Index = 0;
|
||||
if (cookedReadData.History().FindMatchingCommand({ &wch, 1 },
|
||||
_currentCommand,
|
||||
Index,
|
||||
CommandHistory::MatchOptions::JustLooking))
|
||||
{
|
||||
_update(Index - _currentCommand, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine handles the command list popup. It returns when we're out of input or the user has selected a command line.
|
||||
// Return Value:
|
||||
// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created
|
||||
// - CONSOLE_STATUS_READ_COMPLETE - user hit return
|
||||
[[nodiscard]] NTSTATUS CommandListPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
auto Status = STATUS_SUCCESS;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto wch = UNICODE_NULL;
|
||||
auto popupKeys = false;
|
||||
DWORD modifiers = 0;
|
||||
|
||||
Status = _getUserInput(cookedReadData, popupKeys, modifiers, wch);
|
||||
if (FAILED_NTSTATUS(Status))
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
if (popupKeys)
|
||||
{
|
||||
Status = _handlePopupKeys(cookedReadData, wch, modifiers);
|
||||
if (Status != STATUS_SUCCESS)
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
}
|
||||
else if (wch == UNICODE_CARRIAGERETURN)
|
||||
{
|
||||
_handleReturn(cookedReadData);
|
||||
return CONSOLE_STATUS_READ_COMPLETE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// cycle through commands that start with the letter of the key pressed
|
||||
_cycleSelectionToMatchingCommands(cookedReadData, wch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandListPopup::_DrawContent()
|
||||
{
|
||||
_drawList();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Draws a list of commands for the user to choose from
|
||||
void CommandListPopup::_drawList()
|
||||
{
|
||||
// draw empty popup
|
||||
til::point WriteCoord;
|
||||
WriteCoord.x = _region.left + 1;
|
||||
WriteCoord.y = _region.top + 1;
|
||||
size_t lStringLength = Width();
|
||||
for (til::CoordType i = 0; i < Height(); ++i)
|
||||
{
|
||||
const OutputCellIterator spaces(UNICODE_SPACE, _attributes, lStringLength);
|
||||
const auto result = _screenInfo.Write(spaces, WriteCoord);
|
||||
lStringLength = result.GetCellDistance(spaces);
|
||||
WriteCoord.y += 1;
|
||||
}
|
||||
|
||||
auto api = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().api;
|
||||
|
||||
WriteCoord.y = _region.top + 1;
|
||||
auto i = std::max(_bottomIndex - Height() + 1, 0);
|
||||
for (; i <= _bottomIndex; i++)
|
||||
{
|
||||
CHAR CommandNumber[COMMAND_NUMBER_SIZE];
|
||||
// Write command number to screen.
|
||||
if (0 != _itoa_s(i, CommandNumber, ARRAYSIZE(CommandNumber), 10))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto CommandNumberPtr = CommandNumber;
|
||||
|
||||
size_t CommandNumberLength;
|
||||
if (FAILED(StringCchLengthA(CommandNumberPtr, ARRAYSIZE(CommandNumber), &CommandNumberLength)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
__assume_bound(CommandNumberLength);
|
||||
|
||||
if (CommandNumberLength + 1 >= ARRAYSIZE(CommandNumber))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CommandNumber[CommandNumberLength] = ':';
|
||||
CommandNumber[CommandNumberLength + 1] = ' ';
|
||||
CommandNumberLength += 2;
|
||||
if (CommandNumberLength > static_cast<ULONG>(Width()))
|
||||
{
|
||||
CommandNumberLength = static_cast<ULONG>(Width());
|
||||
}
|
||||
|
||||
WriteCoord.x = _region.left + 1;
|
||||
|
||||
LOG_IF_FAILED(api->WriteConsoleOutputCharacterAImpl(_screenInfo,
|
||||
{ CommandNumberPtr, CommandNumberLength },
|
||||
WriteCoord,
|
||||
CommandNumberLength));
|
||||
|
||||
// write command to screen
|
||||
auto command = _history.GetNth(i);
|
||||
lStringLength = command.size();
|
||||
{
|
||||
auto lTmpStringLength = lStringLength;
|
||||
auto lPopupLength = static_cast<LONG>(Width() - CommandNumberLength);
|
||||
auto lpStr = command.data();
|
||||
while (lTmpStringLength--)
|
||||
{
|
||||
if (IsGlyphFullWidth(*lpStr++))
|
||||
{
|
||||
lPopupLength -= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
lPopupLength--;
|
||||
}
|
||||
|
||||
if (lPopupLength <= 0)
|
||||
{
|
||||
lStringLength -= lTmpStringLength;
|
||||
if (lPopupLength < 0)
|
||||
{
|
||||
lStringLength--;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WriteCoord.x = gsl::narrow<til::CoordType>(WriteCoord.x + CommandNumberLength);
|
||||
size_t used;
|
||||
LOG_IF_FAILED(api->WriteConsoleOutputCharacterWImpl(_screenInfo,
|
||||
{ command.data(), lStringLength },
|
||||
WriteCoord,
|
||||
used));
|
||||
|
||||
// write attributes to screen
|
||||
if (i == _currentCommand)
|
||||
{
|
||||
WriteCoord.x = _region.left + 1;
|
||||
// inverted attributes
|
||||
lStringLength = Width();
|
||||
auto inverted = _attributes;
|
||||
inverted.Invert();
|
||||
|
||||
const OutputCellIterator it(inverted, lStringLength);
|
||||
const auto done = _screenInfo.Write(it, WriteCoord);
|
||||
|
||||
lStringLength = done.GetCellDistance(it);
|
||||
}
|
||||
|
||||
WriteCoord.y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - For popup lists, will adjust the position of the highlighted item and
|
||||
// possibly scroll the list if necessary.
|
||||
// Arguments:
|
||||
// - originalDelta - The number of lines to move up or down
|
||||
// - wrap - Down past the bottom or up past the top should wrap the command list
|
||||
void CommandListPopup::_update(const CommandHistory::Index originalDelta, const bool wrap)
|
||||
{
|
||||
auto delta = originalDelta;
|
||||
if (delta == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto Size = Height();
|
||||
|
||||
auto CurCmdNum = _currentCommand;
|
||||
CommandHistory::Index NewCmdNum = CurCmdNum + delta;
|
||||
|
||||
if (wrap)
|
||||
{
|
||||
// Modulo the number of commands to "circle" around if we went off the end.
|
||||
NewCmdNum %= _history.GetNumberOfCommands();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NewCmdNum >= _history.GetNumberOfCommands())
|
||||
{
|
||||
NewCmdNum = _history.GetNumberOfCommands() - 1;
|
||||
}
|
||||
else if (NewCmdNum < 0)
|
||||
{
|
||||
NewCmdNum = 0;
|
||||
}
|
||||
}
|
||||
delta = NewCmdNum - CurCmdNum;
|
||||
|
||||
auto Scroll = false;
|
||||
// determine amount to scroll, if any
|
||||
if (NewCmdNum <= _bottomIndex - Size)
|
||||
{
|
||||
_bottomIndex += delta;
|
||||
if (_bottomIndex < Size - 1)
|
||||
{
|
||||
_bottomIndex = Size - 1;
|
||||
}
|
||||
Scroll = true;
|
||||
}
|
||||
else if (NewCmdNum > _bottomIndex)
|
||||
{
|
||||
_bottomIndex += delta;
|
||||
if (_bottomIndex >= _history.GetNumberOfCommands())
|
||||
{
|
||||
_bottomIndex = _history.GetNumberOfCommands() - 1;
|
||||
}
|
||||
Scroll = true;
|
||||
}
|
||||
|
||||
// write commands to popup
|
||||
if (Scroll)
|
||||
{
|
||||
_currentCommand = NewCmdNum;
|
||||
_drawList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateHighlight(_currentCommand, NewCmdNum);
|
||||
_currentCommand = NewCmdNum;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Adjusts the highlighted line in a list of commands
|
||||
// Arguments:
|
||||
// - OldCurrentCommand - The previous command highlighted
|
||||
// - NewCurrentCommand - The new command to be highlighted.
|
||||
void CommandListPopup::_updateHighlight(const CommandHistory::Index OldCurrentCommand, const CommandHistory::Index NewCurrentCommand)
|
||||
{
|
||||
til::CoordType TopIndex;
|
||||
if (_bottomIndex < Height())
|
||||
{
|
||||
TopIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
TopIndex = _bottomIndex - Height() + 1;
|
||||
}
|
||||
til::point WriteCoord;
|
||||
WriteCoord.x = _region.left + 1;
|
||||
size_t lStringLength = Width();
|
||||
|
||||
WriteCoord.y = _region.top + 1 + OldCurrentCommand - TopIndex;
|
||||
|
||||
const OutputCellIterator it(_attributes, lStringLength);
|
||||
const auto done = _screenInfo.Write(it, WriteCoord);
|
||||
lStringLength = done.GetCellDistance(it);
|
||||
|
||||
// highlight new command
|
||||
WriteCoord.y = _region.top + 1 + NewCurrentCommand - TopIndex;
|
||||
|
||||
// inverted attributes
|
||||
auto inverted = _attributes;
|
||||
inverted.Invert();
|
||||
const OutputCellIterator itAttr(inverted, lStringLength);
|
||||
const auto doneAttr = _screenInfo.Write(itAttr, WriteCoord);
|
||||
lStringLength = done.GetCellDistance(itAttr);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CommandListPopup.hpp
|
||||
|
||||
Abstract:
|
||||
- Popup used for use command list input
|
||||
- contains code pulled from popup.cpp and cmdline.cpp
|
||||
|
||||
Author:
|
||||
- Austin Diviness (AustDi) 18-Aug-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "popup.h"
|
||||
|
||||
class CommandListPopup : public Popup
|
||||
{
|
||||
public:
|
||||
CommandListPopup(SCREEN_INFORMATION& screenInfo, const CommandHistory& history);
|
||||
|
||||
[[nodiscard]] NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override;
|
||||
|
||||
protected:
|
||||
void _DrawContent() override;
|
||||
|
||||
private:
|
||||
void _drawList();
|
||||
void _update(const CommandHistory::Index delta, const bool wrap = false);
|
||||
void _updateHighlight(const CommandHistory::Index oldCommand, const CommandHistory::Index newCommand);
|
||||
|
||||
void _handleReturn(COOKED_READ_DATA& cookedReadData);
|
||||
void _cycleSelectionToMatchingCommands(COOKED_READ_DATA& cookedReadData, const wchar_t wch);
|
||||
void _setBottomIndex();
|
||||
[[nodiscard]] NTSTATUS _handlePopupKeys(COOKED_READ_DATA& cookedReadData, const wchar_t wch, const DWORD modifiers) noexcept;
|
||||
[[nodiscard]] NTSTATUS _deleteSelection(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
[[nodiscard]] NTSTATUS _swapUp(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
[[nodiscard]] NTSTATUS _swapDown(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
|
||||
CommandHistory::Index _currentCommand;
|
||||
CommandHistory::Index _bottomIndex; // number of command displayed on last line of popup
|
||||
const CommandHistory& _history;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class CommandListPopupTests;
|
||||
#endif
|
||||
};
|
||||
@@ -1,195 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "CommandNumberPopup.hpp"
|
||||
|
||||
#include "stream.h"
|
||||
#include "_stream.h"
|
||||
#include "cmdline.h"
|
||||
#include "resource.h"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
// 5 digit number for command history
|
||||
static constexpr size_t COMMAND_NUMBER_LENGTH = 5;
|
||||
|
||||
static constexpr size_t COMMAND_NUMBER_PROMPT_LENGTH = 22;
|
||||
|
||||
CommandNumberPopup::CommandNumberPopup(SCREEN_INFORMATION& screenInfo) :
|
||||
Popup(screenInfo, { COMMAND_NUMBER_PROMPT_LENGTH + COMMAND_NUMBER_LENGTH, 1 })
|
||||
{
|
||||
_userInput.reserve(COMMAND_NUMBER_LENGTH);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - handles numerical user input
|
||||
// Arguments:
|
||||
// - cookedReadData - read data to operate on
|
||||
// - wch - digit to handle
|
||||
void CommandNumberPopup::_handleNumber(COOKED_READ_DATA& cookedReadData, const wchar_t wch) noexcept
|
||||
{
|
||||
if (_userInput.size() < COMMAND_NUMBER_LENGTH)
|
||||
{
|
||||
auto CharsToWrite = sizeof(wchar_t);
|
||||
const auto realAttributes = cookedReadData.ScreenInfo().GetAttributes();
|
||||
cookedReadData.ScreenInfo().SetAttributes(_attributes);
|
||||
size_t NumSpaces;
|
||||
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
|
||||
_userInput.data(),
|
||||
_userInput.data() + _userInput.size(),
|
||||
&wch,
|
||||
&CharsToWrite,
|
||||
&NumSpaces,
|
||||
cookedReadData.OriginalCursorPosition().x,
|
||||
WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE,
|
||||
nullptr));
|
||||
cookedReadData.ScreenInfo().SetAttributes(realAttributes);
|
||||
try
|
||||
{
|
||||
_push(wch);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - handles backspace user input. removes a digit from the user input
|
||||
// Arguments:
|
||||
// - cookedReadData - read data to operate on
|
||||
void CommandNumberPopup::_handleBackspace(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
if (_userInput.size() > 0)
|
||||
{
|
||||
auto CharsToWrite = sizeof(WCHAR);
|
||||
const auto backspace = UNICODE_BACKSPACE;
|
||||
const auto realAttributes = cookedReadData.ScreenInfo().GetAttributes();
|
||||
cookedReadData.ScreenInfo().SetAttributes(_attributes);
|
||||
size_t NumSpaces;
|
||||
FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
|
||||
_userInput.data(),
|
||||
_userInput.data() + _userInput.size(),
|
||||
&backspace,
|
||||
&CharsToWrite,
|
||||
&NumSpaces,
|
||||
cookedReadData.OriginalCursorPosition().x,
|
||||
WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE,
|
||||
nullptr));
|
||||
cookedReadData.ScreenInfo().SetAttributes(realAttributes);
|
||||
_pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - handles escape user input. cancels the popup
|
||||
// Arguments:
|
||||
// - cookedReadData - read data to operate on
|
||||
void CommandNumberPopup::_handleEscape(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
CommandLine::Instance().EndAllPopups();
|
||||
|
||||
// Note that cookedReadData's OriginalCursorPosition is the position before ANY text was entered on the edit line.
|
||||
// We want to use the position before the cursor was moved for this popup handler specifically, which may
|
||||
// be *anywhere* in the edit line and will be synchronized with the pointers in the cookedReadData
|
||||
// structure (BufPtr, etc.)
|
||||
LOG_IF_FAILED(cookedReadData.ScreenInfo().SetCursorPosition(cookedReadData.BeforeDialogCursorPosition(), TRUE));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - handles return user input. sets the prompt to the history item indicated
|
||||
// Arguments:
|
||||
// - cookedReadData - read data to operate on
|
||||
void CommandNumberPopup::_handleReturn(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
const auto commandNumber = gsl::narrow<short>(std::min(_parse(), cookedReadData.History().GetNumberOfCommands() - 1));
|
||||
|
||||
CommandLine::Instance().EndAllPopups();
|
||||
SetCurrentCommandLine(cookedReadData, commandNumber);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine handles the command number selection popup.
|
||||
// Return Value:
|
||||
// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created
|
||||
// - CONSOLE_STATUS_READ_COMPLETE - user hit return
|
||||
[[nodiscard]] NTSTATUS CommandNumberPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
auto Status = STATUS_SUCCESS;
|
||||
auto wch = UNICODE_NULL;
|
||||
auto popupKeys = false;
|
||||
DWORD modifiers = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Status = _getUserInput(cookedReadData, popupKeys, modifiers, wch);
|
||||
if (FAILED_NTSTATUS(Status))
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
if (std::iswdigit(wch))
|
||||
{
|
||||
_handleNumber(cookedReadData, wch);
|
||||
}
|
||||
else if (wch == UNICODE_BACKSPACE)
|
||||
{
|
||||
_handleBackspace(cookedReadData);
|
||||
}
|
||||
else if (wch == VK_ESCAPE)
|
||||
{
|
||||
_handleEscape(cookedReadData);
|
||||
break;
|
||||
}
|
||||
else if (wch == UNICODE_CARRIAGERETURN)
|
||||
{
|
||||
_handleReturn(cookedReadData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CONSOLE_STATUS_WAIT_NO_BLOCK;
|
||||
}
|
||||
|
||||
void CommandNumberPopup::_DrawContent()
|
||||
{
|
||||
_DrawPrompt(ID_CONSOLE_MSGCMDLINEF9);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - adds single digit number to the popup's number buffer
|
||||
// Arguments:
|
||||
// - wch - char of the number to add. must be in the range [L'0', L'9']
|
||||
// Note: will throw if wch is out of range
|
||||
void CommandNumberPopup::_push(const wchar_t wch)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, !std::iswdigit(wch));
|
||||
if (_userInput.size() < COMMAND_NUMBER_LENGTH)
|
||||
{
|
||||
_userInput += wch;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - removes the last number added to the number buffer
|
||||
void CommandNumberPopup::_pop() noexcept
|
||||
{
|
||||
if (!_userInput.empty())
|
||||
{
|
||||
_userInput.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - get numerical value for the data stored in the number buffer
|
||||
// Return Value:
|
||||
// - parsed integer representing the string value found in the number buffer
|
||||
int CommandNumberPopup::_parse() const noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return std::stoi(_userInput);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CommandNumberPopup.hpp
|
||||
|
||||
Abstract:
|
||||
- Popup used for use command number input
|
||||
- contains code pulled from popup.cpp and cmdline.cpp
|
||||
|
||||
Author:
|
||||
- Austin Diviness (AustDi) 18-Aug-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "popup.h"
|
||||
|
||||
class CommandNumberPopup final : public Popup
|
||||
{
|
||||
public:
|
||||
explicit CommandNumberPopup(SCREEN_INFORMATION& screenInfo);
|
||||
|
||||
[[nodiscard]] NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override;
|
||||
|
||||
protected:
|
||||
void _DrawContent() override;
|
||||
|
||||
private:
|
||||
std::wstring _userInput;
|
||||
|
||||
void _handleNumber(COOKED_READ_DATA& cookedReadData, const wchar_t wch) noexcept;
|
||||
void _handleBackspace(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
void _handleEscape(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
void _handleReturn(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
|
||||
void _push(const wchar_t wch);
|
||||
void _pop() noexcept;
|
||||
int _parse() const noexcept;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class CommandNumberPopupTests;
|
||||
#endif
|
||||
};
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "CopyFromCharPopup.hpp"
|
||||
|
||||
#include "_stream.h"
|
||||
#include "resource.h"
|
||||
|
||||
static constexpr size_t COPY_FROM_CHAR_PROMPT_LENGTH = 28;
|
||||
|
||||
CopyFromCharPopup::CopyFromCharPopup(SCREEN_INFORMATION& screenInfo) :
|
||||
Popup(screenInfo, { COPY_FROM_CHAR_PROMPT_LENGTH + 2, 1 })
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine handles the delete from cursor to char popup. It returns when we're out of input or the user has entered a char.
|
||||
// Return Value:
|
||||
// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created
|
||||
// - CONSOLE_STATUS_READ_COMPLETE - user hit return
|
||||
[[nodiscard]] NTSTATUS CopyFromCharPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
// get user input
|
||||
auto Char = UNICODE_NULL;
|
||||
auto PopupKeys = false;
|
||||
DWORD modifiers = 0;
|
||||
auto Status = _getUserInput(cookedReadData, PopupKeys, modifiers, Char);
|
||||
if (FAILED_NTSTATUS(Status))
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
CommandLine::Instance().EndCurrentPopup();
|
||||
|
||||
if (PopupKeys && Char == VK_ESCAPE)
|
||||
{
|
||||
return CONSOLE_STATUS_WAIT_NO_BLOCK;
|
||||
}
|
||||
|
||||
const auto span = cookedReadData.SpanAtPointer();
|
||||
const auto foundLocation = std::find(std::next(span.begin()), span.end(), Char);
|
||||
if (foundLocation == span.end())
|
||||
{
|
||||
// char not found, delete everything to the right of the cursor
|
||||
CommandLine::Instance().DeletePromptAfterCursor(cookedReadData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// char was found, delete everything between the cursor and it
|
||||
const auto difference = std::distance(span.begin(), foundLocation);
|
||||
for (unsigned int i = 0; i < gsl::narrow<unsigned int>(difference); ++i)
|
||||
{
|
||||
CommandLine::Instance().DeleteFromRightOfCursor(cookedReadData);
|
||||
}
|
||||
}
|
||||
return CONSOLE_STATUS_WAIT_NO_BLOCK;
|
||||
}
|
||||
|
||||
void CopyFromCharPopup::_DrawContent()
|
||||
{
|
||||
_DrawPrompt(ID_CONSOLE_MSGCMDLINEF4);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CopyFromCharPopup.hpp
|
||||
|
||||
Abstract:
|
||||
- Popup used for use copying from char input
|
||||
- contains code pulled from popup.cpp and cmdline.cpp
|
||||
|
||||
Author:
|
||||
- Austin Diviness (AustDi) 18-Aug-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "popup.h"
|
||||
|
||||
class CopyFromCharPopup final : public Popup
|
||||
{
|
||||
public:
|
||||
explicit CopyFromCharPopup(SCREEN_INFORMATION& screenInfo);
|
||||
|
||||
[[nodiscard]] NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override;
|
||||
|
||||
protected:
|
||||
void _DrawContent() override;
|
||||
};
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "CopyToCharPopup.hpp"
|
||||
|
||||
#include "stream.h"
|
||||
#include "_stream.h"
|
||||
#include "resource.h"
|
||||
|
||||
static constexpr size_t COPY_TO_CHAR_PROMPT_LENGTH = 26;
|
||||
|
||||
CopyToCharPopup::CopyToCharPopup(SCREEN_INFORMATION& screenInfo) :
|
||||
Popup(screenInfo, { COPY_TO_CHAR_PROMPT_LENGTH + 2, 1 })
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - copies text from the previous command into the current prompt line, up to but not including the first
|
||||
// instance of wch after the current cookedReadData's cursor position. if wch is not found, nothing is copied.
|
||||
// Arguments:
|
||||
// - cookedReadData - the read data to operate on
|
||||
// - LastCommand - the most recent command run
|
||||
// - wch - the wchar to copy up to
|
||||
void CopyToCharPopup::_copyToChar(COOKED_READ_DATA& cookedReadData, const std::wstring_view LastCommand, const wchar_t wch)
|
||||
{
|
||||
// make sure that there it is possible to copy any found text over
|
||||
if (cookedReadData.InsertionPoint() >= LastCommand.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto searchStart = std::next(LastCommand.cbegin(), cookedReadData.InsertionPoint() + 1);
|
||||
auto location = std::find(searchStart, LastCommand.cend(), wch);
|
||||
|
||||
// didn't find wch so copy nothing
|
||||
if (location == LastCommand.cend())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto startIt = std::next(LastCommand.cbegin(), cookedReadData.InsertionPoint());
|
||||
const auto endIt = location;
|
||||
|
||||
cookedReadData.Write({ &*startIt, gsl::narrow<size_t>(std::distance(startIt, endIt)) });
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine handles the delete char popup. It returns when we're out of input or the user has entered a char.
|
||||
// Return Value:
|
||||
// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created
|
||||
// - CONSOLE_STATUS_READ_COMPLETE - user hit return
|
||||
[[nodiscard]] NTSTATUS CopyToCharPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept
|
||||
{
|
||||
auto wch = UNICODE_NULL;
|
||||
auto popupKey = false;
|
||||
DWORD modifiers = 0;
|
||||
auto Status = _getUserInput(cookedReadData, popupKey, modifiers, wch);
|
||||
if (FAILED_NTSTATUS(Status))
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
CommandLine::Instance().EndCurrentPopup();
|
||||
|
||||
if (popupKey && wch == VK_ESCAPE)
|
||||
{
|
||||
return CONSOLE_STATUS_WAIT_NO_BLOCK;
|
||||
}
|
||||
|
||||
// copy up to specified char
|
||||
const auto lastCommand = cookedReadData.History().GetLastCommand();
|
||||
if (!lastCommand.empty())
|
||||
{
|
||||
_copyToChar(cookedReadData, lastCommand, wch);
|
||||
}
|
||||
|
||||
return CONSOLE_STATUS_WAIT_NO_BLOCK;
|
||||
}
|
||||
|
||||
void CopyToCharPopup::_DrawContent()
|
||||
{
|
||||
_DrawPrompt(ID_CONSOLE_MSGCMDLINEF2);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CopyToCharPopup.hpp
|
||||
|
||||
Abstract:
|
||||
- Popup used for use copying to char input
|
||||
- contains code pulled from popup.cpp and cmdline.cpp
|
||||
|
||||
Author:
|
||||
- Austin Diviness (AustDi) 18-Aug-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "popup.h"
|
||||
|
||||
class CopyToCharPopup final : public Popup
|
||||
{
|
||||
public:
|
||||
CopyToCharPopup(SCREEN_INFORMATION& screenInfo);
|
||||
|
||||
[[nodiscard]] NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept override;
|
||||
|
||||
protected:
|
||||
void _DrawContent() override;
|
||||
|
||||
private:
|
||||
void _copyToChar(COOKED_READ_DATA& cookedReadData, const std::wstring_view LastCommand, const wchar_t wch);
|
||||
};
|
||||
@@ -39,7 +39,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
|
||||
// - coordCursor - New location of cursor.
|
||||
// - fKeepCursorVisible - TRUE if changing window origin desirable when hit right edge
|
||||
// Return Value:
|
||||
void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, const BOOL fKeepCursorVisible, _Inout_opt_ til::CoordType* psScrollY)
|
||||
static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, const bool interactive, _Inout_opt_ til::CoordType* psScrollY)
|
||||
{
|
||||
const auto bufferSize = screenInfo.GetBufferSize().Dimensions();
|
||||
if (coordCursor.x < 0)
|
||||
@@ -77,7 +77,7 @@ void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordC
|
||||
{
|
||||
*psScrollY += bufferSize.height - coordCursor.y - 1;
|
||||
}
|
||||
coordCursor.y += bufferSize.height - coordCursor.y - 1;
|
||||
coordCursor.y = bufferSize.height - 1;
|
||||
}
|
||||
|
||||
const auto cursorMovedPastViewport = coordCursor.y > screenInfo.GetViewport().BottomInclusive();
|
||||
@@ -91,21 +91,19 @@ void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordC
|
||||
LOG_IF_FAILED(screenInfo.SetViewportOrigin(false, WindowOrigin, true));
|
||||
}
|
||||
|
||||
if (fKeepCursorVisible)
|
||||
if (interactive)
|
||||
{
|
||||
screenInfo.MakeCursorVisible(coordCursor);
|
||||
}
|
||||
LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor, !!fKeepCursorVisible));
|
||||
LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor, interactive));
|
||||
}
|
||||
|
||||
// As the name implies, this writes text without processing its control characters.
|
||||
static size_t _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const DWORD dwFlags, til::CoordType* const psScrollY, const std::wstring_view& text)
|
||||
static void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, const bool interactive, til::CoordType* psScrollY)
|
||||
{
|
||||
const auto keepCursorVisible = WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE);
|
||||
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
|
||||
const auto hasAccessibilityEventing = screenInfo.HasAccessibilityEventing();
|
||||
auto& textBuffer = screenInfo.GetTextBuffer();
|
||||
size_t numSpaces = 0;
|
||||
|
||||
RowWriteState state{
|
||||
.text = text,
|
||||
@@ -120,8 +118,6 @@ static size_t _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const
|
||||
textBuffer.Write(cursorPosition.y, textBuffer.GetCurrentAttributes(), state);
|
||||
cursorPosition.x = state.columnEnd;
|
||||
|
||||
numSpaces += gsl::narrow_cast<size_t>(state.columnEnd - state.columnBegin);
|
||||
|
||||
if (wrapAtEOL && state.columnEnd >= state.columnLimit)
|
||||
{
|
||||
textBuffer.SetWrapForced(cursorPosition.y, true);
|
||||
@@ -132,50 +128,22 @@ static size_t _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const
|
||||
screenInfo.NotifyAccessibilityEventing(state.columnBegin, cursorPosition.y, state.columnEnd - 1, cursorPosition.y);
|
||||
}
|
||||
|
||||
AdjustCursorPosition(screenInfo, cursorPosition, keepCursorVisible, psScrollY);
|
||||
AdjustCursorPosition(screenInfo, cursorPosition, interactive, psScrollY);
|
||||
}
|
||||
|
||||
return numSpaces;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine writes a string to the screen, processing any embedded
|
||||
// unicode characters. The string is also copied to the input buffer, if
|
||||
// the output mode is line mode.
|
||||
// Arguments:
|
||||
// - screenInfo - reference to screen buffer information structure.
|
||||
// - pwchBufferBackupLimit - Pointer to beginning of buffer.
|
||||
// - pwchBuffer - Pointer to buffer to copy string to. assumed to be at least as long as pwchRealUnicode.
|
||||
// This pointer is updated to point to the next position in the buffer.
|
||||
// - pwchRealUnicode - Pointer to string to write.
|
||||
// - pcb - On input, number of bytes to write. On output, number of bytes written.
|
||||
// - pcSpaces - On output, the number of spaces consumed by the written characters.
|
||||
// - dwFlags -
|
||||
// WC_INTERACTIVE backspace overwrites characters, control characters are expanded (as in, to "^X")
|
||||
// WC_KEEP_CURSOR_VISIBLE change window origin desirable when hit rt. edge
|
||||
// Return Value:
|
||||
// Note:
|
||||
// - This routine does not process tabs and backspace properly. That code will be implemented as part of the line editing services.
|
||||
[[nodiscard]] NTSTATUS WriteCharsLegacy(SCREEN_INFORMATION& screenInfo,
|
||||
_In_range_(<=, pwchBuffer) const wchar_t* const pwchBufferBackupLimit,
|
||||
_In_ const wchar_t* pwchBuffer,
|
||||
_In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode,
|
||||
_Inout_ size_t* const pcb,
|
||||
_Out_opt_ size_t* const pcSpaces,
|
||||
const til::CoordType sOriginalXPosition,
|
||||
const DWORD dwFlags,
|
||||
_Inout_opt_ til::CoordType* const psScrollY)
|
||||
try
|
||||
// This routine writes a string to the screen while handling control characters.
|
||||
// `interactive` exists for COOKED_READ_DATA which uses it to transform control characters into visible text like "^X".
|
||||
// Similarly, `psScrollY` is also used by it to track whether the underlying buffer circled. It requires this information to know where the input line moved to.
|
||||
void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, const bool interactive, til::CoordType* psScrollY)
|
||||
{
|
||||
static constexpr wchar_t tabSpaces[8]{ L' ', L' ', L' ', L' ', L' ', L' ', L' ', L' ' };
|
||||
|
||||
auto& textBuffer = screenInfo.GetTextBuffer();
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
const auto keepCursorVisible = WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE);
|
||||
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
|
||||
auto it = pwchRealUnicode;
|
||||
const auto end = it + *pcb / sizeof(wchar_t);
|
||||
size_t numSpaces = 0;
|
||||
auto it = text.begin();
|
||||
const auto end = text.end();
|
||||
|
||||
// In VT mode, when you have a 120-column terminal you can write 120 columns without the cursor wrapping.
|
||||
// Whenever the cursor is in that 120th column IsDelayedEOLWrap() will return true. I'm not sure why the VT parts
|
||||
@@ -192,7 +160,7 @@ try
|
||||
{
|
||||
pos.x = 0;
|
||||
pos.y++;
|
||||
AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY);
|
||||
AdjustCursorPosition(screenInfo, pos, interactive, psScrollY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +168,7 @@ try
|
||||
// If it's not set, we can just straight up give everything to _writeCharsLegacyUnprocessed.
|
||||
if (WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT))
|
||||
{
|
||||
numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { it, end });
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { it, end }, interactive, psScrollY);
|
||||
it = end;
|
||||
}
|
||||
|
||||
@@ -209,7 +177,7 @@ try
|
||||
const auto nextControlChar = std::find_if(it, end, [](const auto& wch) { return !IS_GLYPH_CHAR(wch); });
|
||||
if (nextControlChar != it)
|
||||
{
|
||||
numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { it, nextControlChar });
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { it, nextControlChar }, interactive, psScrollY);
|
||||
it = nextControlChar;
|
||||
}
|
||||
|
||||
@@ -218,14 +186,14 @@ try
|
||||
switch (*it)
|
||||
{
|
||||
case UNICODE_NULL:
|
||||
if (WI_IsFlagSet(dwFlags, WC_INTERACTIVE))
|
||||
if (interactive)
|
||||
{
|
||||
break;
|
||||
}
|
||||
numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { &tabSpaces[0], 1 });
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, interactive, psScrollY);
|
||||
continue;
|
||||
case UNICODE_BELL:
|
||||
if (WI_IsFlagSet(dwFlags, WC_INTERACTIVE))
|
||||
if (interactive)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -233,171 +201,20 @@ try
|
||||
continue;
|
||||
case UNICODE_BACKSPACE:
|
||||
{
|
||||
// Backspace handling for interactive mode should happen in COOKED_READ_DATA
|
||||
// where it has full control over the text and can delete it directly.
|
||||
// Otherwise handling backspacing tabs/whitespace can turn up complex and bug-prone.
|
||||
assert(!interactive);
|
||||
auto pos = cursor.GetPosition();
|
||||
|
||||
if (WI_IsFlagClear(dwFlags, WC_INTERACTIVE))
|
||||
{
|
||||
pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x);
|
||||
AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto moveUp = [&]() {
|
||||
pos.x = -1;
|
||||
AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY);
|
||||
|
||||
const auto y = cursor.GetPosition().y;
|
||||
auto& row = textBuffer.GetMutableRowByOffset(y);
|
||||
|
||||
pos.x = textBuffer.GetSize().RightExclusive();
|
||||
pos.y = y;
|
||||
|
||||
if (row.WasDoubleBytePadded())
|
||||
{
|
||||
pos.x--;
|
||||
numSpaces--;
|
||||
}
|
||||
|
||||
row.SetWrapForced(false);
|
||||
row.SetDoubleBytePadded(false);
|
||||
};
|
||||
|
||||
// We have to move up early because the tab handling code below needs to be on
|
||||
// the row of the tab already, so that we can call GetText() for precedingText.
|
||||
if (pos.x == 0 && pos.y != 0)
|
||||
{
|
||||
moveUp();
|
||||
}
|
||||
|
||||
til::CoordType glyphCount = 1;
|
||||
|
||||
if (pwchBuffer != pwchBufferBackupLimit)
|
||||
{
|
||||
const auto lastChar = pwchBuffer[-1];
|
||||
|
||||
// Deleting tabs is a bit tricky, because they have a variable width between 1 and 8 spaces,
|
||||
// are stored as whitespace but are technically distinct from whitespace.
|
||||
if (lastChar == UNICODE_TAB)
|
||||
{
|
||||
const auto precedingText = textBuffer.GetRowByOffset(pos.y).GetText(pos.x - 8, pos.x);
|
||||
|
||||
// First, we measure the amount of spaces that precede the cursor in the text buffer,
|
||||
// which is generally the amount of spaces that we end up deleting. We do it this way,
|
||||
// because we don't know what kind of complex mix of wide/narrow glyphs precede the tab.
|
||||
// Basically, by asking the text buffer we get the size information of the preceding text.
|
||||
if (precedingText.size() >= 2 && precedingText.back() == L' ')
|
||||
{
|
||||
auto textIt = precedingText.rbegin() + 1;
|
||||
const auto textEnd = precedingText.rend();
|
||||
|
||||
for (; textIt != textEnd && *textIt == L' '; ++textIt)
|
||||
{
|
||||
glyphCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// But there's a problem: When you print " \t" it should delete 6 spaces and not 8.
|
||||
// In other words, we shouldn't delete any actual preceding whitespaces. We can ask
|
||||
// the "backup" buffer (= preceding text in the commandline) for this information.
|
||||
//
|
||||
// backupEnd points to the character immediately preceding the tab (LastChar).
|
||||
const auto backupEnd = pwchBuffer - 1;
|
||||
// backupLimit points to how far back we need to search. Even if we have 9000 characters in our command line,
|
||||
// we'll only need to check a total of 8 whitespaces. "pwchBuffer - pwchBufferBackupLimit" will
|
||||
// always be at least 1 because that's the \t character in the backup buffer. In other words,
|
||||
// backupLimit will at a minimum be equal to backupEnd, or precede it by 7 more characters.
|
||||
const auto backupLimit = pwchBuffer - std::min<ptrdiff_t>(8, pwchBuffer - pwchBufferBackupLimit);
|
||||
// Now count how many spaces precede the \t character. "backupEnd - backupBeg" will be the amount.
|
||||
auto backupBeg = backupEnd;
|
||||
for (; backupBeg != backupLimit && backupBeg[-1] == L' '; --backupBeg, --glyphCount)
|
||||
{
|
||||
}
|
||||
|
||||
// There's one final problem: A prompt like...
|
||||
// fputs("foo: ", stdout);
|
||||
// fgets(buffer, stdin);
|
||||
// ...has a trailing whitespace in front of our pwchBufferBackupLimit which we should not backspace over.
|
||||
// sOriginalXPosition stores the start of the prompt at the pwchBufferBackupLimit.
|
||||
if (backupBeg == pwchBufferBackupLimit)
|
||||
{
|
||||
glyphCount = pos.x - sOriginalXPosition;
|
||||
}
|
||||
|
||||
// Now that we finally know how many columns precede the cursor we can
|
||||
// subtract the previously determined amount of ' ' from the '\t'.
|
||||
glyphCount -= gsl::narrow_cast<til::CoordType>(backupEnd - backupBeg);
|
||||
|
||||
// Can the above code leave glyphCount <= 0? Let's just not find out!
|
||||
glyphCount = std::max(1, glyphCount);
|
||||
}
|
||||
// Control chars in interactive mode were previously written out
|
||||
// as ^X for instance, so now we also need to delete 2 glyphs.
|
||||
else if (IS_CONTROL_CHAR(lastChar))
|
||||
{
|
||||
glyphCount = 2;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// We've already moved up if the cursor was in the first column so
|
||||
// we need to start off with overwriting the text with whitespace.
|
||||
// It wouldn't make sense to check the cursor position again already.
|
||||
{
|
||||
const auto previousColumn = pos.x;
|
||||
pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(previousColumn);
|
||||
|
||||
RowWriteState state{
|
||||
.text = { &tabSpaces[0], 8 },
|
||||
.columnBegin = pos.x,
|
||||
.columnLimit = previousColumn,
|
||||
};
|
||||
textBuffer.Write(pos.y, textBuffer.GetCurrentAttributes(), state);
|
||||
numSpaces -= previousColumn - pos.x;
|
||||
}
|
||||
|
||||
// The cursor movement logic is a little different for the last iteration, so we exit early here.
|
||||
glyphCount--;
|
||||
if (glyphCount <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, in case we need to delete 2 or more glyphs, we need to ensure we properly wrap lines back up.
|
||||
if (pos.x == 0 && pos.y != 0)
|
||||
{
|
||||
moveUp();
|
||||
}
|
||||
}
|
||||
|
||||
// After the last iteration the cursor might now be in the first column after a line
|
||||
// that was previously padded with a whitespace in the last column due to a wide glyph.
|
||||
// Now that the wide glyph is presumably gone, we can move up a line.
|
||||
if (pos.x == 0 && pos.y != 0 && textBuffer.GetRowByOffset(pos.y - 1).WasDoubleBytePadded())
|
||||
{
|
||||
moveUp();
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY);
|
||||
}
|
||||
|
||||
// Notify accessibility to read the backspaced character.
|
||||
// See GH:12735, MSFT:31748387
|
||||
if (screenInfo.HasAccessibilityEventing())
|
||||
{
|
||||
if (const auto pConsoleWindow = ServiceLocator::LocateConsoleWindow())
|
||||
{
|
||||
LOG_IF_FAILED(pConsoleWindow->SignalUia(UIA_Text_TextChangedEventId));
|
||||
}
|
||||
}
|
||||
pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x);
|
||||
AdjustCursorPosition(screenInfo, pos, interactive, psScrollY);
|
||||
continue;
|
||||
}
|
||||
case UNICODE_TAB:
|
||||
{
|
||||
const auto pos = cursor.GetPosition();
|
||||
const auto tabCount = gsl::narrow_cast<size_t>(NUMBER_OF_SPACES_IN_TAB(pos.x));
|
||||
numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { &tabSpaces[0], tabCount });
|
||||
const auto tabCount = gsl::narrow_cast<size_t>(8 - (pos.x & 7));
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, interactive, psScrollY);
|
||||
continue;
|
||||
}
|
||||
case UNICODE_LINEFEED:
|
||||
@@ -410,24 +227,25 @@ try
|
||||
|
||||
textBuffer.GetMutableRowByOffset(pos.y).SetWrapForced(false);
|
||||
pos.y = pos.y + 1;
|
||||
AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY);
|
||||
AdjustCursorPosition(screenInfo, pos, interactive, psScrollY);
|
||||
continue;
|
||||
}
|
||||
case UNICODE_CARRIAGERETURN:
|
||||
{
|
||||
auto pos = cursor.GetPosition();
|
||||
pos.x = 0;
|
||||
AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY);
|
||||
AdjustCursorPosition(screenInfo, pos, interactive, psScrollY);
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (WI_IsFlagSet(dwFlags, WC_INTERACTIVE) && IS_CONTROL_CHAR(*it))
|
||||
// In the interactive mode we replace C0 control characters (0x00-0x1f) with ASCII representations like ^C (= 0x03).
|
||||
if (interactive && *it < L' ')
|
||||
{
|
||||
const wchar_t wchs[2]{ L'^', static_cast<wchar_t>(*it + L'@') };
|
||||
numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { &wchs[0], 2 });
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { &wchs[0], 2 }, interactive, psScrollY);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -439,76 +257,12 @@ try
|
||||
const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1);
|
||||
if (result == 1)
|
||||
{
|
||||
numSpaces += _writeCharsLegacyUnprocessed(screenInfo, dwFlags, psScrollY, { &wch, 1 });
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, interactive, psScrollY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pcSpaces)
|
||||
{
|
||||
*pcSpaces = numSpaces;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
NT_CATCH_RETURN()
|
||||
|
||||
// Routine Description:
|
||||
// - This routine writes a string to the screen, processing any embedded
|
||||
// unicode characters. The string is also copied to the input buffer, if
|
||||
// the output mode is line mode.
|
||||
// Arguments:
|
||||
// - screenInfo - reference to screen buffer information structure.
|
||||
// - pwchBufferBackupLimit - Pointer to beginning of buffer.
|
||||
// - pwchBuffer - Pointer to buffer to copy string to. assumed to be at least as long as pwchRealUnicode.
|
||||
// This pointer is updated to point to the next position in the buffer.
|
||||
// - pwchRealUnicode - Pointer to string to write.
|
||||
// - pcb - On input, number of bytes to write. On output, number of bytes written.
|
||||
// - pcSpaces - On output, the number of spaces consumed by the written characters.
|
||||
// - dwFlags -
|
||||
// WC_INTERACTIVE backspace overwrites characters, control characters are expanded (as in, to "^X")
|
||||
// WC_KEEP_CURSOR_VISIBLE change window origin (viewport) desirable when hit rt. edge
|
||||
// Return Value:
|
||||
// Note:
|
||||
// - This routine does not process tabs and backspace properly. That code will be implemented as part of the line editing services.
|
||||
[[nodiscard]] NTSTATUS WriteChars(SCREEN_INFORMATION& screenInfo,
|
||||
_In_range_(<=, pwchBuffer) const wchar_t* const pwchBufferBackupLimit,
|
||||
_In_ const wchar_t* pwchBuffer,
|
||||
_In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode,
|
||||
_Inout_ size_t* const pcb,
|
||||
_Out_opt_ size_t* const pcSpaces,
|
||||
const til::CoordType sOriginalXPosition,
|
||||
const DWORD dwFlags,
|
||||
_Inout_opt_ til::CoordType* const psScrollY)
|
||||
try
|
||||
{
|
||||
if (WI_IsAnyFlagClear(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT))
|
||||
{
|
||||
return WriteCharsLegacy(screenInfo,
|
||||
pwchBufferBackupLimit,
|
||||
pwchBuffer,
|
||||
pwchRealUnicode,
|
||||
pcb,
|
||||
pcSpaces,
|
||||
sOriginalXPosition,
|
||||
dwFlags,
|
||||
psScrollY);
|
||||
}
|
||||
|
||||
auto& machine = screenInfo.GetStateMachine();
|
||||
const auto cch = *pcb / sizeof(WCHAR);
|
||||
|
||||
machine.ProcessString({ pwchRealUnicode, cch });
|
||||
|
||||
if (nullptr != pcSpaces)
|
||||
{
|
||||
*pcSpaces = 0;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
NT_CATCH_RETURN()
|
||||
|
||||
// Routine Description:
|
||||
// - Takes the given text and inserts it into the given screen buffer.
|
||||
@@ -530,23 +284,16 @@ NT_CATCH_RETURN()
|
||||
SCREEN_INFORMATION& screenInfo,
|
||||
bool requiresVtQuirk,
|
||||
std::unique_ptr<WriteData>& waiter)
|
||||
try
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING)))
|
||||
{
|
||||
try
|
||||
{
|
||||
waiter = std::make_unique<WriteData>(screenInfo,
|
||||
pwchBuffer,
|
||||
*pcbBuffer,
|
||||
gci.OutputCP,
|
||||
requiresVtQuirk);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
|
||||
}
|
||||
|
||||
waiter = std::make_unique<WriteData>(screenInfo,
|
||||
pwchBuffer,
|
||||
*pcbBuffer,
|
||||
gci.OutputCP,
|
||||
requiresVtQuirk);
|
||||
return CONSOLE_STATUS_WAIT;
|
||||
}
|
||||
|
||||
@@ -563,17 +310,20 @@ NT_CATCH_RETURN()
|
||||
restoreVtQuirk.release();
|
||||
}
|
||||
|
||||
const auto& textBuffer = screenInfo.GetTextBuffer();
|
||||
return WriteChars(screenInfo,
|
||||
pwchBuffer,
|
||||
pwchBuffer,
|
||||
pwchBuffer,
|
||||
pcbBuffer,
|
||||
nullptr,
|
||||
textBuffer.GetCursor().GetPosition().x,
|
||||
0,
|
||||
nullptr);
|
||||
const std::wstring_view str{ pwchBuffer, *pcbBuffer / sizeof(WCHAR) };
|
||||
|
||||
if (WI_IsAnyFlagClear(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT))
|
||||
{
|
||||
WriteCharsLegacy(screenInfo, str, false, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
screenInfo.GetStateMachine().ProcessString(str);
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
NT_CATCH_RETURN()
|
||||
|
||||
// Routine Description:
|
||||
// - This method performs the actual work of attempting to write to the console, converting data types as necessary
|
||||
|
||||
@@ -17,76 +17,13 @@ Revision History:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../server/IWaitRoutine.h"
|
||||
#include "writeData.hpp"
|
||||
|
||||
/*++
|
||||
Routine Description:
|
||||
This routine updates the cursor position. Its input is the non-special
|
||||
cased new location of the cursor. For example, if the cursor were being
|
||||
moved one space backwards from the left edge of the screen, the X
|
||||
coordinate would be -1. This routine would set the X coordinate to
|
||||
the right edge of the screen and decrement the Y coordinate by one.
|
||||
|
||||
Arguments:
|
||||
pScreenInfo - Pointer to screen buffer information structure.
|
||||
coordCursor - New location of cursor.
|
||||
fKeepCursorVisible - TRUE if changing window origin desirable when hit right edge
|
||||
|
||||
Return Value:
|
||||
--*/
|
||||
void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, const BOOL fKeepCursorVisible, _Inout_opt_ til::CoordType* psScrollY);
|
||||
|
||||
/*++
|
||||
Routine Description:
|
||||
This routine writes a string to the screen, processing any embedded
|
||||
unicode characters. The string is also copied to the input buffer, if
|
||||
the output mode is line mode.
|
||||
|
||||
Arguments:
|
||||
ScreenInfo - Pointer to screen buffer information structure.
|
||||
lpBufferBackupLimit - Pointer to beginning of buffer.
|
||||
lpBuffer - Pointer to buffer to copy string to. assumed to be at least
|
||||
as long as lpRealUnicodeString. This pointer is updated to point to the
|
||||
next position in the buffer.
|
||||
lpRealUnicodeString - Pointer to string to write.
|
||||
NumBytes - On input, number of bytes to write. On output, number of
|
||||
bytes written.
|
||||
NumSpaces - On output, the number of spaces consumed by the written characters.
|
||||
dwFlags -
|
||||
WC_INTERACTIVE backspace overwrites characters, control characters are expanded (as in, to "^X")
|
||||
WC_KEEP_CURSOR_VISIBLE change window origin desirable when hit rt. edge
|
||||
|
||||
Return Value:
|
||||
|
||||
Note:
|
||||
This routine does not process tabs and backspace properly. That code
|
||||
will be implemented as part of the line editing services.
|
||||
--*/
|
||||
[[nodiscard]] NTSTATUS WriteCharsLegacy(SCREEN_INFORMATION& screenInfo,
|
||||
_In_range_(<=, pwchBuffer) const wchar_t* const pwchBufferBackupLimit,
|
||||
_In_ const wchar_t* pwchBuffer,
|
||||
_In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode,
|
||||
_Inout_ size_t* const pcb,
|
||||
_Out_opt_ size_t* const pcSpaces,
|
||||
const til::CoordType sOriginalXPosition,
|
||||
const DWORD dwFlags,
|
||||
_Inout_opt_ til::CoordType* const psScrollY);
|
||||
|
||||
// The new entry point for WriteChars to act as an intercept in case we place a Virtual Terminal processor in the way.
|
||||
[[nodiscard]] NTSTATUS WriteChars(SCREEN_INFORMATION& screenInfo,
|
||||
_In_range_(<=, pwchBuffer) const wchar_t* const pwchBufferBackupLimit,
|
||||
_In_ const wchar_t* pwchBuffer,
|
||||
_In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode,
|
||||
_Inout_ size_t* const pcb,
|
||||
_Out_opt_ size_t* const pcSpaces,
|
||||
const til::CoordType sOriginalXPosition,
|
||||
const DWORD dwFlags,
|
||||
_Inout_opt_ til::CoordType* const psScrollY);
|
||||
void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str, bool interactive, til::CoordType* psScrollY);
|
||||
|
||||
// NOTE: console lock must be held when calling this routine
|
||||
// String has been translated to unicode at this point.
|
||||
[[nodiscard]] NTSTATUS DoWriteConsole(_In_reads_bytes_(*pcbBuffer) PCWCHAR pwchBuffer,
|
||||
[[nodiscard]] NTSTATUS DoWriteConsole(_In_reads_bytes_(pcbBuffer) const wchar_t* pwchBuffer,
|
||||
_Inout_ size_t* const pcbBuffer,
|
||||
SCREEN_INFORMATION& screenInfo,
|
||||
bool requiresVtQuirk,
|
||||
|
||||
@@ -817,32 +817,19 @@ void Alias::s_ClearCmdExeAliases()
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Trims trailing \r\n off of a string
|
||||
// Arguments:
|
||||
// - str - String to trim
|
||||
void Alias::s_TrimTrailingCrLf(std::wstring& str)
|
||||
{
|
||||
const auto trailingCrLfPos = str.find_last_of(UNICODE_CARRIAGERETURN);
|
||||
if (std::wstring::npos != trailingCrLfPos)
|
||||
{
|
||||
str.erase(trailingCrLfPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Tokenizes a string into a collection using space as a separator
|
||||
// Arguments:
|
||||
// - str - String to tokenize
|
||||
// Return Value:
|
||||
// - Collection of tokenized strings
|
||||
std::deque<std::wstring> Alias::s_Tokenize(const std::wstring& str)
|
||||
std::deque<std::wstring> Alias::s_Tokenize(const std::wstring_view str)
|
||||
{
|
||||
std::deque<std::wstring> result;
|
||||
|
||||
size_t prevIndex = 0;
|
||||
auto spaceIndex = str.find(L' ');
|
||||
while (std::wstring::npos != spaceIndex)
|
||||
while (std::wstring_view::npos != spaceIndex)
|
||||
{
|
||||
const auto length = spaceIndex - prevIndex;
|
||||
|
||||
@@ -867,11 +854,11 @@ std::deque<std::wstring> Alias::s_Tokenize(const std::wstring& str)
|
||||
// - str - String to split into just args
|
||||
// Return Value:
|
||||
// - Only the arguments part of the string or empty if there are no arguments.
|
||||
std::wstring Alias::s_GetArgString(const std::wstring& str)
|
||||
std::wstring Alias::s_GetArgString(const std::wstring_view str)
|
||||
{
|
||||
std::wstring result;
|
||||
auto firstSpace = str.find_first_of(L' ');
|
||||
if (std::wstring::npos != firstSpace)
|
||||
if (std::wstring_view::npos != firstSpace)
|
||||
{
|
||||
firstSpace++;
|
||||
if (firstSpace < str.size())
|
||||
@@ -1126,16 +1113,8 @@ size_t Alias::s_ReplaceMacros(std::wstring& str,
|
||||
// - If we found a matching alias, this will be the processed data
|
||||
// and lineCount is updated to the new number of lines.
|
||||
// - If we didn't match and process an alias, return an empty string.
|
||||
std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText,
|
||||
const std::wstring& exeName,
|
||||
size_t& lineCount)
|
||||
std::wstring Alias::s_MatchAndCopyAlias(std::wstring_view sourceText, const std::wstring& exeName, size_t& lineCount)
|
||||
{
|
||||
// Copy source text into a local for manipulation.
|
||||
auto sourceCopy = sourceText;
|
||||
|
||||
// Trim trailing \r\n off of sourceCopy if it has one.
|
||||
s_TrimTrailingCrLf(sourceCopy);
|
||||
|
||||
// Check if we have an EXE in the list that matches the request first.
|
||||
auto exeIter = g_aliasData.find(exeName);
|
||||
if (exeIter == g_aliasData.end())
|
||||
@@ -1152,7 +1131,7 @@ std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText,
|
||||
}
|
||||
|
||||
// Tokenize the text by spaces
|
||||
const auto tokens = s_Tokenize(sourceCopy);
|
||||
const auto tokens = s_Tokenize(sourceText);
|
||||
|
||||
// If there are no tokens, return an empty string
|
||||
if (tokens.size() == 0)
|
||||
@@ -1169,14 +1148,14 @@ std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText,
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
const auto target = aliasIter->second;
|
||||
const auto& target = aliasIter->second;
|
||||
if (target.size() == 0)
|
||||
{
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
// Get the string of all parameters as a shorthand for $* later.
|
||||
const auto allParams = s_GetArgString(sourceCopy);
|
||||
const auto allParams = s_GetArgString(sourceText);
|
||||
|
||||
// The final text will be the target but with macros replaced.
|
||||
auto finalText = target;
|
||||
@@ -1185,59 +1164,6 @@ std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText,
|
||||
return finalText;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine matches the input string with an alias and copies the alias to the input buffer.
|
||||
// Arguments:
|
||||
// - pwchSource - string to match
|
||||
// - cbSource - length of pwchSource in bytes
|
||||
// - pwchTarget - where to store matched string
|
||||
// - cbTargetSize - on input, contains size of pwchTarget.
|
||||
// - cbTargetWritten - On output, contains length of alias stored in pwchTarget.
|
||||
// - pwchExe - Name of exe that command is associated with to find related aliases
|
||||
// - cbExe - Length in bytes of exe name
|
||||
// - LineCount - aliases can contain multiple commands. $T is the command separator
|
||||
// Return Value:
|
||||
// - None. It will just maintain the source as the target if we can't match an alias.
|
||||
void Alias::s_MatchAndCopyAliasLegacy(_In_reads_bytes_(cbSource) PCWCH pwchSource,
|
||||
_In_ size_t cbSource,
|
||||
_Out_writes_bytes_(cbTargetWritten) PWCHAR pwchTarget,
|
||||
_In_ const size_t cbTargetSize,
|
||||
size_t& cbTargetWritten,
|
||||
const std::wstring& exeName,
|
||||
DWORD& lines)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::wstring sourceText(pwchSource, cbSource / sizeof(WCHAR));
|
||||
size_t lineCount = lines;
|
||||
|
||||
const auto targetText = s_MatchAndCopyAlias(sourceText, exeName, lineCount);
|
||||
|
||||
// Only return data if the reply was non-empty (we had a match).
|
||||
if (!targetText.empty())
|
||||
{
|
||||
const auto cchTargetSize = cbTargetSize / sizeof(wchar_t);
|
||||
|
||||
// If the target text will fit in the result buffer, fill out the results.
|
||||
if (targetText.size() <= cchTargetSize)
|
||||
{
|
||||
// Non-null terminated copy into memory space
|
||||
std::copy_n(targetText.data(), targetText.size(), pwchTarget);
|
||||
|
||||
// Return bytes copied.
|
||||
cbTargetWritten = gsl::narrow<ULONG>(targetText.size() * sizeof(wchar_t));
|
||||
|
||||
// Return lines info.
|
||||
lines = gsl::narrow<DWORD>(lineCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
void Alias::s_TestAddAlias(std::wstring& exe,
|
||||
std::wstring& alias,
|
||||
|
||||
@@ -16,22 +16,11 @@ class Alias
|
||||
public:
|
||||
static void s_ClearCmdExeAliases();
|
||||
|
||||
static void s_MatchAndCopyAliasLegacy(_In_reads_bytes_(cbSource) PCWCH pwchSource,
|
||||
_In_ size_t cbSource,
|
||||
_Out_writes_bytes_(cbTargetWritten) PWCHAR pwchTarget,
|
||||
_In_ const size_t cbTargetSize,
|
||||
size_t& cbTargetWritten,
|
||||
const std::wstring& exeName,
|
||||
DWORD& lines);
|
||||
|
||||
static std::wstring s_MatchAndCopyAlias(const std::wstring& sourceText,
|
||||
const std::wstring& exeName,
|
||||
size_t& lineCount);
|
||||
static std::wstring s_MatchAndCopyAlias(std::wstring_view sourceText, const std::wstring& exeName, size_t& lineCount);
|
||||
|
||||
private:
|
||||
static void s_TrimTrailingCrLf(std::wstring& str);
|
||||
static std::deque<std::wstring> s_Tokenize(const std::wstring& str);
|
||||
static std::wstring s_GetArgString(const std::wstring& str);
|
||||
static std::deque<std::wstring> s_Tokenize(const std::wstring_view str);
|
||||
static std::wstring s_GetArgString(const std::wstring_view str);
|
||||
static size_t s_ReplaceMacros(std::wstring& str,
|
||||
const std::deque<std::wstring>& tokens,
|
||||
const std::wstring& fullArgString);
|
||||
|
||||
1245
src/host/cmdline.cpp
1245
src/host/cmdline.cpp
File diff suppressed because it is too large
Load Diff
@@ -3,90 +3,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "input.h"
|
||||
#include "screenInfo.hpp"
|
||||
#include "server.h"
|
||||
|
||||
#include "history.h"
|
||||
#include "alias.h"
|
||||
#include "readDataCooked.hpp"
|
||||
#include "popup.h"
|
||||
|
||||
class CommandLine
|
||||
{
|
||||
public:
|
||||
~CommandLine();
|
||||
|
||||
static CommandLine& Instance();
|
||||
|
||||
static bool IsEditLineEmpty();
|
||||
void Hide(const bool fUpdateFields);
|
||||
void Show();
|
||||
bool IsVisible() const noexcept;
|
||||
|
||||
[[nodiscard]] NTSTATUS ProcessCommandLine(COOKED_READ_DATA& cookedReadData,
|
||||
_In_ WCHAR wch,
|
||||
const DWORD dwKeyState);
|
||||
|
||||
[[nodiscard]] HRESULT StartCommandNumberPopup(COOKED_READ_DATA& cookedReadData);
|
||||
|
||||
bool HasPopup() const noexcept;
|
||||
Popup& GetPopup() const;
|
||||
|
||||
void EndCurrentPopup();
|
||||
void EndAllPopups();
|
||||
|
||||
void DeletePromptAfterCursor(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
til::point DeleteFromRightOfCursor(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
|
||||
protected:
|
||||
CommandLine();
|
||||
|
||||
// delete these because we don't want to accidentally get copies of the singleton
|
||||
CommandLine(const CommandLine&) = delete;
|
||||
CommandLine& operator=(const CommandLine&) = delete;
|
||||
|
||||
[[nodiscard]] NTSTATUS _startCommandListPopup(COOKED_READ_DATA& cookedReadData);
|
||||
[[nodiscard]] NTSTATUS _startCopyFromCharPopup(COOKED_READ_DATA& cookedReadData);
|
||||
[[nodiscard]] NTSTATUS _startCopyToCharPopup(COOKED_READ_DATA& cookedReadData);
|
||||
|
||||
void _processHistoryCycling(COOKED_READ_DATA& cookedReadData, const CommandHistory::SearchDirection searchDirection);
|
||||
void _setPromptToOldestCommand(COOKED_READ_DATA& cookedReadData);
|
||||
void _setPromptToNewestCommand(COOKED_READ_DATA& cookedReadData);
|
||||
til::point _deletePromptBeforeCursor(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
til::point _moveCursorToEndOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
til::point _moveCursorToStartOfPrompt(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
til::point _moveCursorLeftByWord(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
til::point _moveCursorLeft(COOKED_READ_DATA& cookedReadData);
|
||||
til::point _moveCursorRightByWord(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
til::point _moveCursorRight(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
void _insertCtrlZ(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
void _deleteCommandHistory(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
void _fillPromptWithPreviousCommandFragment(COOKED_READ_DATA& cookedReadData) noexcept;
|
||||
til::point _cycleMatchingCommandHistoryToPrompt(COOKED_READ_DATA& cookedReadData);
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class CommandLineTests;
|
||||
friend class CommandNumberPopupTests;
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::deque<std::unique_ptr<Popup>> _popups;
|
||||
bool _isVisible;
|
||||
};
|
||||
|
||||
void DeleteCommandLine(COOKED_READ_DATA& cookedReadData, const bool fUpdateFields);
|
||||
|
||||
void RedrawCommandLine(COOKED_READ_DATA& cookedReadData);
|
||||
|
||||
// Values for WriteChars(), WriteCharsLegacy() dwFlags
|
||||
#define WC_INTERACTIVE 0x01
|
||||
#define WC_KEEP_CURSOR_VISIBLE 0x02
|
||||
|
||||
// Word delimiters
|
||||
bool IsWordDelim(const wchar_t wch);
|
||||
bool IsWordDelim(const std::wstring_view charData);
|
||||
|
||||
bool IsValidStringBuffer(_In_ bool Unicode, _In_reads_bytes_(Size) PVOID Buffer, _In_ ULONG Size, _In_ ULONG Count, ...);
|
||||
|
||||
void SetCurrentCommandLine(COOKED_READ_DATA& cookedReadData, _In_ CommandHistory::Index Index);
|
||||
bool IsWordDelim(wchar_t wch) noexcept;
|
||||
bool IsWordDelim(const std::wstring_view& charData) noexcept;
|
||||
int DelimiterClass(wchar_t wch) noexcept;
|
||||
|
||||
@@ -130,6 +130,11 @@ bool CONSOLE_INFORMATION::HasPendingCookedRead() const noexcept
|
||||
return _cookedReadData != nullptr;
|
||||
}
|
||||
|
||||
bool CONSOLE_INFORMATION::HasPendingPopup() const noexcept
|
||||
{
|
||||
return _cookedReadData && _cookedReadData->PresentingPopup();
|
||||
}
|
||||
|
||||
const COOKED_READ_DATA& CONSOLE_INFORMATION::CookedReadData() const noexcept
|
||||
{
|
||||
return *_cookedReadData;
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include <til/u8u16convert.h>
|
||||
|
||||
#include "../ConsoleArguments.hpp"
|
||||
#include "../srvinit.h"
|
||||
#include "../../server/Entrypoints.h"
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../../server/DeviceHandle.h"
|
||||
#include "../../server/IoThread.h"
|
||||
#include "../_stream.h"
|
||||
#include "../getset.h"
|
||||
#include <til/u8u16convert.h>
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../../server/IoThread.h"
|
||||
|
||||
struct NullDeviceComm : public IDeviceComm
|
||||
{
|
||||
@@ -132,17 +130,8 @@ extern "C" __declspec(dllexport) int LLVMFuzzerTestOneInput(const uint8_t* data,
|
||||
|
||||
const auto u16String{ til::u8u16(std::string_view{ reinterpret_cast<const char*>(data), size }) };
|
||||
til::CoordType scrollY{};
|
||||
auto sizeInBytes{ u16String.size() * 2 };
|
||||
gci.LockConsole();
|
||||
auto u = wil::scope_exit([&]() { gci.UnlockConsole(); });
|
||||
(void)WriteCharsLegacy(gci.GetActiveOutputBuffer(),
|
||||
u16String.data(),
|
||||
u16String.data(),
|
||||
u16String.data(),
|
||||
&sizeInBytes,
|
||||
nullptr,
|
||||
0,
|
||||
WC_INTERACTIVE | WC_KEEP_CURSOR_VISIBLE,
|
||||
&scrollY);
|
||||
WriteCharsLegacy(gci.GetActiveOutputBuffer(), u16String, true, &scrollY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -598,13 +598,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
const auto requestedBufferSize = til::wrap_coord_size(data.dwSize);
|
||||
if (requestedBufferSize != coordScreenBufferSize)
|
||||
{
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
|
||||
commandLine.Hide(FALSE);
|
||||
|
||||
LOG_IF_FAILED(context.ResizeScreenBuffer(requestedBufferSize, TRUE));
|
||||
|
||||
commandLine.Show();
|
||||
}
|
||||
const auto newBufferSize = context.GetBufferSize().Dimensions();
|
||||
|
||||
@@ -655,15 +649,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
if (NewSize.width != context.GetViewport().Width() ||
|
||||
NewSize.height != context.GetViewport().Height())
|
||||
{
|
||||
// GH#1856 - make sure to hide the commandline _before_ we execute
|
||||
// the resize, and the re-display it after the resize. If we leave
|
||||
// it displayed, we'll crash during the resize when we try to figure
|
||||
// out if the bounds of the old commandline fit within the new
|
||||
// window (it might not).
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine.Hide(FALSE);
|
||||
context.SetViewportSize(&NewSize);
|
||||
commandLine.Show();
|
||||
|
||||
const auto pWindow = ServiceLocator::LocateConsoleWindow();
|
||||
if (pWindow != nullptr)
|
||||
|
||||
@@ -167,47 +167,9 @@ const std::vector<std::wstring>& CommandHistory::GetCommands() const noexcept
|
||||
return _commands;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT CommandHistory::RetrieveNth(const Index index, std::span<wchar_t> buffer, size_t& commandSize)
|
||||
std::wstring_view CommandHistory::Retrieve(const SearchDirection searchDirection)
|
||||
{
|
||||
LastDisplayed = index;
|
||||
|
||||
try
|
||||
{
|
||||
const auto& cmd = _commands.at(index);
|
||||
if (cmd.size() > buffer.size())
|
||||
{
|
||||
commandSize = buffer.size(); // room for CRLF?
|
||||
}
|
||||
else
|
||||
{
|
||||
commandSize = cmd.size();
|
||||
}
|
||||
|
||||
std::copy_n(cmd.cbegin(), commandSize, buffer.begin());
|
||||
|
||||
commandSize *= sizeof(wchar_t);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT CommandHistory::Retrieve(const SearchDirection searchDirection,
|
||||
const std::span<wchar_t> buffer,
|
||||
size_t& commandSize)
|
||||
{
|
||||
FAIL_FAST_IF(!(WI_IsFlagSet(Flags, CLE_ALLOCATED)));
|
||||
|
||||
if (_commands.size() == 0)
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
if (_commands.size() == 1)
|
||||
{
|
||||
LastDisplayed = 0;
|
||||
}
|
||||
else if (searchDirection == SearchDirection::Previous)
|
||||
if (searchDirection == SearchDirection::Previous)
|
||||
{
|
||||
// if this is the first time for this read that a command has
|
||||
// been retrieved, return the current command. otherwise, return
|
||||
@@ -218,15 +180,27 @@ const std::vector<std::wstring>& CommandHistory::GetCommands() const noexcept
|
||||
}
|
||||
else
|
||||
{
|
||||
_Prev(LastDisplayed);
|
||||
LastDisplayed--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_Next(LastDisplayed);
|
||||
LastDisplayed++;
|
||||
}
|
||||
|
||||
return RetrieveNth(LastDisplayed, buffer, commandSize);
|
||||
return RetrieveNth(LastDisplayed);
|
||||
}
|
||||
|
||||
std::wstring_view CommandHistory::RetrieveNth(Index index)
|
||||
{
|
||||
if (_commands.empty())
|
||||
{
|
||||
LastDisplayed = 0;
|
||||
return {};
|
||||
}
|
||||
|
||||
LastDisplayed = std::clamp(index, 0, GetNumberOfCommands() - 1);
|
||||
return _commands.at(LastDisplayed);
|
||||
}
|
||||
|
||||
std::wstring_view CommandHistory::GetLastCommand() const
|
||||
|
||||
@@ -43,13 +43,8 @@ public:
|
||||
[[nodiscard]] HRESULT Add(const std::wstring_view command,
|
||||
const bool suppressDuplicates);
|
||||
|
||||
[[nodiscard]] HRESULT Retrieve(const SearchDirection searchDirection,
|
||||
const std::span<wchar_t> buffer,
|
||||
size_t& commandSize);
|
||||
|
||||
[[nodiscard]] HRESULT RetrieveNth(const Index index,
|
||||
const std::span<wchar_t> buffer,
|
||||
size_t& commandSize);
|
||||
std::wstring_view Retrieve(const SearchDirection searchDirection);
|
||||
std::wstring_view RetrieveNth(Index index);
|
||||
|
||||
Index GetNumberOfCommands() const;
|
||||
std::wstring_view GetNth(Index index) const;
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\alias.cpp" />
|
||||
<ClCompile Include="..\cmdline.cpp" />
|
||||
<ClCompile Include="..\CommandNumberPopup.cpp" />
|
||||
<ClCompile Include="..\CommandListPopup.cpp" />
|
||||
<ClCompile Include="..\CopyFromCharPopup.cpp" />
|
||||
<ClCompile Include="..\CopyToCharPopup.cpp" />
|
||||
<ClCompile Include="..\ConsoleArguments.cpp" />
|
||||
<ClCompile Include="..\CursorBlinker.cpp" />
|
||||
<ClCompile Include="..\readDataCooked.cpp" />
|
||||
@@ -29,7 +25,6 @@
|
||||
<ClCompile Include="..\ntprivapi.cpp" />
|
||||
<ClCompile Include="..\output.cpp" />
|
||||
<ClCompile Include="..\outputStream.cpp" />
|
||||
<ClCompile Include="..\popup.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -64,10 +59,6 @@
|
||||
<ClInclude Include="..\alias.h" />
|
||||
<ClInclude Include="..\ApiRoutines.h" />
|
||||
<ClInclude Include="..\cmdline.h" />
|
||||
<ClInclude Include="..\CommandNumberPopup.hpp" />
|
||||
<ClInclude Include="..\CommandListPopup.hpp" />
|
||||
<ClInclude Include="..\CopyFromCharPopup.hpp" />
|
||||
<ClInclude Include="..\CopyToCharPopup.hpp" />
|
||||
<ClInclude Include="..\conapi.h" />
|
||||
<ClInclude Include="..\conddkrefs.h" />
|
||||
<ClInclude Include="..\conareainfo.h" />
|
||||
@@ -90,7 +81,6 @@
|
||||
<ClInclude Include="..\ntprivapi.hpp" />
|
||||
<ClInclude Include="..\output.h" />
|
||||
<ClInclude Include="..\outputStream.hpp" />
|
||||
<ClInclude Include="..\popup.h" />
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
<ClInclude Include="..\readData.hpp" />
|
||||
<ClInclude Include="..\readDataCooked.hpp" />
|
||||
|
||||
@@ -119,7 +119,7 @@ void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak)
|
||||
if (keyEvent.wVirtualKeyCode == 'C' && IsInProcessedInputMode())
|
||||
{
|
||||
HandleCtrlEvent(CTRL_C_EVENT);
|
||||
if (gci.PopupCount == 0)
|
||||
if (!gci.HasPendingPopup())
|
||||
{
|
||||
gci.pInputBuffer->TerminateRead(WaitTerminationReason::CtrlC);
|
||||
}
|
||||
@@ -135,7 +135,7 @@ void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak)
|
||||
{
|
||||
gci.pInputBuffer->Flush();
|
||||
HandleCtrlEvent(CTRL_BREAK_EVENT);
|
||||
if (gci.PopupCount == 0)
|
||||
if (!gci.HasPendingPopup())
|
||||
{
|
||||
gci.pInputBuffer->TerminateRead(WaitTerminationReason::CtrlBreak);
|
||||
}
|
||||
|
||||
@@ -58,11 +58,6 @@ private:
|
||||
ULONG _ulControlKeyState;
|
||||
};
|
||||
|
||||
#define TAB_SIZE 8
|
||||
#define TAB_MASK (TAB_SIZE - 1)
|
||||
// WHY IS THIS NOT POSITION % TAB_SIZE?!
|
||||
#define NUMBER_OF_SPACES_IN_TAB(POSITION) (TAB_SIZE - ((POSITION)&TAB_MASK))
|
||||
|
||||
// these values are related to GetKeyboardState
|
||||
#define KEY_PRESSED 0x8000
|
||||
#define KEY_TOGGLED 0x01
|
||||
|
||||
@@ -159,21 +159,6 @@
|
||||
<ClCompile Include="..\conareainfo.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\popup.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\CommandNumberPopup.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\CommandListPopup.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\CopyFromCharPopup.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\CopyToCharPopup.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\CursorBlinker.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -329,21 +314,6 @@
|
||||
<ClInclude Include="..\conareainfo.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\popup.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\CommandNumberPopup.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\CommandListPopup.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\CopyFromCharPopup.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\CopyToCharPopup.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\CursorBlinker.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -47,140 +47,6 @@ void SetConsoleCPInfo(const BOOL fOutput)
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine check bisected on Unicode string end.
|
||||
// Arguments:
|
||||
// - pwchBuffer - Pointer to Unicode string buffer.
|
||||
// - cWords - Number of Unicode string.
|
||||
// - cBytes - Number of bisect position by byte counts.
|
||||
// Return Value:
|
||||
// - TRUE - Bisected character.
|
||||
// - FALSE - Correctly.
|
||||
BOOL CheckBisectStringW(_In_reads_bytes_(cBytes) const WCHAR* pwchBuffer,
|
||||
_In_ size_t cWords,
|
||||
_In_ size_t cBytes) noexcept
|
||||
{
|
||||
while (cWords && cBytes)
|
||||
{
|
||||
if (IsGlyphFullWidth(*pwchBuffer))
|
||||
{
|
||||
if (cBytes < 2)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
cWords--;
|
||||
cBytes -= 2;
|
||||
pwchBuffer++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cWords--;
|
||||
cBytes--;
|
||||
pwchBuffer++;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine check bisected on Unicode string end.
|
||||
// Arguments:
|
||||
// - ScreenInfo - reference to screen information structure.
|
||||
// - pwchBuffer - Pointer to Unicode string buffer.
|
||||
// - cWords - Number of Unicode string.
|
||||
// - cBytes - Number of bisect position by byte counts.
|
||||
// - fPrintableControlChars - TRUE if control characters are being expanded (to ^X)
|
||||
// Return Value:
|
||||
// - TRUE - Bisected character.
|
||||
// - FALSE - Correctly.
|
||||
BOOL CheckBisectProcessW(const SCREEN_INFORMATION& ScreenInfo,
|
||||
_In_reads_bytes_(cBytes) const WCHAR* pwchBuffer,
|
||||
_In_ size_t cWords,
|
||||
_In_ size_t cBytes,
|
||||
_In_ til::CoordType sOriginalXPosition,
|
||||
_In_ BOOL fPrintableControlChars)
|
||||
{
|
||||
if (WI_IsFlagSet(ScreenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT))
|
||||
{
|
||||
while (cWords && cBytes)
|
||||
{
|
||||
const auto Char = *pwchBuffer;
|
||||
if (Char >= UNICODE_SPACE)
|
||||
{
|
||||
if (IsGlyphFullWidth(Char))
|
||||
{
|
||||
if (cBytes < 2)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
cWords--;
|
||||
cBytes -= 2;
|
||||
pwchBuffer++;
|
||||
sOriginalXPosition += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cWords--;
|
||||
cBytes--;
|
||||
pwchBuffer++;
|
||||
sOriginalXPosition++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cWords--;
|
||||
pwchBuffer++;
|
||||
switch (Char)
|
||||
{
|
||||
case UNICODE_BELL:
|
||||
if (fPrintableControlChars)
|
||||
goto CtrlChar;
|
||||
break;
|
||||
case UNICODE_BACKSPACE:
|
||||
case UNICODE_LINEFEED:
|
||||
case UNICODE_CARRIAGERETURN:
|
||||
break;
|
||||
case UNICODE_TAB:
|
||||
{
|
||||
size_t TabSize = NUMBER_OF_SPACES_IN_TAB(sOriginalXPosition);
|
||||
sOriginalXPosition = (til::CoordType)(sOriginalXPosition + TabSize);
|
||||
if (cBytes < TabSize)
|
||||
return TRUE;
|
||||
cBytes -= TabSize;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (fPrintableControlChars)
|
||||
{
|
||||
CtrlChar:
|
||||
if (cBytes < 2)
|
||||
return TRUE;
|
||||
cBytes -= 2;
|
||||
sOriginalXPosition += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
cBytes--;
|
||||
sOriginalXPosition++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CheckBisectStringW(pwchBuffer, cWords, cBytes);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Converts unicode characters to ANSI given a destination codepage
|
||||
// Arguments:
|
||||
@@ -205,18 +71,6 @@ int ConvertToOem(const UINT uiCodePage,
|
||||
return LOG_IF_WIN32_BOOL_FALSE(WideCharToMultiByte(uiCodePage, 0, pwchSource, cchSource, pchTarget, cchTarget, nullptr, nullptr));
|
||||
}
|
||||
|
||||
// Data in the output buffer is the true unicode value.
|
||||
int ConvertInputToUnicode(const UINT uiCodePage,
|
||||
_In_reads_(cchSource) const CHAR* const pchSource,
|
||||
const UINT cchSource,
|
||||
_Out_writes_(cchTarget) WCHAR* const pwchTarget,
|
||||
const UINT cchTarget) noexcept
|
||||
{
|
||||
DBGCHARS(("ConvertInputToUnicode %d->U %.*s\n", uiCodePage, cchSource > 10 ? 10 : cchSource, pchSource));
|
||||
|
||||
return MultiByteToWideChar(uiCodePage, 0, pchSource, cchSource, pwchTarget, cchTarget);
|
||||
}
|
||||
|
||||
// Output data is always translated via the ansi codepage so glyph translation works.
|
||||
int ConvertOutputToUnicode(_In_ UINT uiCodePage,
|
||||
_In_reads_(cchSource) const CHAR* const pchSource,
|
||||
@@ -226,46 +80,6 @@ int ConvertOutputToUnicode(_In_ UINT uiCodePage,
|
||||
{
|
||||
FAIL_FAST_IF(!(cchTarget > 0));
|
||||
pwchTarget[0] = L'\0';
|
||||
|
||||
DBGCHARS(("ConvertOutputToUnicode %d->U %.*s\n", uiCodePage, cchSource > 10 ? 10 : cchSource, pchSource));
|
||||
|
||||
if (DoBuffersOverlap(reinterpret_cast<const BYTE* const>(pchSource),
|
||||
cchSource * sizeof(CHAR),
|
||||
reinterpret_cast<const BYTE* const>(pwchTarget),
|
||||
cchTarget * sizeof(WCHAR)))
|
||||
{
|
||||
try
|
||||
{
|
||||
// buffers overlap so we need to copy one
|
||||
std::string copyData(pchSource, cchSource);
|
||||
return MultiByteToWideChar(uiCodePage, MB_USEGLYPHCHARS, copyData.data(), cchSource, pwchTarget, cchTarget);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return MultiByteToWideChar(uiCodePage, MB_USEGLYPHCHARS, pchSource, cchSource, pwchTarget, cchTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - checks if two buffers overlap
|
||||
// Arguments:
|
||||
// - pBufferA - pointer to start of first buffer
|
||||
// - cbBufferA - size of first buffer, in bytes
|
||||
// - pBufferB - pointer to start of second buffer
|
||||
// - cbBufferB - size of second buffer, in bytes
|
||||
// Return Value:
|
||||
// - true if buffers overlap, false otherwise
|
||||
bool DoBuffersOverlap(const BYTE* const pBufferA,
|
||||
const UINT cbBufferA,
|
||||
const BYTE* const pBufferB,
|
||||
const UINT cbBufferB) noexcept
|
||||
{
|
||||
const auto pBufferAEnd = pBufferA + cbBufferA;
|
||||
const auto pBufferBEnd = pBufferB + cbBufferB;
|
||||
return (pBufferA <= pBufferB && pBufferAEnd >= pBufferB) || (pBufferB <= pBufferA && pBufferBEnd >= pBufferA);
|
||||
return MultiByteToWideChar(uiCodePage, MB_USEGLYPHCHARS, pchSource, cchSource, pwchTarget, cchTarget);
|
||||
}
|
||||
|
||||
@@ -28,35 +28,14 @@ WCHAR CharToWchar(_In_reads_(cch) const char* const pch, const UINT cch);
|
||||
|
||||
void SetConsoleCPInfo(const BOOL fOutput);
|
||||
|
||||
BOOL CheckBisectStringW(_In_reads_bytes_(cBytes) const WCHAR* pwchBuffer,
|
||||
_In_ size_t cWords,
|
||||
_In_ size_t cBytes) noexcept;
|
||||
BOOL CheckBisectProcessW(const SCREEN_INFORMATION& ScreenInfo,
|
||||
_In_reads_bytes_(cBytes) const WCHAR* pwchBuffer,
|
||||
_In_ size_t cWords,
|
||||
_In_ size_t cBytes,
|
||||
_In_ til::CoordType sOriginalXPosition,
|
||||
_In_ BOOL fPrintableControlChars);
|
||||
|
||||
int ConvertToOem(const UINT uiCodePage,
|
||||
_In_reads_(cchSource) const WCHAR* const pwchSource,
|
||||
const UINT cchSource,
|
||||
_Out_writes_(cchTarget) CHAR* const pchTarget,
|
||||
const UINT cchTarget) noexcept;
|
||||
|
||||
int ConvertInputToUnicode(const UINT uiCodePage,
|
||||
_In_reads_(cchSource) const CHAR* const pchSource,
|
||||
const UINT cchSource,
|
||||
_Out_writes_(cchTarget) WCHAR* const pwchTarget,
|
||||
const UINT cchTarget) noexcept;
|
||||
|
||||
int ConvertOutputToUnicode(_In_ UINT uiCodePage,
|
||||
_In_reads_(cchSource) const CHAR* const pchSource,
|
||||
_In_ UINT cchSource,
|
||||
_Out_writes_(cchTarget) WCHAR* pwchTarget,
|
||||
_In_ UINT cchTarget) noexcept;
|
||||
|
||||
bool DoBuffersOverlap(const BYTE* const pBufferA,
|
||||
const UINT cbBufferA,
|
||||
const BYTE* const pBufferB,
|
||||
const UINT cbBufferB) noexcept;
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "popup.h"
|
||||
|
||||
#include "_output.h"
|
||||
#include "output.h"
|
||||
|
||||
#include "dbcs.h"
|
||||
#include "srvinit.h"
|
||||
#include "stream.h"
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
using namespace Microsoft::Console::Types;
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
// Routine Description:
|
||||
// - Creates an object representing an interactive popup overlay during cooked mode command line editing.
|
||||
// - NOTE: Modifies global popup count (and adjusts cursor visibility as appropriate.)
|
||||
// Arguments:
|
||||
// - screenInfo - Reference to screen on which the popup should be drawn/overlaid.
|
||||
// - proposedSize - Suggested size of the popup. May be adjusted based on screen size.
|
||||
Popup::Popup(SCREEN_INFORMATION& screenInfo, const til::size proposedSize) :
|
||||
_screenInfo(screenInfo),
|
||||
_userInputFunction(&Popup::_getUserInputInternal)
|
||||
{
|
||||
_attributes = screenInfo.GetPopupAttributes();
|
||||
|
||||
const auto size = _CalculateSize(screenInfo, proposedSize);
|
||||
const auto origin = _CalculateOrigin(screenInfo, size);
|
||||
|
||||
_region.left = origin.x;
|
||||
_region.top = origin.y;
|
||||
_region.right = origin.x + size.width - 1;
|
||||
_region.bottom = origin.y + size.height - 1;
|
||||
|
||||
_oldScreenSize = screenInfo.GetBufferSize().Dimensions();
|
||||
|
||||
til::inclusive_rect TargetRect;
|
||||
TargetRect.left = 0;
|
||||
TargetRect.top = _region.top;
|
||||
TargetRect.right = _oldScreenSize.width - 1;
|
||||
TargetRect.bottom = _region.bottom;
|
||||
|
||||
// copy the data into the backup buffer
|
||||
_oldContents = std::move(screenInfo.ReadRect(Viewport::FromInclusive(TargetRect)));
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto countWas = gci.PopupCount.fetch_add(1ui16);
|
||||
if (0 == countWas)
|
||||
{
|
||||
// If this is the first popup to be shown, stop the cursor from appearing/blinking
|
||||
screenInfo.GetTextBuffer().GetCursor().SetIsPopupShown(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Cleans up a popup object
|
||||
// - NOTE: Modifies global popup count (and adjusts cursor visibility as appropriate.)
|
||||
Popup::~Popup()
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto countWas = gci.PopupCount.fetch_sub(1);
|
||||
if (1 == countWas)
|
||||
{
|
||||
// Notify we're done showing popups.
|
||||
gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().SetIsPopupShown(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Popup::Draw()
|
||||
{
|
||||
_DrawBorder();
|
||||
_DrawContent();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Draws the outlines of the popup area in the screen buffer
|
||||
void Popup::_DrawBorder()
|
||||
{
|
||||
// fill attributes of top line
|
||||
til::point WriteCoord;
|
||||
WriteCoord.x = _region.left;
|
||||
WriteCoord.y = _region.top;
|
||||
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
|
||||
|
||||
// draw upper left corner
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT, 1), WriteCoord);
|
||||
|
||||
// draw upper bar
|
||||
WriteCoord.x += 1;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord);
|
||||
|
||||
// draw upper right corner
|
||||
WriteCoord.x = _region.right;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_LEFT, 1), WriteCoord);
|
||||
|
||||
for (til::CoordType i = 0; i < Height(); i++)
|
||||
{
|
||||
WriteCoord.y += 1;
|
||||
WriteCoord.x = _region.left;
|
||||
|
||||
// fill attributes
|
||||
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
|
||||
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord);
|
||||
|
||||
WriteCoord.x = _region.right;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord);
|
||||
}
|
||||
|
||||
// Draw bottom line.
|
||||
// Fill attributes of top line.
|
||||
WriteCoord.x = _region.left;
|
||||
WriteCoord.y = _region.bottom;
|
||||
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
|
||||
|
||||
// Draw bottom left corner.
|
||||
WriteCoord.x = _region.left;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_RIGHT, 1), WriteCoord);
|
||||
|
||||
// Draw lower bar.
|
||||
WriteCoord.x += 1;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord);
|
||||
|
||||
// draw lower right corner
|
||||
WriteCoord.x = _region.right;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_LEFT, 1), WriteCoord);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Draws prompt information in the popup area to tell the user what to enter.
|
||||
// Arguments:
|
||||
// - id - Resource ID for string to display to user
|
||||
void Popup::_DrawPrompt(const UINT id)
|
||||
{
|
||||
auto text = _LoadString(id);
|
||||
|
||||
// Draw empty popup.
|
||||
til::point WriteCoord;
|
||||
WriteCoord.x = _region.left + 1;
|
||||
WriteCoord.y = _region.top + 1;
|
||||
auto lStringLength = Width();
|
||||
for (til::CoordType i = 0; i < Height(); i++)
|
||||
{
|
||||
const OutputCellIterator it(UNICODE_SPACE, _attributes, lStringLength);
|
||||
const auto done = _screenInfo.Write(it, WriteCoord);
|
||||
lStringLength = done.GetCellDistance(it);
|
||||
|
||||
WriteCoord.y += 1;
|
||||
}
|
||||
|
||||
WriteCoord.x = _region.left + 1;
|
||||
WriteCoord.y = _region.top + 1;
|
||||
|
||||
// write prompt to screen
|
||||
lStringLength = gsl::narrow<til::CoordType>(text.size());
|
||||
if (lStringLength > Width())
|
||||
{
|
||||
text = text.substr(0, Width());
|
||||
}
|
||||
|
||||
size_t used;
|
||||
LOG_IF_FAILED(ServiceLocator::LocateGlobals().api->WriteConsoleOutputCharacterWImpl(_screenInfo,
|
||||
text,
|
||||
WriteCoord,
|
||||
used));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Cleans up a popup by restoring the stored buffer information to the region of
|
||||
// the screen that the popup was covering and frees resources.
|
||||
void Popup::End()
|
||||
{
|
||||
// restore previous contents to screen
|
||||
|
||||
til::inclusive_rect SourceRect;
|
||||
SourceRect.left = 0;
|
||||
SourceRect.top = _region.top;
|
||||
SourceRect.right = _oldScreenSize.width - 1;
|
||||
SourceRect.bottom = _region.bottom;
|
||||
|
||||
const auto sourceViewport = Viewport::FromInclusive(SourceRect);
|
||||
|
||||
_screenInfo.WriteRect(_oldContents, sourceViewport.Origin());
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to calculate the size of the popup.
|
||||
// Arguments:
|
||||
// - screenInfo - Screen buffer we will be drawing into
|
||||
// - proposedSize - The suggested size of the popup that may need to be adjusted to fit
|
||||
// Return Value:
|
||||
// - Coordinate size that the popup should consume in the screen buffer
|
||||
til::size Popup::_CalculateSize(const SCREEN_INFORMATION& screenInfo, const til::size proposedSize)
|
||||
{
|
||||
// determine popup dimensions
|
||||
auto size = proposedSize;
|
||||
size.width += 2; // add borders
|
||||
size.height += 2; // add borders
|
||||
|
||||
const auto viewportSize = screenInfo.GetViewport().Dimensions();
|
||||
|
||||
size.width = std::min(size.width, viewportSize.width);
|
||||
size.height = std::min(size.height, viewportSize.height);
|
||||
|
||||
// make sure there's enough room for the popup borders
|
||||
THROW_HR_IF(E_NOT_SUFFICIENT_BUFFER, size.width < 2 || size.height < 2);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to calculate the origin point (within the screen buffer) for the popup
|
||||
// Arguments:
|
||||
// - screenInfo - Screen buffer we will be drawing into
|
||||
// - size - The size that the popup will consume
|
||||
// Return Value:
|
||||
// - Coordinate position of the origin point of the popup
|
||||
til::point Popup::_CalculateOrigin(const SCREEN_INFORMATION& screenInfo, const til::size size)
|
||||
{
|
||||
const auto viewport = screenInfo.GetViewport();
|
||||
|
||||
// determine origin. center popup on window
|
||||
til::point origin;
|
||||
origin.x = (viewport.Width() - size.width) / 2 + viewport.Left();
|
||||
origin.y = (viewport.Height() - size.height) / 2 + viewport.Top();
|
||||
return origin;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to return the width of the popup in columns
|
||||
// Return Value:
|
||||
// - Width of popup inside attached screen buffer.
|
||||
til::CoordType Popup::Width() const noexcept
|
||||
{
|
||||
return _region.right - _region.left - 1;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to return the height of the popup in columns
|
||||
// Return Value:
|
||||
// - Height of popup inside attached screen buffer.
|
||||
til::CoordType Popup::Height() const noexcept
|
||||
{
|
||||
return _region.bottom - _region.top - 1;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to get the position on top of some types of popup dialogs where
|
||||
// we should overlay the cursor for user input.
|
||||
// Return Value:
|
||||
// - Coordinate location on the popup where the cursor should be placed.
|
||||
til::point Popup::GetCursorPosition() const noexcept
|
||||
{
|
||||
til::point CursorPosition;
|
||||
CursorPosition.x = _region.right - MINIMUM_COMMAND_PROMPT_SIZE;
|
||||
CursorPosition.y = _region.top + 1;
|
||||
return CursorPosition;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - changes the function used to gather user input. for allowing custom input during unit tests only
|
||||
// Arguments:
|
||||
// - function - function to use to fetch input
|
||||
void Popup::SetUserInputFunction(UserInputFunction function) noexcept
|
||||
{
|
||||
_userInputFunction = function;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets a single char input from the user
|
||||
// Arguments:
|
||||
// - cookedReadData - cookedReadData for this popup operation
|
||||
// - popupKey - on completion, will be true if key was a popup key
|
||||
// - wch - on completion, the char read from the user
|
||||
// Return Value:
|
||||
// - relevant NTSTATUS
|
||||
[[nodiscard]] NTSTATUS Popup::_getUserInput(COOKED_READ_DATA& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept
|
||||
{
|
||||
return _userInputFunction(cookedReadData, popupKey, modifiers, wch);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets a single char input from the user using the InputBuffer
|
||||
// Arguments:
|
||||
// - cookedReadData - cookedReadData for this popup operation
|
||||
// - popupKey - on completion, will be true if key was a popup key
|
||||
// - wch - on completion, the char read from the user
|
||||
// Return Value:
|
||||
// - relevant NTSTATUS
|
||||
[[nodiscard]] NTSTATUS Popup::_getUserInputInternal(COOKED_READ_DATA& cookedReadData,
|
||||
bool& popupKey,
|
||||
DWORD& modifiers,
|
||||
wchar_t& wch) noexcept
|
||||
{
|
||||
const auto pInputBuffer = cookedReadData.GetInputBuffer();
|
||||
auto Status = GetChar(pInputBuffer,
|
||||
&wch,
|
||||
true,
|
||||
nullptr,
|
||||
&popupKey,
|
||||
&modifiers);
|
||||
if (FAILED_NTSTATUS(Status) && Status != CONSOLE_STATUS_WAIT)
|
||||
{
|
||||
cookedReadData.BytesRead() = 0;
|
||||
}
|
||||
return Status;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- popup.h
|
||||
|
||||
Abstract:
|
||||
- This file contains the internal structures and definitions used by command line input and editing.
|
||||
|
||||
Author:
|
||||
- Therese Stowell (ThereseS) 15-Nov-1991
|
||||
|
||||
Revision History:
|
||||
- Mike Griese (migrie) Jan 2018:
|
||||
Refactored the history and alias functionality into their own files.
|
||||
- Michael Niksa (miniksa) May 2018:
|
||||
Separated out popups from the rest of command line functionality.
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "readDataCooked.hpp"
|
||||
#include "screenInfo.hpp"
|
||||
#include "readDataCooked.hpp"
|
||||
|
||||
class CommandHistory;
|
||||
|
||||
class Popup
|
||||
{
|
||||
public:
|
||||
static constexpr til::CoordType MINIMUM_COMMAND_PROMPT_SIZE = 5;
|
||||
|
||||
using UserInputFunction = std::function<NTSTATUS(COOKED_READ_DATA&, bool&, DWORD&, wchar_t&)>;
|
||||
|
||||
Popup(SCREEN_INFORMATION& screenInfo, const til::size proposedSize);
|
||||
virtual ~Popup();
|
||||
[[nodiscard]] virtual NTSTATUS Process(COOKED_READ_DATA& cookedReadData) noexcept = 0;
|
||||
|
||||
void Draw();
|
||||
|
||||
void End();
|
||||
|
||||
til::CoordType Width() const noexcept;
|
||||
til::CoordType Height() const noexcept;
|
||||
|
||||
til::point GetCursorPosition() const noexcept;
|
||||
|
||||
protected:
|
||||
// used in test code to alter how the popup fetches use input
|
||||
void SetUserInputFunction(UserInputFunction function) noexcept;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class CopyFromCharPopupTests;
|
||||
friend class CopyToCharPopupTests;
|
||||
friend class CommandNumberPopupTests;
|
||||
friend class CommandListPopupTests;
|
||||
#endif
|
||||
|
||||
[[nodiscard]] NTSTATUS _getUserInput(COOKED_READ_DATA& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept;
|
||||
void _DrawPrompt(const UINT id);
|
||||
virtual void _DrawContent() = 0;
|
||||
|
||||
til::inclusive_rect _region; // region popup occupies
|
||||
SCREEN_INFORMATION& _screenInfo;
|
||||
TextAttribute _attributes; // text attributes
|
||||
|
||||
private:
|
||||
til::size _CalculateSize(const SCREEN_INFORMATION& screenInfo, const til::size proposedSize);
|
||||
til::point _CalculateOrigin(const SCREEN_INFORMATION& screenInfo, const til::size size);
|
||||
|
||||
void _DrawBorder();
|
||||
|
||||
[[nodiscard]] static NTSTATUS _getUserInputInternal(COOKED_READ_DATA& cookedReadData,
|
||||
bool& popupKey,
|
||||
DWORD& modifiers,
|
||||
wchar_t& wch) noexcept;
|
||||
|
||||
OutputCellRect _oldContents; // contains data under popup
|
||||
til::size _oldScreenSize;
|
||||
UserInputFunction _userInputFunction;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,5 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- readDataCooked.hpp
|
||||
|
||||
Abstract:
|
||||
- This file defines the read data structure for reading the command line.
|
||||
- Cooked reads specifically refer to when the console host acts as a command line on behalf
|
||||
of another console application (e.g. aliases, command history, completion, line manipulation, etc.)
|
||||
- The data struct will help store context across multiple calls or in the case of a wait condition.
|
||||
- Wait conditions happen frequently for cooked reads because they're virtually always waiting for
|
||||
the user to finish "manipulating" the edit line before hitting enter and submitting the final
|
||||
result to the client application.
|
||||
- A cooked read is also limited specifically to string/textual information. Only keyboard-type input applies.
|
||||
- This can be triggered via ReadConsole A/W and ReadFile A/W calls.
|
||||
|
||||
Author:
|
||||
- Austin Diviness (AustDi) 1-Mar-2017
|
||||
- Michael Niksa (MiNiksa) 1-Mar-2017
|
||||
|
||||
Revision History:
|
||||
- Pulled from original authoring by Therese Stowell (ThereseS, 1990)
|
||||
- Separated from cmdline.h/cmdline.cpp (AustDi, 2017)
|
||||
--*/
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -33,132 +9,141 @@ Revision History:
|
||||
class COOKED_READ_DATA final : public ReadData
|
||||
{
|
||||
public:
|
||||
COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer,
|
||||
_In_ INPUT_READ_HANDLE_DATA* const pInputReadHandleData,
|
||||
COOKED_READ_DATA(_In_ InputBuffer* pInputBuffer,
|
||||
_In_ INPUT_READ_HANDLE_DATA* pInputReadHandleData,
|
||||
SCREEN_INFORMATION& screenInfo,
|
||||
_In_ size_t UserBufferSize,
|
||||
_In_ char* UserBuffer,
|
||||
_In_ ULONG CtrlWakeupMask,
|
||||
_In_ const std::wstring_view exeName,
|
||||
_In_ const std::wstring_view initialData,
|
||||
_In_ ConsoleProcessHandle* const pClientProcess);
|
||||
_In_ std::wstring_view exeName,
|
||||
_In_ std::wstring_view initialData,
|
||||
_In_ ConsoleProcessHandle* pClientProcess);
|
||||
|
||||
~COOKED_READ_DATA() override;
|
||||
COOKED_READ_DATA(COOKED_READ_DATA&&) = default;
|
||||
void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) noexcept override;
|
||||
|
||||
bool AtEol() const noexcept;
|
||||
bool Notify(WaitTerminationReason TerminationReason,
|
||||
bool fIsUnicode,
|
||||
_Out_ NTSTATUS* pReplyStatus,
|
||||
_Out_ size_t* pNumBytes,
|
||||
_Out_ DWORD* pControlKeyState,
|
||||
_Out_ void* pOutputData) noexcept override;
|
||||
|
||||
void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) override;
|
||||
bool Read(bool isUnicode, size_t& numBytes, ULONG& controlKeyState);
|
||||
|
||||
bool Notify(const WaitTerminationReason TerminationReason,
|
||||
const bool fIsUnicode,
|
||||
_Out_ NTSTATUS* const pReplyStatus,
|
||||
_Out_ size_t* const pNumBytes,
|
||||
_Out_ DWORD* const pControlKeyState,
|
||||
_Out_ void* const pOutputData) override;
|
||||
void EraseBeforeResize();
|
||||
void RedrawAfterResize();
|
||||
|
||||
std::span<wchar_t> SpanAtPointer();
|
||||
std::span<wchar_t> SpanWholeBuffer();
|
||||
|
||||
size_t Write(const std::wstring_view wstr);
|
||||
|
||||
void ProcessAliases(DWORD& lineCount);
|
||||
|
||||
[[nodiscard]] HRESULT Read(const bool isUnicode,
|
||||
size_t& numBytes,
|
||||
ULONG& controlKeyState) noexcept;
|
||||
|
||||
bool ProcessInput(const wchar_t wch,
|
||||
const DWORD keyState,
|
||||
NTSTATUS& status);
|
||||
|
||||
CommandHistory& History() noexcept;
|
||||
bool HasHistory() const noexcept;
|
||||
|
||||
const size_t& VisibleCharCount() const noexcept;
|
||||
size_t& VisibleCharCount() noexcept;
|
||||
|
||||
SCREEN_INFORMATION& ScreenInfo() noexcept;
|
||||
|
||||
til::point OriginalCursorPosition() const noexcept;
|
||||
til::point& OriginalCursorPosition() noexcept;
|
||||
|
||||
til::point& BeforeDialogCursorPosition() noexcept;
|
||||
|
||||
bool IsEchoInput() const noexcept;
|
||||
bool IsInsertMode() const noexcept;
|
||||
void SetInsertMode(const bool mode) noexcept;
|
||||
bool IsUnicode() const noexcept;
|
||||
|
||||
size_t UserBufferSize() const noexcept;
|
||||
|
||||
wchar_t* BufferStartPtr() noexcept;
|
||||
wchar_t* BufferCurrentPtr() noexcept;
|
||||
void SetBufferCurrentPtr(wchar_t* ptr) noexcept;
|
||||
|
||||
const size_t& BytesRead() const noexcept;
|
||||
size_t& BytesRead() noexcept;
|
||||
|
||||
const size_t& InsertionPoint() const noexcept;
|
||||
size_t& InsertionPoint() noexcept;
|
||||
|
||||
void SetReportedByteCount(const size_t count) noexcept;
|
||||
|
||||
void Erase() noexcept;
|
||||
size_t SavePromptToUserBuffer(const size_t cch);
|
||||
void SavePendingInput(const size_t cch, const bool multiline);
|
||||
|
||||
#if UNIT_TESTING
|
||||
friend class CommandLineTests;
|
||||
friend class CopyToCharPopupTests;
|
||||
friend class CommandNumberPopupTests;
|
||||
friend class CommandListPopupTests;
|
||||
friend class PopupTestHelper;
|
||||
#endif
|
||||
void SetInsertMode(bool insertMode) noexcept;
|
||||
bool IsEmpty() const noexcept;
|
||||
bool PresentingPopup() const noexcept;
|
||||
til::point_span GetBoundaries() const noexcept;
|
||||
|
||||
private:
|
||||
size_t _bufferSize; // size in bytes
|
||||
size_t _bytesRead;
|
||||
static constexpr uint8_t CommandNumberMaxInputLength = 5;
|
||||
|
||||
// insertion position into the buffer (where the conceptual prompt cursor is)
|
||||
size_t _currentPosition; // char position, not byte position
|
||||
enum class PopupKind
|
||||
{
|
||||
// Copies text from the previous command between the current cursor position and the first instance
|
||||
// of a given char (but not including it) into the current prompt line at the current cursor position.
|
||||
// Basically, F3 and this prompt have identical behavior, but the prompt searches for a terminating character.
|
||||
//
|
||||
// Let's say your last command was:
|
||||
// echo hello
|
||||
// And you type the following with the cursor at "^":
|
||||
// echo abcd efgh
|
||||
// ^
|
||||
// Then this command, given the char "o" will turn it into
|
||||
// echo hell efgh
|
||||
CopyToChar,
|
||||
// Erases text between the current cursor position and the first instance of a given char (but not including it).
|
||||
// It's unknown to me why this is was historically called "copy from char" as it conhost never copied anything.
|
||||
CopyFromChar,
|
||||
// Let's you choose to replace the current prompt with one from the command history by index.
|
||||
CommandNumber,
|
||||
// Let's you choose to replace the current prompt with one from the command history via a
|
||||
// visual select dialog. Among all the popups this one is the most widely used one by far.
|
||||
CommandList,
|
||||
};
|
||||
|
||||
wchar_t* _bufPtr; // current position to insert chars at
|
||||
struct Popup
|
||||
{
|
||||
PopupKind kind;
|
||||
|
||||
// should be const. the first char of the buffer
|
||||
wchar_t* _backupLimit;
|
||||
// The inner rectangle of the popup, excluding the border that we draw.
|
||||
// In absolute TextBuffer coordinates.
|
||||
til::rect contentRect;
|
||||
// The area we've backed up and need to restore when we dismiss the popup.
|
||||
// It'll practically always be 1 larger than contentRect in all 4 directions.
|
||||
Microsoft::Console::Types::Viewport backupRect;
|
||||
// The backed up buffer contents. Uses CHAR_INFO for convenience.
|
||||
std::vector<CHAR_INFO> backup;
|
||||
|
||||
size_t _userBufferSize; // doubled size in ansi case
|
||||
char* _userBuffer;
|
||||
// Using a std::variant would be preferable in modern C++ but is practically equally annoying to use.
|
||||
union
|
||||
{
|
||||
// Used by PopupKind::CommandNumber
|
||||
struct
|
||||
{
|
||||
// Keep 1 char space for the trailing \0 char.
|
||||
std::array<wchar_t, CommandNumberMaxInputLength + 1> buffer;
|
||||
uint8_t bufferSize;
|
||||
} commandNumber;
|
||||
|
||||
size_t* _pdwNumBytes;
|
||||
// Used by PopupKind::CommandList
|
||||
struct
|
||||
{
|
||||
// Command history index of the first row we draw in the popup.
|
||||
CommandHistory::Index top;
|
||||
// Command history index of the currently selected row.
|
||||
CommandHistory::Index selected;
|
||||
// Tracks the part of the popup that has previously been drawn and needs to be redrawn in the next paint.
|
||||
// This becomes relevant when the length of the history changes while the popup is open (= when deleting entries).
|
||||
til::CoordType dirtyHeight;
|
||||
} commandList;
|
||||
};
|
||||
};
|
||||
|
||||
std::unique_ptr<byte[]> _buffer;
|
||||
static size_t _wordPrev(const std::wstring_view& chars, size_t position);
|
||||
static size_t _wordNext(const std::wstring_view& chars, size_t position);
|
||||
|
||||
const std::wstring_view& _newlineSuffix() const noexcept;
|
||||
bool _readCharInputLoop();
|
||||
bool _handleChar(wchar_t wch, DWORD modifiers);
|
||||
void _handleVkey(uint16_t vkey, DWORD modifiers);
|
||||
void _handlePostCharInputLoop(bool isUnicode, size_t& numBytes, ULONG& controlKeyState);
|
||||
void _markAsDirty();
|
||||
void _flushBuffer();
|
||||
void _erase(til::CoordType distance);
|
||||
til::CoordType _writeChars(const std::wstring_view& text) const;
|
||||
til::point _offsetPosition(til::point pos, til::CoordType distance) const;
|
||||
void _unwindCursorPosition(til::CoordType distance) const;
|
||||
void _replaceBuffer(const std::wstring_view& str);
|
||||
|
||||
void _popupPush(PopupKind kind);
|
||||
void _popupsDone();
|
||||
void _popupHandleCopyToCharInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers);
|
||||
void _popupHandleCopyFromCharInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers);
|
||||
void _popupHandleCommandNumberInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers);
|
||||
bool _popupHandleCommandListInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers);
|
||||
bool _popupHandleInput(wchar_t wch, uint16_t vkey, DWORD keyState);
|
||||
void _popupDrawPrompt(const Popup& popup, UINT id) const;
|
||||
void _popupDrawCommandList(Popup& popup) const;
|
||||
|
||||
SCREEN_INFORMATION& _screenInfo;
|
||||
std::span<char> _userBuffer;
|
||||
std::wstring _exeName;
|
||||
ConsoleProcessHandle* _processHandle = nullptr;
|
||||
CommandHistory* _history = nullptr;
|
||||
ULONG _ctrlWakeupMask = 0;
|
||||
ULONG _controlKeyState = 0;
|
||||
std::unique_ptr<ConsoleHandleData> _tempHandle;
|
||||
|
||||
// TODO MSFT:11285829 make this something other than a deletable pointer
|
||||
// non-ownership pointer
|
||||
CommandHistory* _commandHistory;
|
||||
std::wstring _buffer;
|
||||
size_t _bufferCursor = 0;
|
||||
til::CoordType _distanceCursor;
|
||||
til::CoordType _distanceEnd;
|
||||
bool _bufferDirty = false;
|
||||
bool _insertMode = false;
|
||||
|
||||
ULONG _controlKeyState;
|
||||
ULONG _ctrlWakeupMask;
|
||||
size_t _visibleCharCount; // TODO MSFT:11285829 is this cells or glyphs? ie. is a wide char counted as 1 or 2?
|
||||
SCREEN_INFORMATION& _screenInfo;
|
||||
|
||||
// Note that cookedReadData's _originalCursorPosition is the position before ANY text was entered on the edit line.
|
||||
til::point _originalCursorPosition;
|
||||
til::point _beforeDialogCursorPosition; // Currently only used for F9 (ProcessCommandNumberInput) since it's the only pop-up to move the cursor when it starts.
|
||||
|
||||
const bool _echoInput;
|
||||
const bool _lineInput;
|
||||
const bool _processedInput;
|
||||
bool _insertMode;
|
||||
bool _unicode;
|
||||
|
||||
ConsoleProcessHandle* const _clientProcess;
|
||||
|
||||
[[nodiscard]] NTSTATUS _readCharInputLoop(const bool isUnicode, size_t& numBytes) noexcept;
|
||||
|
||||
[[nodiscard]] NTSTATUS _handlePostCharInputLoop(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState) noexcept;
|
||||
std::vector<Popup> _popups;
|
||||
};
|
||||
|
||||
@@ -873,12 +873,7 @@ void SCREEN_INFORMATION::ProcessResizeWindow(const til::rect* const prcClientNew
|
||||
|
||||
// 1.a In some modes, the screen buffer size needs to change on window size,
|
||||
// so do that first.
|
||||
// _AdjustScreenBuffer might hide the commandline. If it does so, it'll
|
||||
// return S_OK instead of S_FALSE. In that case, we'll need to re-show
|
||||
// the commandline ourselves once the viewport size is updated.
|
||||
// (See 1.b below)
|
||||
const auto adjustBufferSizeResult = _AdjustScreenBuffer(prcClientNew);
|
||||
LOG_IF_FAILED(adjustBufferSizeResult);
|
||||
LOG_IF_FAILED(_AdjustScreenBuffer(prcClientNew));
|
||||
|
||||
// 2. Now calculate how large the new viewport should be
|
||||
til::size coordViewportSize;
|
||||
@@ -888,16 +883,6 @@ void SCREEN_INFORMATION::ProcessResizeWindow(const til::rect* const prcClientNew
|
||||
// The old/new comparison is to figure out which side the window was resized from.
|
||||
_AdjustViewportSize(prcClientNew, prcClientOld, &coordViewportSize);
|
||||
|
||||
// 1.b If we did actually change the buffer size, then we need to show the
|
||||
// commandline again. We hid it during _AdjustScreenBuffer, but we
|
||||
// couldn't turn it back on until the Viewport was updated to the new
|
||||
// size. See MSFT:19976291
|
||||
if (SUCCEEDED(adjustBufferSizeResult) && adjustBufferSizeResult != S_FALSE)
|
||||
{
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine.Show();
|
||||
}
|
||||
|
||||
// 4. Finally, update the scroll bars.
|
||||
UpdateScrollBars();
|
||||
|
||||
@@ -1016,23 +1001,7 @@ void SCREEN_INFORMATION::ProcessResizeWindow(const til::rect* const prcClientNew
|
||||
// Only attempt to modify the buffer if something changed. Expensive operation.
|
||||
if (coordBufferSizeOld != coordBufferSizeNew)
|
||||
{
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
|
||||
// TODO: Deleting and redrawing the command line during resizing can cause flickering. See: http://osgvsowi/658439
|
||||
// 1. Delete input string if necessary (see menu.c)
|
||||
commandLine.Hide(FALSE);
|
||||
|
||||
const auto savedCursorVisibility = _textBuffer->GetCursor().IsVisible();
|
||||
_textBuffer->GetCursor().SetIsVisible(false);
|
||||
|
||||
// 2. Call the resize screen buffer method (expensive) to redimension the backing buffer (and reflow)
|
||||
LOG_IF_FAILED(ResizeScreenBuffer(coordBufferSizeNew, FALSE));
|
||||
|
||||
// MSFT:19976291 Don't re-show the commandline here. We need to wait for
|
||||
// the viewport to also get resized before we can re-show the commandline.
|
||||
// ProcessResizeWindow will call commandline.Show() for us.
|
||||
_textBuffer->GetCursor().SetIsVisible(savedCursorVisibility);
|
||||
|
||||
// Return S_OK, to indicate we succeeded and actually did something.
|
||||
hr = S_OK;
|
||||
}
|
||||
@@ -1540,8 +1509,17 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
|
||||
// cancel any active selection before resizing or it will not necessarily line up with the new buffer positions
|
||||
Selection::Instance().ClearSelection();
|
||||
|
||||
// cancel any popups before resizing or they will not necessarily line up with new buffer positions
|
||||
CommandLine::Instance().EndAllPopups();
|
||||
if (gci.HasPendingCookedRead())
|
||||
{
|
||||
gci.CookedReadData().EraseBeforeResize();
|
||||
}
|
||||
const auto cookedReadRestore = wil::scope_exit([]() {
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (gci.HasPendingCookedRead())
|
||||
{
|
||||
gci.CookedReadData().RedrawAfterResize();
|
||||
}
|
||||
});
|
||||
|
||||
const auto fWrapText = gci.GetWrapText();
|
||||
// GH#3493: Don't reflow the alt buffer.
|
||||
@@ -1937,10 +1915,7 @@ void SCREEN_INFORMATION::_handleDeferredResize(SCREEN_INFORMATION& siMain)
|
||||
// too much work.
|
||||
if (newBufferSize != oldScreenBufferSize)
|
||||
{
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine.Hide(FALSE);
|
||||
LOG_IF_FAILED(siMain.ResizeScreenBuffer(newBufferSize, TRUE));
|
||||
commandLine.Show();
|
||||
}
|
||||
|
||||
// Not that the buffer is smaller, actually make sure to resize our
|
||||
@@ -2098,7 +2073,7 @@ bool SCREEN_INFORMATION::_IsInVTMode() const
|
||||
// <none>
|
||||
// Return value:
|
||||
// - This screen buffer's attributes
|
||||
TextAttribute SCREEN_INFORMATION::GetAttributes() const
|
||||
const TextAttribute& SCREEN_INFORMATION::GetAttributes() const noexcept
|
||||
{
|
||||
return _textBuffer->GetCurrentAttributes();
|
||||
}
|
||||
@@ -2109,7 +2084,7 @@ TextAttribute SCREEN_INFORMATION::GetAttributes() const
|
||||
// <none>
|
||||
// Return value:
|
||||
// - This screen buffer's popup attributes
|
||||
TextAttribute SCREEN_INFORMATION::GetPopupAttributes() const
|
||||
const TextAttribute& SCREEN_INFORMATION::GetPopupAttributes() const noexcept
|
||||
{
|
||||
return _PopupAttributes;
|
||||
}
|
||||
@@ -2311,56 +2286,6 @@ void SCREEN_INFORMATION::SetTerminalConnection(_In_ VtEngine* const pTtyConnecti
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine copies a rectangular region from the screen buffer. no clipping is done.
|
||||
// Arguments:
|
||||
// - viewport - rectangle in source buffer to copy
|
||||
// Return Value:
|
||||
// - output cell rectangle copy of screen buffer data
|
||||
// Note:
|
||||
// - will throw exception on error.
|
||||
OutputCellRect SCREEN_INFORMATION::ReadRect(const Viewport viewport) const
|
||||
{
|
||||
// If the viewport given doesn't fit inside this screen, it's not a valid argument.
|
||||
THROW_HR_IF(E_INVALIDARG, !GetBufferSize().IsInBounds(viewport));
|
||||
|
||||
OutputCellRect result(viewport.Height(), viewport.Width());
|
||||
const OutputCell paddingCell{ std::wstring_view{ &UNICODE_SPACE, 1 }, {}, GetAttributes() };
|
||||
for (til::CoordType rowIndex = 0, height = viewport.Height(); rowIndex < height; ++rowIndex)
|
||||
{
|
||||
auto location = viewport.Origin();
|
||||
location.y += rowIndex;
|
||||
|
||||
auto data = GetCellLineDataAt(location);
|
||||
const auto span = result.GetRow(rowIndex);
|
||||
auto it = span.begin();
|
||||
|
||||
// Copy row data while there still is data and we haven't run out of rect to store it into.
|
||||
while (data && it < span.end())
|
||||
{
|
||||
*it++ = *data++;
|
||||
}
|
||||
|
||||
// Pad out any remaining space.
|
||||
while (it < span.end())
|
||||
{
|
||||
*it++ = paddingCell;
|
||||
}
|
||||
|
||||
// if we're clipping a dbcs char then don't include it, add a space instead
|
||||
if (span.begin()->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
*span.begin() = paddingCell;
|
||||
}
|
||||
if (span.rbegin()->DbcsAttr() == DbcsAttribute::Leading)
|
||||
{
|
||||
*span.rbegin() = paddingCell;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer at the cursor position.
|
||||
// Arguments:
|
||||
|
||||
@@ -123,8 +123,6 @@ public:
|
||||
static void s_InsertScreenBuffer(_In_ SCREEN_INFORMATION* const pScreenInfo);
|
||||
static void s_RemoveScreenBuffer(_In_ SCREEN_INFORMATION* const pScreenInfo);
|
||||
|
||||
OutputCellRect ReadRect(const Microsoft::Console::Types::Viewport location) const;
|
||||
|
||||
TextBufferCellIterator GetCellDataAt(const til::point at) const;
|
||||
TextBufferCellIterator GetCellLineDataAt(const til::point at) const;
|
||||
TextBufferCellIterator GetCellDataAt(const til::point at, const Microsoft::Console::Types::Viewport limit) const;
|
||||
@@ -202,8 +200,8 @@ public:
|
||||
SCREEN_INFORMATION& GetActiveBuffer();
|
||||
const SCREEN_INFORMATION& GetActiveBuffer() const;
|
||||
|
||||
TextAttribute GetAttributes() const;
|
||||
TextAttribute GetPopupAttributes() const;
|
||||
const TextAttribute& GetAttributes() const noexcept;
|
||||
const TextAttribute& GetPopupAttributes() const noexcept;
|
||||
|
||||
void SetAttributes(const TextAttribute& attributes);
|
||||
void SetPopupAttributes(const TextAttribute& popupAttributes);
|
||||
|
||||
@@ -208,7 +208,7 @@ bool Scrolling::s_HandleKeyScrollingEvent(const INPUT_KEY_INFO* const pKeyInfo)
|
||||
|
||||
const auto VirtualKeyCode = pKeyInfo->GetVirtualKey();
|
||||
const auto fIsCtrlPressed = pKeyInfo->IsCtrlPressed();
|
||||
const auto fIsEditLineEmpty = CommandLine::IsEditLineEmpty();
|
||||
const auto fIsEditLineEmpty = !gci.HasPendingCookedRead() || gci.CookedReadData().IsEmpty();
|
||||
|
||||
// If escape, enter or ctrl-c, cancel scroll.
|
||||
if (VirtualKeyCode == VK_ESCAPE ||
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Microsoft::Console::Types;
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
// Routine Description:
|
||||
@@ -950,49 +948,27 @@ bool Selection::_HandleMarkModeSelectionNav(const INPUT_KEY_INFO* const pInputKe
|
||||
[[nodiscard]] bool Selection::s_GetInputLineBoundaries(_Out_opt_ til::point* const pcoordInputStart, _Out_opt_ til::point* const pcoordInputEnd)
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto bufferSize = gci.GetActiveOutputBuffer().GetBufferSize();
|
||||
|
||||
auto& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer();
|
||||
|
||||
const auto pendingCookedRead = gci.HasPendingCookedRead();
|
||||
const auto isVisible = CommandLine::Instance().IsVisible();
|
||||
|
||||
// if we have no read data, we have no input line.
|
||||
if (!pendingCookedRead || gci.CookedReadData().VisibleCharCount() == 0 || !isVisible)
|
||||
if (gci.HasPendingCookedRead())
|
||||
{
|
||||
return false;
|
||||
auto boundaries = gci.CookedReadData().GetBoundaries();
|
||||
if (boundaries.start < boundaries.end)
|
||||
{
|
||||
if (pcoordInputStart != nullptr)
|
||||
{
|
||||
*pcoordInputStart = boundaries.start;
|
||||
}
|
||||
if (pcoordInputEnd != nullptr)
|
||||
{
|
||||
// - 1 so the coordinate is on top of the last position of the text, not one past it.
|
||||
gci.GetActiveOutputBuffer().GetBufferSize().MoveInBounds(-1, boundaries.end);
|
||||
*pcoordInputEnd = boundaries.end;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& cookedRead = gci.CookedReadData();
|
||||
const auto coordStart = cookedRead.OriginalCursorPosition();
|
||||
auto coordEnd = cookedRead.OriginalCursorPosition();
|
||||
|
||||
if (coordEnd.x < 0 && coordEnd.y < 0)
|
||||
{
|
||||
// if the original cursor position from the input line data is invalid, then the buffer cursor position is the final position
|
||||
coordEnd = textBuffer.GetCursor().GetPosition();
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, we need to add the number of characters in the input line to the original cursor position
|
||||
bufferSize.MoveInBounds(gsl::narrow<til::CoordType>(cookedRead.VisibleCharCount()), coordEnd);
|
||||
}
|
||||
|
||||
// - 1 so the coordinate is on top of the last position of the text, not one past it.
|
||||
bufferSize.MoveInBounds(-1, coordEnd);
|
||||
|
||||
if (pcoordInputStart != nullptr)
|
||||
{
|
||||
pcoordInputStart->x = coordStart.x;
|
||||
pcoordInputStart->y = coordStart.y;
|
||||
}
|
||||
|
||||
if (pcoordInputEnd != nullptr)
|
||||
{
|
||||
*pcoordInputEnd = coordEnd;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@@ -16,20 +16,17 @@ Revision History:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IIoProvider.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "conimeinfo.h"
|
||||
#include "VtIo.hpp"
|
||||
#include "CursorBlinker.hpp"
|
||||
|
||||
#include "IIoProvider.hpp"
|
||||
#include "readDataCooked.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "VtIo.hpp"
|
||||
#include "../audio/midi/MidiAudio.hpp"
|
||||
#include "../host/RenderData.hpp"
|
||||
#include "../server/ProcessList.h"
|
||||
#include "../server/WaitQueue.h"
|
||||
|
||||
#include "../host/RenderData.hpp"
|
||||
#include "../audio/midi/MidiAudio.hpp"
|
||||
|
||||
#include <til/ticket_lock.h>
|
||||
|
||||
// clang-format off
|
||||
@@ -91,8 +88,6 @@ public:
|
||||
|
||||
DWORD Flags = 0;
|
||||
|
||||
std::atomic<WORD> PopupCount = 0;
|
||||
|
||||
// the following fields are used for ansi-unicode translation
|
||||
UINT CP = 0;
|
||||
UINT OutputCP = 0;
|
||||
@@ -121,6 +116,7 @@ public:
|
||||
|
||||
bool IsInVtIoMode() const;
|
||||
bool HasPendingCookedRead() const noexcept;
|
||||
bool HasPendingPopup() const noexcept;
|
||||
const COOKED_READ_DATA& CookedReadData() const noexcept;
|
||||
COOKED_READ_DATA& CookedReadData() noexcept;
|
||||
void SetCookedReadData(COOKED_READ_DATA* readData) noexcept;
|
||||
@@ -167,8 +163,6 @@ private:
|
||||
MidiAudio _midiAudio;
|
||||
};
|
||||
|
||||
#define ConsoleLocked() (ServiceLocator::LocateGlobals()->getConsoleInformation()->ConsoleLock.OwningThread == NtCurrentTeb()->ClientId.UniqueThread)
|
||||
|
||||
#define CONSOLE_STATUS_WAIT 0xC0030001
|
||||
#define CONSOLE_STATUS_READ_COMPLETE 0xC0030002
|
||||
#define CONSOLE_STATUS_WAIT_NO_BLOCK 0xC0030003
|
||||
|
||||
@@ -46,7 +46,6 @@ SOURCES = \
|
||||
..\scrolling.cpp \
|
||||
..\cmdline.cpp \
|
||||
..\CursorBlinker.cpp \
|
||||
..\popup.cpp \
|
||||
..\alias.cpp \
|
||||
..\history.cpp \
|
||||
..\VtIo.cpp \
|
||||
@@ -89,10 +88,6 @@ SOURCES = \
|
||||
..\conareainfo.cpp \
|
||||
..\conimeinfo.cpp \
|
||||
..\ConsoleArguments.cpp \
|
||||
..\CommandNumberPopup.cpp \
|
||||
..\CommandListPopup.cpp \
|
||||
..\CopyFromCharPopup.cpp \
|
||||
..\CopyToCharPopup.cpp \
|
||||
..\VtApiRoutines.cpp \
|
||||
|
||||
|
||||
|
||||
@@ -247,93 +247,6 @@ static bool IsCommandLineEditingKey(const KEY_EVENT_RECORD& event)
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine returns the total number of screen spaces the characters up to the specified character take up.
|
||||
til::CoordType RetrieveTotalNumberOfSpaces(const til::CoordType sOriginalCursorPositionX,
|
||||
_In_reads_(ulCurrentPosition) const WCHAR* const pwchBuffer,
|
||||
_In_ size_t ulCurrentPosition)
|
||||
{
|
||||
auto XPosition = sOriginalCursorPositionX;
|
||||
til::CoordType NumSpaces = 0;
|
||||
|
||||
for (size_t i = 0; i < ulCurrentPosition; i++)
|
||||
{
|
||||
const auto Char = pwchBuffer[i];
|
||||
|
||||
til::CoordType NumSpacesForChar;
|
||||
if (Char == UNICODE_TAB)
|
||||
{
|
||||
NumSpacesForChar = NUMBER_OF_SPACES_IN_TAB(XPosition);
|
||||
}
|
||||
else if (IS_CONTROL_CHAR(Char))
|
||||
{
|
||||
NumSpacesForChar = 2;
|
||||
}
|
||||
else if (IsGlyphFullWidth(Char))
|
||||
{
|
||||
NumSpacesForChar = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
NumSpacesForChar = 1;
|
||||
}
|
||||
XPosition += NumSpacesForChar;
|
||||
NumSpaces += NumSpacesForChar;
|
||||
}
|
||||
|
||||
return NumSpaces;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine returns the number of screen spaces the specified character takes up.
|
||||
til::CoordType RetrieveNumberOfSpaces(_In_ til::CoordType sOriginalCursorPositionX,
|
||||
_In_reads_(ulCurrentPosition + 1) const WCHAR* const pwchBuffer,
|
||||
_In_ size_t ulCurrentPosition)
|
||||
{
|
||||
auto Char = pwchBuffer[ulCurrentPosition];
|
||||
if (Char == UNICODE_TAB)
|
||||
{
|
||||
til::CoordType NumSpaces = 0;
|
||||
auto XPosition = sOriginalCursorPositionX;
|
||||
|
||||
for (size_t i = 0; i <= ulCurrentPosition; i++)
|
||||
{
|
||||
Char = pwchBuffer[i];
|
||||
if (Char == UNICODE_TAB)
|
||||
{
|
||||
NumSpaces = NUMBER_OF_SPACES_IN_TAB(XPosition);
|
||||
}
|
||||
else if (IS_CONTROL_CHAR(Char))
|
||||
{
|
||||
NumSpaces = 2;
|
||||
}
|
||||
else if (IsGlyphFullWidth(Char))
|
||||
{
|
||||
NumSpaces = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
NumSpaces = 1;
|
||||
}
|
||||
XPosition += NumSpaces;
|
||||
}
|
||||
|
||||
return NumSpaces;
|
||||
}
|
||||
else if (IS_CONTROL_CHAR(Char))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else if (IsGlyphFullWidth(Char))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - if we have leftover input, copy as much fits into the user's
|
||||
// buffer and return. we may have multi line input, if a macro
|
||||
@@ -448,7 +361,7 @@ NT_CATCH_RETURN()
|
||||
|
||||
gci.SetCookedReadData(cookedReadData.get());
|
||||
bytesRead = buffer.size_bytes(); // This parameter on the way in is the size to read, on the way out, it will be updated to what is actually read.
|
||||
if (CONSOLE_STATUS_WAIT == cookedReadData->Read(unicode, bytesRead, controlKeyState))
|
||||
if (!cookedReadData->Read(unicode, bytesRead, controlKeyState))
|
||||
{
|
||||
// memory will be cleaned up by wait queue
|
||||
waiter.reset(cookedReadData.release());
|
||||
|
||||
@@ -20,8 +20,6 @@ Revision History:
|
||||
#include "../server/IWaitRoutine.h"
|
||||
#include "readData.hpp"
|
||||
|
||||
#define IS_CONTROL_CHAR(wch) ((wch) < L' ')
|
||||
|
||||
[[nodiscard]] NTSTATUS GetChar(_Inout_ InputBuffer* const pInputBuffer,
|
||||
_Out_ wchar_t* const pwchOut,
|
||||
const bool Wait,
|
||||
@@ -35,16 +33,4 @@ Revision History:
|
||||
INPUT_READ_HANDLE_DATA& readHandleState,
|
||||
const bool unicode);
|
||||
|
||||
// Routine Description:
|
||||
// - This routine returns the total number of screen spaces the characters up to the specified character take up.
|
||||
til::CoordType RetrieveTotalNumberOfSpaces(const til::CoordType sOriginalCursorPositionX,
|
||||
_In_reads_(ulCurrentPosition) const WCHAR* const pwchBuffer,
|
||||
const size_t ulCurrentPosition);
|
||||
|
||||
// Routine Description:
|
||||
// - This routine returns the number of screen spaces the specified character takes up.
|
||||
til::CoordType RetrieveNumberOfSpaces(_In_ til::CoordType sOriginalCursorPositionX,
|
||||
_In_reads_(ulCurrentPosition + 1) const WCHAR* const pwchBuffer,
|
||||
_In_ size_t ulCurrentPosition);
|
||||
|
||||
VOID UnblockWriteConsole(const DWORD dwReason);
|
||||
|
||||
@@ -173,16 +173,17 @@ void Tracing::s_TraceInputRecord(const INPUT_RECORD& inputRecord)
|
||||
}
|
||||
}
|
||||
|
||||
void Tracing::s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, _In_reads_(cchCookedBufferLength) const wchar_t* pwchCookedBuffer, _In_ ULONG cchCookedBufferLength)
|
||||
void Tracing::s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, const std::wstring_view& text)
|
||||
{
|
||||
if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, TraceKeywords::CookedRead))
|
||||
{
|
||||
const auto length = ::base::saturated_cast<ULONG>(text.size());
|
||||
TraceLoggingWrite(
|
||||
g_hConhostV2EventTraceProvider,
|
||||
"CookedRead",
|
||||
TraceLoggingPid(pConsoleProcessHandle->dwProcessId, "AttachedProcessId"),
|
||||
TraceLoggingCountedWideString(pwchCookedBuffer, cchCookedBufferLength, "ReadBuffer"),
|
||||
TraceLoggingULong(cchCookedBufferLength, "ReadBufferLength"),
|
||||
TraceLoggingCountedWideString(text.data(), length, "ReadBuffer"),
|
||||
TraceLoggingULong(length, "ReadBufferLength"),
|
||||
TraceLoggingFileTime(pConsoleProcessHandle->GetProcessCreationTime(), "AttachedProcessCreationTime"),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE),
|
||||
TraceLoggingKeyword(TraceKeywords::CookedRead));
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
static void s_TraceWindowMessage(const MSG& msg);
|
||||
static void s_TraceInputRecord(const INPUT_RECORD& inputRecord);
|
||||
|
||||
static void s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, _In_reads_(cchCookedBufferLength) const wchar_t* pwchCookedBuffer, _In_ ULONG cchCookedBufferLength);
|
||||
static void s_TraceCookedRead(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, const std::wstring_view& text);
|
||||
static void s_TraceConsoleAttachDetach(_In_ ConsoleProcessHandle* const pConsoleProcessHandle, _In_ bool bIsAttach);
|
||||
|
||||
static void __stdcall TraceFailure(const wil::FailureInfo& failure) noexcept;
|
||||
|
||||
@@ -111,167 +111,48 @@ class AliasTests
|
||||
// and match to our expected values.
|
||||
std::wstring alias(aliasName);
|
||||
std::wstring exe(exeName);
|
||||
std::wstring original(originalString);
|
||||
|
||||
std::wstring target;
|
||||
std::wstring expected;
|
||||
_RetrieveTargetExpectedPair(target, expected);
|
||||
|
||||
auto linesExpected = _ReplacePercentWithCRLF(expected);
|
||||
|
||||
std::wstring original(originalString);
|
||||
const auto linesExpected = _ReplacePercentWithCRLF(expected);
|
||||
|
||||
Alias::s_TestAddAlias(exe, alias, target);
|
||||
|
||||
// Fill classic wchar_t[] buffer for interfacing with the MatchAndCopyAlias function
|
||||
const auto bufferSize = 160ui16;
|
||||
auto buffer = std::make_unique<wchar_t[]>(bufferSize);
|
||||
wcscpy_s(buffer.get(), bufferSize, original.data());
|
||||
|
||||
const auto cbBuffer = bufferSize * sizeof(wchar_t);
|
||||
size_t bufferUsed = 0;
|
||||
DWORD linesActual = 0;
|
||||
|
||||
// Run the match and copy function.
|
||||
Alias::s_MatchAndCopyAliasLegacy(buffer.get(),
|
||||
wcslen(buffer.get()) * sizeof(wchar_t),
|
||||
buffer.get(),
|
||||
cbBuffer,
|
||||
bufferUsed,
|
||||
exe,
|
||||
linesActual);
|
||||
|
||||
// Null terminate buffer for comparison
|
||||
buffer[bufferUsed / sizeof(wchar_t)] = L'\0';
|
||||
|
||||
Log::Comment(String().Format(L"Expected: '%s'", expected.data()));
|
||||
Log::Comment(String().Format(L"Actual : '%s'", buffer.get()));
|
||||
|
||||
VERIFY_ARE_EQUAL(WEX::Common::String(expected.data()), WEX::Common::String(buffer.get()));
|
||||
size_t linesActual = 0;
|
||||
const auto actual = Alias::s_MatchAndCopyAlias(original, exe, linesActual);
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
VERIFY_ARE_EQUAL(linesExpected, linesActual);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMatchAndCopyTrailingCRLF)
|
||||
{
|
||||
const auto pwszSource = L"SourceWithoutCRLF\r\n";
|
||||
const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t);
|
||||
|
||||
const size_t cchTarget = 60;
|
||||
auto rgwchTarget = std::make_unique<wchar_t[]>(cchTarget);
|
||||
const auto cbTarget = cchTarget * sizeof(wchar_t);
|
||||
wcscpy_s(rgwchTarget.get(), cchTarget, L"testtesttesttesttest");
|
||||
auto rgwchTargetBefore = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get());
|
||||
|
||||
size_t cbTargetUsed = 0;
|
||||
DWORD dwLines = 0;
|
||||
|
||||
// Register the wrong alias name before we try.
|
||||
std::wstring exe(L"exe.exe");
|
||||
std::wstring sourceWithoutCRLF(L"SourceWithoutCRLF");
|
||||
std::wstring target(L"someTarget");
|
||||
Alias::s_TestAddAlias(exe, sourceWithoutCRLF, target);
|
||||
|
||||
const auto targetExpected = target + L"\r\n";
|
||||
const auto cbTargetExpected = targetExpected.size() * sizeof(wchar_t); // +2 for \r\n that will be added on replace.
|
||||
|
||||
Alias::s_MatchAndCopyAliasLegacy(pwszSource,
|
||||
cbSource,
|
||||
rgwchTarget.get(),
|
||||
cbTarget,
|
||||
cbTargetUsed,
|
||||
exe,
|
||||
dwLines);
|
||||
|
||||
// Terminate target buffer with \0 for comparison
|
||||
rgwchTarget[cbTargetUsed] = L'\0';
|
||||
|
||||
VERIFY_ARE_EQUAL(cbTargetExpected, cbTargetUsed, L"Target bytes should be filled with target size.");
|
||||
VERIFY_ARE_EQUAL(String(targetExpected.data()), String(rgwchTarget.get(), gsl::narrow<int>(cbTargetUsed / sizeof(wchar_t))), L"Target string should be filled with target data.");
|
||||
VERIFY_ARE_EQUAL(1u, dwLines, L"Line count should be 1.");
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMatchAndCopyInvalidExeName)
|
||||
{
|
||||
const auto pwszSource = L"Source";
|
||||
const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t);
|
||||
|
||||
const size_t cchTarget = 12;
|
||||
auto rgwchTarget = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc");
|
||||
auto rgwchTargetBefore = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get());
|
||||
|
||||
const auto cbTarget = cchTarget * sizeof(wchar_t);
|
||||
auto cbTargetUsed = cbTarget;
|
||||
|
||||
DWORD dwLines = 0;
|
||||
const auto dwLinesBefore = dwLines;
|
||||
|
||||
size_t dwLines = 1;
|
||||
std::wstring exeName;
|
||||
|
||||
Alias::s_MatchAndCopyAliasLegacy(pwszSource,
|
||||
cbSource,
|
||||
rgwchTarget.get(),
|
||||
cbTarget,
|
||||
cbTargetUsed,
|
||||
exeName,
|
||||
dwLines);
|
||||
|
||||
VERIFY_ARE_EQUAL(cbTarget, cbTargetUsed, L"Byte count shouldn't have changed with failure.");
|
||||
VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count shouldn't have changed with failure.");
|
||||
VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string shouldn't have changed with failure.");
|
||||
const auto buffer = Alias::s_MatchAndCopyAlias(pwszSource, exeName, dwLines);
|
||||
VERIFY_IS_TRUE(buffer.empty());
|
||||
VERIFY_ARE_EQUAL(1u, dwLines);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMatchAndCopyExeNotFound)
|
||||
{
|
||||
const auto pwszSource = L"Source";
|
||||
const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t);
|
||||
|
||||
const size_t cchTarget = 12;
|
||||
auto rgwchTarget = std::make_unique<wchar_t[]>(cchTarget);
|
||||
const auto cbTarget = cchTarget * sizeof(wchar_t);
|
||||
wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc");
|
||||
auto rgwchTargetBefore = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get());
|
||||
size_t cbTargetUsed = 0;
|
||||
const auto cbTargetUsedBefore = cbTargetUsed;
|
||||
|
||||
std::wstring exeName(L"exe.exe");
|
||||
|
||||
DWORD dwLines = 0;
|
||||
const auto dwLinesBefore = dwLines;
|
||||
|
||||
Alias::s_MatchAndCopyAliasLegacy(pwszSource,
|
||||
cbSource,
|
||||
rgwchTarget.get(),
|
||||
cbTarget,
|
||||
cbTargetUsed,
|
||||
exeName, // we didn't pre-set-up the exe name
|
||||
dwLines);
|
||||
|
||||
VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"No bytes should have been written.");
|
||||
VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string should be unmodified.");
|
||||
VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count should pass through.");
|
||||
size_t dwLines = 1;
|
||||
const std::wstring exeName(L"exe.exe");
|
||||
const auto buffer = Alias::s_MatchAndCopyAlias(pwszSource, exeName, dwLines);
|
||||
VERIFY_IS_TRUE(buffer.empty());
|
||||
VERIFY_ARE_EQUAL(1u, dwLines);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMatchAndCopyAliasNotFound)
|
||||
{
|
||||
const auto pwszSource = L"Source";
|
||||
const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t);
|
||||
|
||||
const size_t cchTarget = 12;
|
||||
auto rgwchTarget = std::make_unique<wchar_t[]>(cchTarget);
|
||||
const auto cbTarget = cchTarget * sizeof(wchar_t);
|
||||
wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc");
|
||||
auto rgwchTargetBefore = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get());
|
||||
|
||||
size_t cbTargetUsed = 0;
|
||||
const auto cbTargetUsedBefore = cbTargetUsed;
|
||||
|
||||
DWORD dwLines = 0;
|
||||
const auto dwLinesBefore = dwLines;
|
||||
size_t dwLines = 1;
|
||||
|
||||
// Register the wrong alias name before we try.
|
||||
std::wstring exe(L"exe.exe");
|
||||
@@ -279,71 +160,15 @@ class AliasTests
|
||||
std::wstring target(L"someTarget");
|
||||
Alias::s_TestAddAlias(exe, badSource, target);
|
||||
|
||||
Alias::s_MatchAndCopyAliasLegacy(pwszSource,
|
||||
cbSource,
|
||||
rgwchTarget.get(),
|
||||
cbTarget,
|
||||
cbTargetUsed,
|
||||
exe,
|
||||
dwLines);
|
||||
|
||||
VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"No bytes should be used if nothing was found.");
|
||||
VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string should be unmodified.");
|
||||
VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count should pass through.");
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMatchAndCopyTargetTooSmall)
|
||||
{
|
||||
const auto pwszSource = L"Source";
|
||||
const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t);
|
||||
|
||||
const size_t cchTarget = 12;
|
||||
auto rgwchTarget = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc");
|
||||
auto rgwchTargetBefore = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get());
|
||||
|
||||
size_t cbTargetUsed = 0;
|
||||
const auto cbTargetUsedBefore = cbTargetUsed;
|
||||
|
||||
DWORD dwLines = 0;
|
||||
const auto dwLinesBefore = dwLines;
|
||||
|
||||
// Register the correct alias name before we try.
|
||||
std::wstring exe(L"exe.exe");
|
||||
std::wstring source(pwszSource);
|
||||
std::wstring target(L"someTarget");
|
||||
Alias::s_TestAddAlias(exe, source, target);
|
||||
|
||||
Alias::s_MatchAndCopyAliasLegacy(pwszSource,
|
||||
cbSource,
|
||||
rgwchTarget.get(),
|
||||
1, // Make the target size too small
|
||||
cbTargetUsed,
|
||||
exe,
|
||||
dwLines);
|
||||
|
||||
VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"Byte count shouldn't have changed with failure.");
|
||||
VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count shouldn't have changed with failure.");
|
||||
VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string shouldn't have changed with failure.");
|
||||
const auto buffer = Alias::s_MatchAndCopyAlias(pwszSource, exe, dwLines);
|
||||
VERIFY_IS_TRUE(buffer.empty());
|
||||
VERIFY_ARE_EQUAL(1u, dwLines);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMatchAndCopyLeadingSpaces)
|
||||
{
|
||||
const auto pwszSource = L" Source";
|
||||
const auto cbSource = wcslen(pwszSource) * sizeof(wchar_t);
|
||||
|
||||
const size_t cchTarget = 12;
|
||||
auto rgwchTarget = std::make_unique<wchar_t[]>(cchTarget);
|
||||
const auto cbTarget = cchTarget * sizeof(wchar_t);
|
||||
wcscpy_s(rgwchTarget.get(), cchTarget, L"testtestabc");
|
||||
auto rgwchTargetBefore = std::make_unique<wchar_t[]>(cchTarget);
|
||||
wcscpy_s(rgwchTargetBefore.get(), cchTarget, rgwchTarget.get());
|
||||
size_t cbTargetUsed = 0;
|
||||
const auto cbTargetUsedBefore = cbTargetUsed;
|
||||
|
||||
DWORD dwLines = 0;
|
||||
const auto dwLinesBefore = dwLines;
|
||||
size_t dwLines = 1;
|
||||
|
||||
// Register the correct alias name before we try.
|
||||
std::wstring exe(L"exe.exe");
|
||||
@@ -352,40 +177,9 @@ class AliasTests
|
||||
Alias::s_TestAddAlias(exe, source, target);
|
||||
|
||||
// Leading spaces should bypass the alias. This should not match anything.
|
||||
Alias::s_MatchAndCopyAliasLegacy(pwszSource,
|
||||
cbSource,
|
||||
rgwchTarget.get(),
|
||||
cbTarget,
|
||||
cbTargetUsed,
|
||||
exe,
|
||||
dwLines);
|
||||
|
||||
VERIFY_ARE_EQUAL(cbTargetUsedBefore, cbTargetUsed, L"No bytes should be used if nothing was found.");
|
||||
VERIFY_ARE_EQUAL(String(rgwchTargetBefore.get(), cchTarget), String(rgwchTarget.get(), cchTarget), L"Target string should be unmodified.");
|
||||
VERIFY_ARE_EQUAL(dwLinesBefore, dwLines, L"Line count should pass through.");
|
||||
}
|
||||
|
||||
TEST_METHOD(TrimTrailing)
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:targetExpectedPair",
|
||||
L"{"
|
||||
L"bar%=bar," // The character % will be turned into an \r\n
|
||||
L"bar=bar"
|
||||
L"}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
std::wstring target;
|
||||
std::wstring expected;
|
||||
_RetrieveTargetExpectedPair(target, expected);
|
||||
|
||||
// Substitute %s from metadata into \r\n (since metadata can't hold \r\n)
|
||||
_ReplacePercentWithCRLF(target);
|
||||
_ReplacePercentWithCRLF(expected);
|
||||
|
||||
Alias::s_TrimTrailingCrLf(target);
|
||||
|
||||
VERIFY_ARE_EQUAL(String(expected.data()), String(target.data()));
|
||||
const auto buffer = Alias::s_MatchAndCopyAlias(pwszSource, exe, dwLines);
|
||||
VERIFY_IS_TRUE(buffer.empty());
|
||||
VERIFY_ARE_EQUAL(1u, dwLines);
|
||||
}
|
||||
|
||||
TEST_METHOD(Tokenize)
|
||||
|
||||
@@ -1,539 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#include "../cmdline.h"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
|
||||
constexpr size_t PROMPT_SIZE = 512;
|
||||
|
||||
class CommandLineTests
|
||||
{
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
CommandHistory* m_pHistory;
|
||||
|
||||
TEST_CLASS(CommandLineTests);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
m_state = std::make_unique<CommonState>();
|
||||
m_state->PrepareGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
m_state->CleanupGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
m_state->PrepareGlobalScreenBuffer();
|
||||
m_state->PrepareReadHandle();
|
||||
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
|
||||
if (!m_pHistory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// History must be prepared before COOKED_READ (as it uses s_Find to get at it)
|
||||
m_state->PrepareCookedReadData();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
CommandHistory::s_Free(nullptr);
|
||||
m_pHistory = nullptr;
|
||||
m_state->CleanupCookedReadData();
|
||||
m_state->CleanupReadHandle();
|
||||
m_state->CleanupGlobalInputBuffer();
|
||||
m_state->CleanupGlobalScreenBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
void VerifyPromptText(COOKED_READ_DATA& cookedReadData, const std::wstring wstr)
|
||||
{
|
||||
const auto span = cookedReadData.SpanWholeBuffer();
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bytesRead, wstr.size() * sizeof(wchar_t));
|
||||
VERIFY_ARE_EQUAL(wstr, (std::wstring_view{ span.data(), cookedReadData._bytesRead / sizeof(wchar_t) }));
|
||||
}
|
||||
|
||||
void InitCookedReadData(COOKED_READ_DATA& cookedReadData,
|
||||
CommandHistory* pHistory,
|
||||
wchar_t* pBuffer,
|
||||
const size_t cchBuffer)
|
||||
{
|
||||
cookedReadData._commandHistory = pHistory;
|
||||
cookedReadData._userBuffer = reinterpret_cast<char*>(pBuffer);
|
||||
cookedReadData._userBufferSize = cchBuffer * sizeof(wchar_t);
|
||||
cookedReadData._bufferSize = cchBuffer * sizeof(wchar_t);
|
||||
cookedReadData._backupLimit = pBuffer;
|
||||
cookedReadData._bufPtr = pBuffer;
|
||||
cookedReadData._exeName = L"cmd.exe";
|
||||
cookedReadData.OriginalCursorPosition() = { 0, 0 };
|
||||
}
|
||||
|
||||
void SetPrompt(COOKED_READ_DATA& cookedReadData, const std::wstring text)
|
||||
{
|
||||
std::copy(text.begin(), text.end(), cookedReadData._backupLimit);
|
||||
cookedReadData._bytesRead = text.size() * sizeof(wchar_t);
|
||||
cookedReadData._currentPosition = text.size();
|
||||
cookedReadData._bufPtr += text.size();
|
||||
cookedReadData._visibleCharCount = text.size();
|
||||
}
|
||||
|
||||
void MoveCursor(COOKED_READ_DATA& cookedReadData, const size_t column)
|
||||
{
|
||||
cookedReadData._currentPosition = column;
|
||||
cookedReadData._bufPtr = cookedReadData._backupLimit + column;
|
||||
}
|
||||
|
||||
TEST_METHOD(CanCycleCommandHistory)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 3", false));
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next);
|
||||
// should not have anything on the prompt
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bytesRead, 0u);
|
||||
|
||||
// go back one history item
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous);
|
||||
VerifyPromptText(cookedReadData, L"echo 3");
|
||||
|
||||
// try to go to the next history item, prompt shouldn't change
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next);
|
||||
VerifyPromptText(cookedReadData, L"echo 3");
|
||||
|
||||
// go back another
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous);
|
||||
VerifyPromptText(cookedReadData, L"echo 2");
|
||||
|
||||
// go forward
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next);
|
||||
VerifyPromptText(cookedReadData, L"echo 3");
|
||||
|
||||
// go back two
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous);
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous);
|
||||
VerifyPromptText(cookedReadData, L"echo 1");
|
||||
|
||||
// make sure we can't go back further
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous);
|
||||
VerifyPromptText(cookedReadData, L"echo 1");
|
||||
|
||||
// can still go forward
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next);
|
||||
VerifyPromptText(cookedReadData, L"echo 2");
|
||||
}
|
||||
|
||||
TEST_METHOD(CanSetPromptToOldestHistory)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 3", false));
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._setPromptToOldestCommand(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"echo 1");
|
||||
|
||||
// change prompt and go back to oldest
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Next);
|
||||
commandLine._setPromptToOldestCommand(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"echo 1");
|
||||
}
|
||||
|
||||
TEST_METHOD(CanSetPromptToNewestHistory)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 3", false));
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._setPromptToNewestCommand(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"echo 3");
|
||||
|
||||
// change prompt and go back to newest
|
||||
commandLine._processHistoryCycling(cookedReadData, CommandHistory::SearchDirection::Previous);
|
||||
commandLine._setPromptToNewestCommand(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"echo 3");
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDeletePromptAfterCursor)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
auto expected = L"test word blah";
|
||||
SetPrompt(cookedReadData, expected);
|
||||
VerifyPromptText(cookedReadData, expected);
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
// set current cursor position somewhere in the middle of the prompt
|
||||
MoveCursor(cookedReadData, 4);
|
||||
commandLine.DeletePromptAfterCursor(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"test");
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDeletePromptBeforeCursor)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
auto expected = L"test word blah";
|
||||
SetPrompt(cookedReadData, expected);
|
||||
VerifyPromptText(cookedReadData, expected);
|
||||
|
||||
// set current cursor position somewhere in the middle of the prompt
|
||||
MoveCursor(cookedReadData, 5);
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
const auto cursorPos = commandLine._deletePromptBeforeCursor(cookedReadData);
|
||||
cookedReadData._currentPosition = cursorPos.x;
|
||||
VerifyPromptText(cookedReadData, L"word blah");
|
||||
}
|
||||
|
||||
TEST_METHOD(CanMoveCursorToEndOfPrompt)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
auto expected = L"test word blah";
|
||||
SetPrompt(cookedReadData, expected);
|
||||
VerifyPromptText(cookedReadData, expected);
|
||||
|
||||
// make sure the cursor is not at the start of the prompt
|
||||
VERIFY_ARE_NOT_EQUAL(cookedReadData._currentPosition, 0u);
|
||||
VERIFY_ARE_NOT_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
|
||||
|
||||
// save current position for later checking
|
||||
const auto expectedCursorPos = cookedReadData._currentPosition;
|
||||
const auto expectedBufferPos = cookedReadData._bufPtr;
|
||||
|
||||
MoveCursor(cookedReadData, 0);
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
const auto cursorPos = commandLine._moveCursorToEndOfPrompt(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(cursorPos.x, gsl::narrow<til::CoordType>(expectedCursorPos));
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, expectedCursorPos);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, expectedBufferPos);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
auto expected = L"test word blah";
|
||||
SetPrompt(cookedReadData, expected);
|
||||
VerifyPromptText(cookedReadData, expected);
|
||||
|
||||
// make sure the cursor is not at the start of the prompt
|
||||
VERIFY_ARE_NOT_EQUAL(cookedReadData._currentPosition, 0u);
|
||||
VERIFY_ARE_NOT_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
const auto cursorPos = commandLine._moveCursorToStartOfPrompt(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(cursorPos.x, 0);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanMoveCursorLeftByWord)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
auto expected = L"test word blah";
|
||||
SetPrompt(cookedReadData, expected);
|
||||
VerifyPromptText(cookedReadData, expected);
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
// cursor position at beginning of "blah"
|
||||
til::CoordType expectedPos = 10;
|
||||
auto cursorPos = commandLine._moveCursorLeftByWord(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(cursorPos.x, expectedPos);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow<size_t>(expectedPos));
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos);
|
||||
|
||||
// move again
|
||||
expectedPos = 5; // before "word"
|
||||
cursorPos = commandLine._moveCursorLeftByWord(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(cursorPos.x, expectedPos);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow<size_t>(expectedPos));
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos);
|
||||
|
||||
// move again
|
||||
expectedPos = 0; // before "test"
|
||||
cursorPos = commandLine._moveCursorLeftByWord(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(cursorPos.x, expectedPos);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow<size_t>(expectedPos));
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + expectedPos);
|
||||
|
||||
// try to move again, nothing should happen
|
||||
cursorPos = commandLine._moveCursorLeftByWord(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(cursorPos.x, expectedPos);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, gsl::narrow<size_t>(expectedPos));
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit + 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);
|
||||
|
||||
const std::wstring expected = L"test word blah";
|
||||
SetPrompt(cookedReadData, expected);
|
||||
VerifyPromptText(cookedReadData, expected);
|
||||
|
||||
// move left from end of prompt text to the beginning of the prompt
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
for (auto it = expected.crbegin(); it != expected.crend(); ++it)
|
||||
{
|
||||
const auto cursorPos = commandLine._moveCursorLeft(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(*cookedReadData._bufPtr, *it);
|
||||
}
|
||||
// should now be at the start of the prompt
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
|
||||
|
||||
// try to move left a final time, nothing should change
|
||||
const auto cursorPos = commandLine._moveCursorLeft(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(cursorPos.x, 0);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, 0u);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, cookedReadData._backupLimit);
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO MSFT:11285829 tcome back and turn these on once the system cursor isn't needed
|
||||
TEST_METHOD(CanMoveCursorRightByWord)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
auto expected = L"test word blah";
|
||||
SetPrompt(cookedReadData, expected);
|
||||
VerifyPromptText(cookedReadData, expected);
|
||||
|
||||
// save current position for later checking
|
||||
const auto endCursorPos = cookedReadData._currentPosition;
|
||||
const auto endBufferPos = cookedReadData._bufPtr;
|
||||
// NOTE: need to initialize the actually cursor and keep it up to date with the changes here. remove
|
||||
once functions are fixed
|
||||
// try to move right, nothing should happen
|
||||
auto expectedPos = endCursorPos;
|
||||
auto cursorPos = MoveCursorRightByWord(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(cursorPos.x, expectedPos);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._currentPosition, expectedPos);
|
||||
VERIFY_ARE_EQUAL(cookedReadData._bufPtr, endBufferPos);
|
||||
|
||||
// move to beginning of prompt and walk to the right by word
|
||||
}
|
||||
|
||||
TEST_METHOD(CanMoveCursorRight)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDeleteFromRightOfCursor)
|
||||
{
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
TEST_METHOD(CanInsertCtrlZ)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, nullptr, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._insertCtrlZ(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"\x1a"); // ctrl-z
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDeleteCommandHistory)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 1", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 2", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"echo 3", false));
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._deleteCommandHistory(cookedReadData);
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetNumberOfCommands(), 0);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanFillPromptWithPreviousCommandFragment)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"I'm a little teapot", false));
|
||||
SetPrompt(cookedReadData, L"short and stout");
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._fillPromptWithPreviousCommandFragment(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"short and stoutapot");
|
||||
}
|
||||
|
||||
TEST_METHOD(CanCycleMatchingCommandHistory)
|
||||
{
|
||||
auto buffer = std::make_unique<wchar_t[]>(PROMPT_SIZE);
|
||||
VERIFY_IS_NOT_NULL(buffer.get());
|
||||
|
||||
auto& cookedReadData = ServiceLocator::LocateGlobals().getConsoleInformation().CookedReadData();
|
||||
InitCookedReadData(cookedReadData, m_pHistory, buffer.get(), PROMPT_SIZE);
|
||||
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"I'm a little teapot", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"short and stout", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"inflammable", false));
|
||||
VERIFY_SUCCEEDED(m_pHistory->Add(L"Indestructible", false));
|
||||
|
||||
SetPrompt(cookedReadData, L"I");
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._cycleMatchingCommandHistoryToPrompt(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"Indestructible");
|
||||
|
||||
// make sure we skip to the next "I" history item
|
||||
commandLine._cycleMatchingCommandHistoryToPrompt(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"I'm a little teapot");
|
||||
|
||||
// should cycle back to the start of the command history
|
||||
commandLine._cycleMatchingCommandHistoryToPrompt(cookedReadData);
|
||||
VerifyPromptText(cookedReadData, L"Indestructible");
|
||||
}
|
||||
|
||||
TEST_METHOD(CmdlineCtrlHomeFullwidthChars)
|
||||
{
|
||||
Log::Comment(L"Set up buffers, create cooked read data, get screen information.");
|
||||
auto buffer = std::make_unique<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);
|
||||
|
||||
Log::Comment(L"Create Japanese text string and calculate the distance we expect the cursor to move.");
|
||||
const std::wstring text(L"\x30ab\x30ac\x30ad\x30ae\x30af"); // katakana KA GA KI GI KU
|
||||
const auto bufferSize = screenInfo.GetBufferSize();
|
||||
const auto cursorBefore = screenInfo.GetTextBuffer().GetCursor().GetPosition();
|
||||
auto cursorAfterExpected = cursorBefore;
|
||||
for (size_t i = 0; i < text.length() * 2; i++)
|
||||
{
|
||||
bufferSize.IncrementInBounds(cursorAfterExpected);
|
||||
}
|
||||
|
||||
Log::Comment(L"Write the text into the buffer using the cooked read structures as if it came off of someone's input.");
|
||||
const auto written = cookedReadData.Write(text);
|
||||
VERIFY_ARE_EQUAL(text.length(), written);
|
||||
|
||||
Log::Comment(L"Retrieve the position of the cursor after insertion and check that it moved as far as we expected.");
|
||||
const auto cursorAfter = screenInfo.GetTextBuffer().GetCursor().GetPosition();
|
||||
VERIFY_ARE_EQUAL(cursorAfterExpected, cursorAfter);
|
||||
|
||||
Log::Comment(L"Walk through the screen buffer data and ensure that the text we wrote filled the cells up as we expected (2 cells per fullwidth char)");
|
||||
{
|
||||
auto cellIterator = screenInfo.GetCellDataAt(cursorBefore);
|
||||
for (size_t i = 0; i < text.length() * 2; i++)
|
||||
{
|
||||
// Our original string was 5 wide characters which we expected to take 10 cells.
|
||||
// Therefore each index of the original string will be used twice ( divide by 2 ).
|
||||
const auto expectedTextValue = text.at(i / 2);
|
||||
const String expectedText(&expectedTextValue, 1);
|
||||
|
||||
const auto actualTextValue = cellIterator->Chars();
|
||||
const String actualText(actualTextValue.data(), gsl::narrow<int>(actualTextValue.size()));
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedText, actualText);
|
||||
cellIterator++;
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"Now perform the command that is triggered with Ctrl+Home keys normally to erase the entire edit line.");
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._deletePromptBeforeCursor(cookedReadData);
|
||||
|
||||
Log::Comment(L"Check that the entire span of the buffer where we had the fullwidth text is now cleared out and full of blanks (nothing left behind).");
|
||||
{
|
||||
auto cursorPos = cursorBefore;
|
||||
auto cellIterator = screenInfo.GetCellDataAt(cursorPos);
|
||||
|
||||
while (Utils::s_CompareCoords(cursorPos, cursorAfter) < 0)
|
||||
{
|
||||
const String expectedText(L"\x20"); // unicode space character
|
||||
|
||||
const auto actualTextValue = cellIterator->Chars();
|
||||
const String actualText(actualTextValue.data(), gsl::narrow<int>(actualTextValue.size()));
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedText, actualText);
|
||||
cellIterator++;
|
||||
|
||||
bufferSize.IncrementInBounds(cursorPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,538 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#include "../CommandListPopup.hpp"
|
||||
#include "PopupTestHelper.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
static constexpr size_t BUFFER_SIZE = 256;
|
||||
static constexpr UINT s_NumberOfHistoryBuffers = 4;
|
||||
static constexpr UINT s_HistoryBufferSize = 50;
|
||||
|
||||
class CommandListPopupTests
|
||||
{
|
||||
TEST_CLASS(CommandListPopupTests);
|
||||
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
CommandHistory* m_pHistory;
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
m_state = std::make_unique<CommonState>();
|
||||
m_state->PrepareGlobalFont();
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.SetNumberOfHistoryBuffers(s_NumberOfHistoryBuffers);
|
||||
gci.SetHistoryBufferSize(s_HistoryBufferSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
m_state->CleanupGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
m_state->PrepareGlobalScreenBuffer();
|
||||
m_state->PrepareReadHandle();
|
||||
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
|
||||
// resize command history storage to 50 items so that we don't cycle on accident
|
||||
// when PopupTestHelper::InitLongHistory() is called.
|
||||
CommandHistory::s_ResizeAll(50);
|
||||
if (!m_pHistory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// History must be prepared before COOKED_READ (as it uses s_Find to get at it)
|
||||
m_state->PrepareCookedReadData();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
CommandHistory::s_Free(nullptr);
|
||||
m_pHistory = nullptr;
|
||||
m_state->CleanupCookedReadData();
|
||||
m_state->CleanupReadHandle();
|
||||
m_state->CleanupGlobalInputBuffer();
|
||||
m_state->CleanupGlobalScreenBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
void InitReadData(COOKED_READ_DATA& cookedReadData,
|
||||
wchar_t* const pBuffer,
|
||||
const size_t cchBuffer,
|
||||
const size_t cursorPosition)
|
||||
{
|
||||
cookedReadData._bufferSize = cchBuffer * sizeof(wchar_t);
|
||||
cookedReadData._bufPtr = pBuffer + cursorPosition;
|
||||
cookedReadData._backupLimit = pBuffer;
|
||||
cookedReadData.OriginalCursorPosition() = { 0, 0 };
|
||||
cookedReadData._bytesRead = cursorPosition * sizeof(wchar_t);
|
||||
cookedReadData._currentPosition = cursorPosition;
|
||||
cookedReadData.VisibleCharCount() = cursorPosition;
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDismiss)
|
||||
{
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
wch = VK_ESCAPE;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
const std::wstring testString = L"hello world";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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));
|
||||
|
||||
// popup has been dismissed
|
||||
VERIFY_IS_FALSE(CommandLine::Instance().HasPopup());
|
||||
}
|
||||
|
||||
TEST_METHOD(UpMovesSelection)
|
||||
{
|
||||
// function to simulate user pressing up arrow
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
wch = VK_UP;
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
}
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
const auto commandNumberBefore = popup._currentCommand;
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
// selection should have moved up one line
|
||||
VERIFY_ARE_EQUAL(commandNumberBefore - 1, popup._currentCommand);
|
||||
}
|
||||
|
||||
TEST_METHOD(DownMovesSelection)
|
||||
{
|
||||
// function to simulate user pressing down arrow
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
wch = VK_DOWN;
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
}
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
// set the current command selection to the top of the list
|
||||
popup._currentCommand = 0;
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
const auto commandNumberBefore = popup._currentCommand;
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
// selection should have moved down one line
|
||||
VERIFY_ARE_EQUAL(commandNumberBefore + 1, popup._currentCommand);
|
||||
}
|
||||
|
||||
TEST_METHOD(EndMovesSelectionToEnd)
|
||||
{
|
||||
// function to simulate user pressing end key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
wch = VK_END;
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
}
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
// set the current command selection to the top of the list
|
||||
popup._currentCommand = 0;
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
// selection should have moved to the bottom line
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetNumberOfCommands() - 1, popup._currentCommand);
|
||||
}
|
||||
|
||||
TEST_METHOD(HomeMovesSelectionToStart)
|
||||
{
|
||||
// function to simulate user pressing home key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
wch = VK_HOME;
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
}
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
// selection should have moved to the bottom line
|
||||
VERIFY_ARE_EQUAL(0, popup._currentCommand);
|
||||
}
|
||||
|
||||
TEST_METHOD(PageUpMovesSelection)
|
||||
{
|
||||
// function to simulate user pressing page up key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
wch = VK_PRIOR;
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
}
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitLongHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
// selection should have moved up a page
|
||||
VERIFY_ARE_EQUAL(static_cast<til::CoordType>(m_pHistory->GetNumberOfCommands()) - popup.Height() - 1, popup._currentCommand);
|
||||
}
|
||||
|
||||
TEST_METHOD(PageDownMovesSelection)
|
||||
{
|
||||
// function to simulate user pressing page down key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
wch = VK_NEXT;
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
}
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitLongHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
// set the current command selection to the top of the list
|
||||
popup._currentCommand = 0;
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
// selection should have moved up a page
|
||||
VERIFY_ARE_EQUAL(popup.Height(), popup._currentCommand);
|
||||
}
|
||||
|
||||
TEST_METHOD(SideArrowsFillsPrompt)
|
||||
{
|
||||
// function to simulate user pressing right arrow key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
wch = VK_RIGHT;
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
// set the current command selection to the top of the list
|
||||
popup._currentCommand = 0;
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
// prompt should have history item in prompt
|
||||
const auto historyItem = m_pHistory->GetLastCommand();
|
||||
const std::wstring_view resultText{ buffer, historyItem.size() };
|
||||
VERIFY_ARE_EQUAL(historyItem, resultText);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanLaunchCommandNumberPopup)
|
||||
{
|
||||
// function to simulate user pressing F9
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
wch = VK_F9;
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
VERIFY_IS_FALSE(commandLine.HasPopup());
|
||||
// should spawn a CommandNumberPopup
|
||||
auto scopeExit = wil::scope_exit([&]() { commandLine.EndAllPopups(); });
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT));
|
||||
VERIFY_IS_TRUE(commandLine.HasPopup());
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDeleteFromCommandHistory)
|
||||
{
|
||||
// function to simulate user pressing the delete key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
wch = VK_DELETE;
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
}
|
||||
popupKey = true;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
const auto startHistorySize = m_pHistory->GetNumberOfCommands();
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetNumberOfCommands(), startHistorySize - 1);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanReorderHistoryUp)
|
||||
{
|
||||
// function to simulate user pressing shift + up arrow
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto firstTime = true;
|
||||
if (firstTime)
|
||||
{
|
||||
wch = VK_UP;
|
||||
firstTime = false;
|
||||
modifiers = SHIFT_PRESSED;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
modifiers = 0;
|
||||
}
|
||||
popupKey = true;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my spout");
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my handle");
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetNth(2), L"here is my spout");
|
||||
}
|
||||
|
||||
TEST_METHOD(CanReorderHistoryDown)
|
||||
{
|
||||
// function to simulate user pressing the up arrow, then shift + down arrow, then escape
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static unsigned int count = 0;
|
||||
if (count == 0)
|
||||
{
|
||||
wch = VK_UP;
|
||||
modifiers = 0;
|
||||
}
|
||||
else if (count == 1)
|
||||
{
|
||||
wch = VK_DOWN;
|
||||
modifiers = SHIFT_PRESSED;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = VK_ESCAPE;
|
||||
modifiers = 0;
|
||||
}
|
||||
popupKey = true;
|
||||
++count;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
CommandListPopup popup{ gci.GetActiveOutputBuffer(), *m_pHistory };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my spout");
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetLastCommand(), L"here is my handle");
|
||||
VERIFY_ARE_EQUAL(m_pHistory->GetNth(2), L"here is my spout");
|
||||
}
|
||||
};
|
||||
@@ -1,297 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
#include "PopupTestHelper.hpp"
|
||||
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#include "../CommandNumberPopup.hpp"
|
||||
#include "../CommandListPopup.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
|
||||
static constexpr size_t BUFFER_SIZE = 256;
|
||||
|
||||
class CommandNumberPopupTests
|
||||
{
|
||||
TEST_CLASS(CommandNumberPopupTests);
|
||||
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
CommandHistory* m_pHistory;
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
m_state = std::make_unique<CommonState>();
|
||||
m_state->PrepareGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
m_state->CleanupGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
m_state->PrepareGlobalScreenBuffer();
|
||||
m_state->PrepareReadHandle();
|
||||
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
|
||||
if (!m_pHistory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// History must be prepared before COOKED_READ (as it uses s_Find to get at it)
|
||||
m_state->PrepareCookedReadData();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
CommandHistory::s_Free(nullptr);
|
||||
m_pHistory = nullptr;
|
||||
m_state->CleanupCookedReadData();
|
||||
m_state->CleanupReadHandle();
|
||||
m_state->CleanupGlobalInputBuffer();
|
||||
m_state->CleanupGlobalScreenBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDismiss)
|
||||
{
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
wch = VK_ESCAPE;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CommandNumberPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
const std::wstring testString = L"hello world";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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));
|
||||
|
||||
// popup has been dismissed
|
||||
VERIFY_IS_FALSE(CommandLine::Instance().HasPopup());
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDismissAllPopups)
|
||||
{
|
||||
Log::Comment(L"that that all popups are dismissed when CommandNumberPopup is dismissed");
|
||||
// CommandNumberPopup is the only popup that can act as a 2nd popup. make sure that it dismisses all
|
||||
// popups when exiting
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
wch = VK_ESCAPE;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// add popups to CommandLine
|
||||
auto& commandLine = CommandLine::Instance();
|
||||
commandLine._popups.emplace_front(std::make_unique<CommandListPopup>(gci.GetActiveOutputBuffer(), *m_pHistory));
|
||||
commandLine._popups.emplace_front(std::make_unique<CommandNumberPopup>(gci.GetActiveOutputBuffer()));
|
||||
auto& numberPopup = *commandLine._popups.front();
|
||||
numberPopup.SetUserInputFunction(fn);
|
||||
|
||||
VERIFY_ARE_EQUAL(commandLine._popups.size(), 2u);
|
||||
|
||||
// prepare cookedReadData
|
||||
const std::wstring testString = L"hello world";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(numberPopup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
VERIFY_IS_FALSE(commandLine.HasPopup());
|
||||
}
|
||||
|
||||
TEST_METHOD(EmptyInputCountsAsOldestHistory)
|
||||
{
|
||||
Log::Comment(L"hitting enter with no input should grab the oldest history item");
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = false;
|
||||
wch = UNICODE_CARRIAGERETURN;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CommandNumberPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
|
||||
// the buffer should contain the least recent history item
|
||||
|
||||
const auto expected = m_pHistory->GetLastCommand();
|
||||
const std::wstring resultString(buffer, buffer + expected.size());
|
||||
VERIFY_ARE_EQUAL(expected, resultString);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanSelectHistoryItem)
|
||||
{
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
for (CommandHistory::Index historyIndex = 0; historyIndex < m_pHistory->GetNumberOfCommands(); ++historyIndex)
|
||||
{
|
||||
Popup::UserInputFunction fn = [historyIndex](COOKED_READ_DATA& /*cookedReadData*/,
|
||||
bool& popupKey,
|
||||
DWORD& modifiers,
|
||||
wchar_t& wch) {
|
||||
static auto needReturn = false;
|
||||
popupKey = false;
|
||||
modifiers = 0;
|
||||
if (!needReturn)
|
||||
{
|
||||
const auto str = std::to_string(historyIndex);
|
||||
wch = str.at(0);
|
||||
needReturn = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = UNICODE_CARRIAGERETURN;
|
||||
needReturn = false;
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CommandNumberPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(LargeNumberGrabsNewestHistoryItem)
|
||||
{
|
||||
Log::Comment(L"entering a number larger than the number of history items should grab the most recent history item");
|
||||
|
||||
// simulates user pressing 1, 2, 3, 4, 5, enter
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
static auto num = 1;
|
||||
popupKey = false;
|
||||
modifiers = 0;
|
||||
if (num <= 5)
|
||||
{
|
||||
const auto str = std::to_string(num);
|
||||
wch = str.at(0);
|
||||
++num;
|
||||
}
|
||||
else
|
||||
{
|
||||
wch = UNICODE_CARRIAGERETURN;
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CommandNumberPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0);
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
|
||||
// the buffer should contain the most recent history item
|
||||
|
||||
const auto expected = m_pHistory->GetLastCommand();
|
||||
const std::wstring resultString(buffer, buffer + expected.size());
|
||||
VERIFY_ARE_EQUAL(expected, resultString);
|
||||
}
|
||||
|
||||
TEST_METHOD(InputIsLimited)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
CommandNumberPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
|
||||
// input can't delete past zero number input
|
||||
popup._pop();
|
||||
VERIFY_ARE_EQUAL(popup._parse(), 0);
|
||||
|
||||
// input can only be numbers
|
||||
VERIFY_THROWS_SPECIFIC(popup._push(L'$'), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
|
||||
VERIFY_THROWS_SPECIFIC(popup._push(L'A'), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
|
||||
|
||||
// input can't be more than 5 numbers
|
||||
popup._push(L'1');
|
||||
VERIFY_ARE_EQUAL(popup._parse(), 1);
|
||||
popup._push(L'2');
|
||||
VERIFY_ARE_EQUAL(popup._parse(), 12);
|
||||
popup._push(L'3');
|
||||
VERIFY_ARE_EQUAL(popup._parse(), 123);
|
||||
popup._push(L'4');
|
||||
VERIFY_ARE_EQUAL(popup._parse(), 1234);
|
||||
popup._push(L'5');
|
||||
VERIFY_ARE_EQUAL(popup._parse(), 12345);
|
||||
// this shouldn't affect the parsed number
|
||||
popup._push(L'6');
|
||||
VERIFY_ARE_EQUAL(popup._parse(), 12345);
|
||||
// make sure we can delete input correctly
|
||||
popup._pop();
|
||||
VERIFY_ARE_EQUAL(popup._parse(), 1234);
|
||||
}
|
||||
};
|
||||
@@ -1,165 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
#include "PopupTestHelper.hpp"
|
||||
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#include "../CopyFromCharPopup.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
|
||||
static constexpr size_t BUFFER_SIZE = 256;
|
||||
|
||||
class CopyFromCharPopupTests
|
||||
{
|
||||
TEST_CLASS(CopyFromCharPopupTests);
|
||||
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
CommandHistory* m_pHistory;
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
m_state = std::make_unique<CommonState>();
|
||||
m_state->PrepareGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
m_state->CleanupGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
m_state->PrepareGlobalScreenBuffer();
|
||||
m_state->PrepareReadHandle();
|
||||
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
|
||||
if (!m_pHistory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// History must be prepared before COOKED_READ (as it uses s_Find to get at it)
|
||||
m_state->PrepareCookedReadData();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
CommandHistory::s_Free(nullptr);
|
||||
m_pHistory = nullptr;
|
||||
m_state->CleanupCookedReadData();
|
||||
m_state->CleanupReadHandle();
|
||||
m_state->CleanupGlobalInputBuffer();
|
||||
m_state->CleanupGlobalScreenBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDismiss)
|
||||
{
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
wch = VK_ESCAPE;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CopyFromCharPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
std::wstring testString = L"hello world";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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));
|
||||
|
||||
// popup has been dismissed
|
||||
VERIFY_IS_FALSE(CommandLine::Instance().HasPopup());
|
||||
}
|
||||
|
||||
TEST_METHOD(DeleteAllWhenCharNotFound)
|
||||
{
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = false;
|
||||
wch = L'x';
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CopyFromCharPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
std::wstring testString = L"hello world";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
// move cursor to beginning of prompt text
|
||||
cookedReadData.InsertionPoint() = 0;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK));
|
||||
|
||||
// all text to the right of the cursor should be gone
|
||||
VERIFY_ARE_EQUAL(cookedReadData.BytesRead(), 0u);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDeletePartialLine)
|
||||
{
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = false;
|
||||
wch = L'f';
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CopyFromCharPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
std::wstring testString = L"By the rude bridge that arched the flood";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
// move cursor to index 12
|
||||
const size_t index = 12;
|
||||
cookedReadData.SetBufferCurrentPtr(buffer + index);
|
||||
cookedReadData.InsertionPoint() = index;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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);
|
||||
}
|
||||
};
|
||||
@@ -1,243 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
#include "PopupTestHelper.hpp"
|
||||
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#include "../CopyToCharPopup.hpp"
|
||||
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
static constexpr size_t BUFFER_SIZE = 256;
|
||||
|
||||
class CopyToCharPopupTests
|
||||
{
|
||||
TEST_CLASS(CopyToCharPopupTests);
|
||||
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
CommandHistory* m_pHistory;
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
m_state = std::make_unique<CommonState>();
|
||||
m_state->PrepareGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
m_state->CleanupGlobalFont();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
m_state->PrepareGlobalScreenBuffer();
|
||||
m_state->PrepareReadHandle();
|
||||
m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
|
||||
if (!m_pHistory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// History must be prepared before COOKED_READ (as it uses s_Find to get at it)
|
||||
m_state->PrepareCookedReadData();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
CommandHistory::s_Free(nullptr);
|
||||
m_pHistory = nullptr;
|
||||
m_state->CleanupCookedReadData();
|
||||
m_state->CleanupReadHandle();
|
||||
m_state->CleanupGlobalInputBuffer();
|
||||
m_state->CleanupGlobalScreenBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(CanDismiss)
|
||||
{
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
wch = VK_ESCAPE;
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CopyToCharPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
const std::wstring testString = L"hello world";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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));
|
||||
|
||||
// popup has been dismissed
|
||||
VERIFY_IS_FALSE(CommandLine::Instance().HasPopup());
|
||||
}
|
||||
|
||||
TEST_METHOD(NothingHappensWhenCharNotFound)
|
||||
{
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
wch = L'x';
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CopyToCharPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0u);
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanCopyToEmptyPrompt)
|
||||
{
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
wch = L's';
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CopyToCharPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), 0u);
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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);
|
||||
}
|
||||
|
||||
TEST_METHOD(WontCopyTextBeforeCursor)
|
||||
{
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
wch = L's';
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CopyToCharPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData with a string longer than the previous history
|
||||
const std::wstring testString = L"Whose woods there are I think I know.";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
const wchar_t* const expectedBufPtr = cookedReadData._bufPtr;
|
||||
const auto expectedBytesRead = cookedReadData._bytesRead;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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);
|
||||
}
|
||||
|
||||
TEST_METHOD(CanMergeLine)
|
||||
{
|
||||
// function to simulate user pressing escape key
|
||||
Popup::UserInputFunction fn = [](COOKED_READ_DATA& /*cookedReadData*/, bool& popupKey, DWORD& modifiers, wchar_t& wch) {
|
||||
popupKey = true;
|
||||
wch = L's';
|
||||
modifiers = 0;
|
||||
return STATUS_SUCCESS;
|
||||
};
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// prepare popup
|
||||
CopyToCharPopup popup{ gci.GetActiveOutputBuffer() };
|
||||
popup.SetUserInputFunction(fn);
|
||||
|
||||
// prepare cookedReadData with a string longer than the previous history
|
||||
const std::wstring testString = L"fear ";
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
std::fill(std::begin(buffer), std::end(buffer), UNICODE_SPACE);
|
||||
std::copy(testString.begin(), testString.end(), std::begin(buffer));
|
||||
auto& cookedReadData = gci.CookedReadData();
|
||||
PopupTestHelper::InitReadData(cookedReadData, buffer, ARRAYSIZE(buffer), testString.size());
|
||||
PopupTestHelper::InitHistory(*m_pHistory);
|
||||
cookedReadData._commandHistory = m_pHistory;
|
||||
|
||||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<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);
|
||||
}
|
||||
};
|
||||
@@ -15,12 +15,7 @@
|
||||
<ClCompile Include="ApiRoutinesTests.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="DbcsTests.cpp" />
|
||||
<ClCompile Include="HistoryTests.cpp" />
|
||||
<ClCompile Include="InitTests.cpp" />
|
||||
@@ -96,7 +91,6 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\inc\CommonState.hpp" />
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
<ClInclude Include="PopupTestHelper.hpp" />
|
||||
<ClInclude Include="UnicodeLiteral.hpp" />
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup>
|
||||
|
||||
@@ -75,27 +75,12 @@
|
||||
<ClCompile Include="CodepointWidthDetectorTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CommandLineTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CommandNumberPopupTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CopyFromCharPopupTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CopyToCharPopupTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TextBufferIteratorTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ViewportTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CommandListPopupTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OutputCellIteratorTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -116,9 +101,6 @@
|
||||
<ClInclude Include="..\..\inc\CommonState.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PopupTestHelper.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*++
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- PopupTestHelper.hpp
|
||||
|
||||
Abstract:
|
||||
- helper functions for unit testing the various popups
|
||||
|
||||
Author(s):
|
||||
- Austin Diviness (AustDi) 06-Sep-2018
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../history.h"
|
||||
#include "../readDataCooked.hpp"
|
||||
|
||||
class PopupTestHelper final
|
||||
{
|
||||
public:
|
||||
static void InitReadData(COOKED_READ_DATA& cookedReadData,
|
||||
wchar_t* const pBuffer,
|
||||
const size_t cchBuffer,
|
||||
const size_t cursorPosition) noexcept
|
||||
{
|
||||
cookedReadData._bufferSize = cchBuffer * sizeof(wchar_t);
|
||||
cookedReadData._bufPtr = pBuffer + cursorPosition;
|
||||
cookedReadData._backupLimit = pBuffer;
|
||||
cookedReadData.OriginalCursorPosition() = { 0, 0 };
|
||||
cookedReadData._bytesRead = cursorPosition * sizeof(wchar_t);
|
||||
cookedReadData._currentPosition = cursorPosition;
|
||||
cookedReadData.VisibleCharCount() = cursorPosition;
|
||||
}
|
||||
|
||||
static void InitHistory(CommandHistory& history) noexcept
|
||||
{
|
||||
history.Empty();
|
||||
history.Flags |= CommandHistory::CLE_ALLOCATED;
|
||||
VERIFY_SUCCEEDED(history.Add(L"I'm a little teapot", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"hear me shout", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"here is my handle", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"here is my spout", false));
|
||||
VERIFY_ARE_EQUAL(history.GetNumberOfCommands(), 4);
|
||||
}
|
||||
|
||||
static void InitLongHistory(CommandHistory& history) noexcept
|
||||
{
|
||||
history.Empty();
|
||||
history.Flags |= CommandHistory::CLE_ALLOCATED;
|
||||
VERIFY_SUCCEEDED(history.Add(L"Because I could not stop for Death", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"He kindly stopped for me", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"The carriage held but just Ourselves", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"And Immortality", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"~", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"We slowly drove - He knew no haste", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"And I had put away", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"My labor and my leisure too", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"For His Civility", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"~", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"We passed the School, where Children strove", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"At Recess - in the Ring", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"We passed the Fields of Gazing Grain", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"We passed the Setting Sun", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"~", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"Or rather - He passed us,", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"The Dews drew quivering and chill,", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"For only Gossamer, my Gown,", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"My Tippet - only Tulle", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"~", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"We paused before a House that seemed", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"A Swelling of the Ground -", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"The Roof was scarcely visible -", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"The Cornice - in the Ground -", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"~", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"Since then - 'tis Centuries - and yet", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"Feels shorter than the Day", false));
|
||||
VERIFY_SUCCEEDED(history.Add(L"~ Emily Dickinson", false));
|
||||
VERIFY_ARE_EQUAL(history.GetNumberOfCommands(), 28);
|
||||
}
|
||||
};
|
||||
@@ -2895,15 +2895,11 @@ void ScreenBufferTests::BackspaceDefaultAttrsWriteCharsLegacy()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:writeSingly", L"{false, true}")
|
||||
TEST_METHOD_PROPERTY(L"Data:writeCharsLegacyMode", L"{0, 1, 2}")
|
||||
END_TEST_METHOD_PROPERTIES();
|
||||
|
||||
bool writeSingly;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"writeSingly", writeSingly), L"Write one at a time = true, all at the same time = false");
|
||||
|
||||
DWORD writeCharsLegacyMode;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"writeCharsLegacyMode", writeCharsLegacyMode), L"");
|
||||
|
||||
// Created for MSFT:19735050.
|
||||
// Kinda the same as above, but with WriteCharsLegacy instead.
|
||||
// The variable that really breaks this scenario
|
||||
@@ -2931,18 +2927,13 @@ void ScreenBufferTests::BackspaceDefaultAttrsWriteCharsLegacy()
|
||||
|
||||
if (writeSingly)
|
||||
{
|
||||
auto str = L"X";
|
||||
size_t seqCb = 2;
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, writeCharsLegacyMode, nullptr));
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, writeCharsLegacyMode, nullptr));
|
||||
str = L"\x08";
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, writeCharsLegacyMode, nullptr));
|
||||
WriteCharsLegacy(si, L"X", false, nullptr);
|
||||
WriteCharsLegacy(si, L"X", false, nullptr);
|
||||
WriteCharsLegacy(si, L"\x08", false, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto str = L"XX\x08";
|
||||
size_t seqCb = 6;
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, writeCharsLegacyMode, nullptr));
|
||||
WriteCharsLegacy(si, L"XX\x08", false, nullptr);
|
||||
}
|
||||
|
||||
TextAttribute expectedDefaults{};
|
||||
@@ -7191,8 +7182,7 @@ void ScreenBufferTests::UpdateVirtualBottomWhenCursorMovesBelowIt()
|
||||
|
||||
Log::Comment(L"Now write several lines of content using WriteCharsLegacy");
|
||||
const auto content = L"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n";
|
||||
auto numBytes = wcslen(content) * sizeof(wchar_t);
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, content, content, content, &numBytes, nullptr, 0, 0, nullptr));
|
||||
WriteCharsLegacy(si, content, false, nullptr);
|
||||
|
||||
Log::Comment(L"Confirm that the cursor position has moved down 10 lines");
|
||||
const auto newCursorPos = til::point{ initialCursorPos.x, initialCursorPos.y + 10 };
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "globals.h"
|
||||
|
||||
#include "selection.hpp"
|
||||
#include "cmdline.h"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
@@ -382,89 +381,6 @@ class SelectionInputTests
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(TestGetInputLineBoundaries)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// 80x80 box
|
||||
const til::CoordType sRowWidth = 80;
|
||||
|
||||
til::inclusive_rect srectEdges;
|
||||
srectEdges.left = srectEdges.top = 0;
|
||||
srectEdges.right = srectEdges.bottom = sRowWidth - 1;
|
||||
|
||||
// false when no cooked read data exists
|
||||
VERIFY_IS_FALSE(gci.HasPendingCookedRead());
|
||||
|
||||
auto fResult = Selection::s_GetInputLineBoundaries(nullptr, nullptr);
|
||||
VERIFY_IS_FALSE(fResult);
|
||||
|
||||
// prepare some read data
|
||||
m_state->PrepareReadHandle();
|
||||
auto cleanupReadHandle = wil::scope_exit([&]() { m_state->CleanupReadHandle(); });
|
||||
|
||||
m_state->PrepareCookedReadData();
|
||||
// set up to clean up read data later
|
||||
auto cleanupCookedRead = wil::scope_exit([&]() { m_state->CleanupCookedReadData(); });
|
||||
|
||||
auto& readData = gci.CookedReadData();
|
||||
|
||||
// backup text info position over remainder of text execution duration
|
||||
auto& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer();
|
||||
til::point coordOldTextInfoPos;
|
||||
coordOldTextInfoPos.x = textBuffer.GetCursor().GetPosition().x;
|
||||
coordOldTextInfoPos.y = textBuffer.GetCursor().GetPosition().y;
|
||||
|
||||
// set various cursor positions
|
||||
readData.OriginalCursorPosition().x = 15;
|
||||
readData.OriginalCursorPosition().y = 3;
|
||||
|
||||
readData.VisibleCharCount() = 200;
|
||||
|
||||
textBuffer.GetCursor().SetXPosition(35);
|
||||
textBuffer.GetCursor().SetYPosition(35);
|
||||
|
||||
// try getting boundaries with no pointers. parameters should be fully optional.
|
||||
fResult = Selection::s_GetInputLineBoundaries(nullptr, nullptr);
|
||||
VERIFY_IS_TRUE(fResult);
|
||||
|
||||
// now let's get some actual data
|
||||
til::point coordStart;
|
||||
til::point coordEnd;
|
||||
|
||||
fResult = Selection::s_GetInputLineBoundaries(&coordStart, &coordEnd);
|
||||
VERIFY_IS_TRUE(fResult);
|
||||
|
||||
// starting position/boundary should always be where the input line started
|
||||
VERIFY_ARE_EQUAL(coordStart.x, readData.OriginalCursorPosition().x);
|
||||
VERIFY_ARE_EQUAL(coordStart.y, readData.OriginalCursorPosition().y);
|
||||
|
||||
// ending position can vary. it's in one of two spots
|
||||
// 1. If the original cooked cursor was valid (which it was this first time), it's NumberOfVisibleChars ahead.
|
||||
til::point coordFinalPos;
|
||||
|
||||
const auto cCharsToAdjust = ((til::CoordType)readData.VisibleCharCount() - 1); // then -1 to be on the last piece of text, not past it
|
||||
|
||||
coordFinalPos.x = (readData.OriginalCursorPosition().x + cCharsToAdjust) % sRowWidth;
|
||||
coordFinalPos.y = readData.OriginalCursorPosition().y + ((readData.OriginalCursorPosition().x + cCharsToAdjust) / sRowWidth);
|
||||
|
||||
VERIFY_ARE_EQUAL(coordEnd.x, coordFinalPos.x);
|
||||
VERIFY_ARE_EQUAL(coordEnd.y, coordFinalPos.y);
|
||||
|
||||
// 2. if the original cooked cursor is invalid, then it's the text info cursor position
|
||||
readData.OriginalCursorPosition().x = -1;
|
||||
readData.OriginalCursorPosition().y = -1;
|
||||
|
||||
fResult = Selection::s_GetInputLineBoundaries(nullptr, &coordEnd);
|
||||
VERIFY_IS_TRUE(fResult);
|
||||
|
||||
VERIFY_ARE_EQUAL(coordEnd.x, textBuffer.GetCursor().GetPosition().x - 1); // -1 to be on the last piece of text, not past it
|
||||
VERIFY_ARE_EQUAL(coordEnd.y, textBuffer.GetCursor().GetPosition().y);
|
||||
|
||||
// restore text buffer info position
|
||||
textBuffer.GetCursor().SetXPosition(coordOldTextInfoPos.x);
|
||||
textBuffer.GetCursor().SetYPosition(coordOldTextInfoPos.y);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestWordByWordPrevious)
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
|
||||
@@ -1554,60 +1554,20 @@ void TextBufferTests::TestBackspaceStringsAPI()
|
||||
// should be the same.
|
||||
std::unique_ptr<WriteData> waiter;
|
||||
|
||||
size_t aCb = 2;
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(L"a", &aCb, si, false, waiter));
|
||||
|
||||
size_t seqCb = 6;
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Using WriteCharsLegacy, write \\b \\b as a single string."));
|
||||
{
|
||||
const auto str = L"\b \b";
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr));
|
||||
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0);
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Using DoWriteConsole, write \\b \\b as a single string."));
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(L"a", &aCb, si, false, waiter));
|
||||
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(str, &seqCb, si, false, waiter));
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0);
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0);
|
||||
}
|
||||
size_t aCb = 2;
|
||||
size_t seqCb = 6;
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(L"a", &aCb, si, false, waiter));
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(L"\b \b", &seqCb, si, false, waiter));
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0);
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0);
|
||||
|
||||
seqCb = 2;
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Using DoWriteConsole, write \\b \\b as separate strings."));
|
||||
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(L"a", &seqCb, si, false, waiter));
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(L"\b", &seqCb, si, false, waiter));
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(L" ", &seqCb, si, false, waiter));
|
||||
VERIFY_SUCCEEDED(DoWriteConsole(L"\b", &seqCb, si, false, waiter));
|
||||
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0);
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Using WriteCharsLegacy, write \\b \\b as separate strings."));
|
||||
{
|
||||
const auto str = L"a";
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr));
|
||||
}
|
||||
{
|
||||
const auto str = L"\b";
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr));
|
||||
}
|
||||
{
|
||||
const auto str = L" ";
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr));
|
||||
}
|
||||
{
|
||||
const auto str = L"\b";
|
||||
VERIFY_NT_SUCCESS(WriteCharsLegacy(si, str, str, str, &seqCb, nullptr, cursor.GetPosition().x, 0, nullptr));
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().x, x0);
|
||||
VERIFY_ARE_EQUAL(cursor.GetPosition().y, y0);
|
||||
}
|
||||
|
||||
@@ -37,11 +37,6 @@ SOURCES = \
|
||||
ConptyOutputTests.cpp \
|
||||
ViewportTests.cpp \
|
||||
ConsoleArgumentsTests.cpp \
|
||||
CommandLineTests.cpp \
|
||||
CommandListPopupTests.cpp \
|
||||
CommandNumberPopupTests.cpp \
|
||||
CopyFromCharPopupTests.cpp \
|
||||
CopyToCharPopupTests.cpp \
|
||||
ObjectTests.cpp \
|
||||
DefaultResource.rc \
|
||||
|
||||
|
||||
@@ -507,13 +507,7 @@ void Menu::s_PropertiesUpdate(PCONSOLE_STATE_INFO pStateInfo)
|
||||
if (coordBuffer.width != coordScreenBufferSize.width ||
|
||||
coordBuffer.height != coordScreenBufferSize.height)
|
||||
{
|
||||
const auto pCommandLine = &CommandLine::Instance();
|
||||
|
||||
pCommandLine->Hide(FALSE);
|
||||
|
||||
LOG_IF_FAILED(ScreenInfo.ResizeScreenBuffer(coordBuffer, TRUE));
|
||||
|
||||
pCommandLine->Show();
|
||||
}
|
||||
|
||||
// Finally, restrict window size to the maximum possible size for the given buffer now that it's processed.
|
||||
|
||||
@@ -397,7 +397,7 @@ void HandleKeyEvent(const HWND hWnd,
|
||||
}
|
||||
}
|
||||
// we need to check if there is an active popup because otherwise they won't be able to receive shift+key events
|
||||
if (pSelection->s_IsValidKeyboardLineSelection(&inputKeyInfo) && IsInProcessedInputMode() && gci.PopupCount.load() == 0)
|
||||
if (pSelection->s_IsValidKeyboardLineSelection(&inputKeyInfo) && IsInProcessedInputMode() && !gci.HasPendingPopup())
|
||||
{
|
||||
if (!bKeyDown || pSelection->HandleKeyboardLineSelectionEvent(&inputKeyInfo))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user