80da24ecf8
We were using std::basic_string_view as a stand-in for std::span so that
we could change over all at once when C++20 dropped with full span
support. That day's not here yet, but as of 54a7fce3e
we're using GSL 3,
whose span is C++20-compliant.
This commit replaces every instance of basic_string_view that was not
referring to an actual string with a span of the appropriate type.
I moved the `const` qualifier into span's `T` because while
`basic_string_view.at()` returns `const T&`, `span.at()` returns `T&`
(without the const). I wanted to maintain the invariant that members of
the span were immutable.
* Mechanical Changes
* `sv.at(x)` -> `gsl::at(sp, x)`
* `sv.c{begin,end}` -> `sp.{begin,end}` (span's iterators are const)
I had to replace a `std::basic_string<>` with a `std::vector<>` in
ConImeInfo, and I chose to replace a manual array walk in
ScreenInfoUiaProviderBase with a ranged-for. Please review those
specifically.
This will almost certainly cause a code size regression in Windows
because I'm blowing out all the PGO counts. Whoops.
Related: #3956, #975.
348 lines
13 KiB
C++
348 lines
13 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "_output.h"
|
|
|
|
#include "dbcs.h"
|
|
#include "handle.h"
|
|
#include "misc.h"
|
|
|
|
#include "../buffer/out/CharRow.hpp"
|
|
|
|
#include "../interactivity/inc/ServiceLocator.hpp"
|
|
#include "../types/inc/Viewport.hpp"
|
|
#include "../types/inc/convert.hpp"
|
|
#include "../types/inc/Utf16Parser.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
#pragma hdrstop
|
|
|
|
using namespace Microsoft::Console::Types;
|
|
using Microsoft::Console::Interactivity::ServiceLocator;
|
|
|
|
// Routine Description:
|
|
// - This routine writes a screen buffer region to the screen.
|
|
// Arguments:
|
|
// - screenInfo - reference to screen buffer information.
|
|
// - srRegion - Region to write in screen buffer coordinates. Region is inclusive
|
|
// Return Value:
|
|
// - <none>
|
|
void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
|
|
{
|
|
DBGOUTPUT(("WriteToScreen\n"));
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
// update to screen, if we're not iconic.
|
|
if (!screenInfo.IsActiveScreenBuffer() || WI_IsFlagSet(gci.Flags, CONSOLE_IS_ICONIC))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// clip region to fit within the viewport
|
|
const auto clippedRegion = screenInfo.GetViewport().Clamp(region);
|
|
if (!clippedRegion.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (screenInfo.IsActiveScreenBuffer())
|
|
{
|
|
if (ServiceLocator::LocateGlobals().pRender != nullptr)
|
|
{
|
|
ServiceLocator::LocateGlobals().pRender->TriggerRedraw(region);
|
|
}
|
|
}
|
|
|
|
WriteConvRegionToScreen(screenInfo, region);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - writes text attributes to the screen
|
|
// Arguments:
|
|
// - OutContext - the screen info to write to
|
|
// - attrs - the attrs to write to the screen
|
|
// - target - the starting coordinate in the screen
|
|
// - used - number of elements written
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG or similar HRESULT error.
|
|
[[nodiscard]] HRESULT ApiRoutines::WriteConsoleOutputAttributeImpl(IConsoleOutputObject& OutContext,
|
|
const gsl::span<const WORD> attrs,
|
|
const COORD target,
|
|
size_t& used) noexcept
|
|
{
|
|
// Set used to 0 from the beginning in case we exit early.
|
|
used = 0;
|
|
|
|
if (attrs.empty())
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
auto& screenInfo = OutContext.GetActiveBuffer();
|
|
const auto bufferSize = screenInfo.GetBufferSize();
|
|
if (!bufferSize.IsInBounds(target))
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
const OutputCellIterator it(attrs);
|
|
const auto done = screenInfo.Write(it, target);
|
|
|
|
used = done.GetCellDistance(it);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - writes text to the screen
|
|
// Arguments:
|
|
// - screenInfo - the screen info to write to
|
|
// - chars - the text to write to the screen
|
|
// - target - the starting coordinate in the screen
|
|
// - used - number of elements written
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG or similar HRESULT error.
|
|
[[nodiscard]] HRESULT ApiRoutines::WriteConsoleOutputCharacterWImpl(IConsoleOutputObject& OutContext,
|
|
const std::wstring_view chars,
|
|
const COORD target,
|
|
size_t& used) noexcept
|
|
{
|
|
// Set used to 0 from the beginning in case we exit early.
|
|
used = 0;
|
|
|
|
if (chars.empty())
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
auto& screenInfo = OutContext.GetActiveBuffer();
|
|
const auto bufferSize = screenInfo.GetBufferSize();
|
|
if (!bufferSize.IsInBounds(target))
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
try
|
|
{
|
|
OutputCellIterator it(chars);
|
|
const auto finished = screenInfo.Write(it, target);
|
|
used = finished.GetInputDistance(it);
|
|
}
|
|
CATCH_RETURN();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - writes text to the screen
|
|
// Arguments:
|
|
// - screenInfo - the screen info to write to
|
|
// - chars - the text to write to the screen
|
|
// - target - the starting coordinate in the screen
|
|
// - used - number of elements written
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG or similar HRESULT error.
|
|
[[nodiscard]] HRESULT ApiRoutines::WriteConsoleOutputCharacterAImpl(IConsoleOutputObject& OutContext,
|
|
const std::string_view chars,
|
|
const COORD target,
|
|
size_t& used) noexcept
|
|
{
|
|
// Set used to 0 from the beginning in case we exit early.
|
|
used = 0;
|
|
|
|
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
const auto codepage = gci.OutputCP;
|
|
try
|
|
{
|
|
// convert to wide chars so we can call the W version of this function
|
|
const auto wideChars = ConvertToW(codepage, chars);
|
|
|
|
size_t wideCharsWritten = 0;
|
|
RETURN_IF_FAILED(WriteConsoleOutputCharacterWImpl(OutContext, wideChars, target, wideCharsWritten));
|
|
|
|
// Create a view over the wide chars and reduce it to the amount actually written (do in two steps to enforce bounds)
|
|
std::wstring_view writtenView(wideChars);
|
|
writtenView = writtenView.substr(0, wideCharsWritten);
|
|
|
|
// Look over written wide chars to find equivalent count of ascii chars so we can properly report back
|
|
// how many elements were actually written
|
|
used = GetALengthFromW(codepage, writtenView);
|
|
}
|
|
CATCH_RETURN();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - fills the screen buffer with the specified text attribute
|
|
// Arguments:
|
|
// - OutContext - reference to screen buffer information.
|
|
// - attribute - the text attribute to use to fill
|
|
// - lengthToWrite - the number of elements to write
|
|
// - startingCoordinate - Screen buffer coordinate to begin writing to.
|
|
// - cellsModified - the number of elements written
|
|
// Return Value:
|
|
// - S_OK or suitable HRESULT code from failure to write (memory issues, invalid arg, etc.)
|
|
[[nodiscard]] HRESULT ApiRoutines::FillConsoleOutputAttributeImpl(IConsoleOutputObject& OutContext,
|
|
const WORD attribute,
|
|
const size_t lengthToWrite,
|
|
const COORD startingCoordinate,
|
|
size_t& cellsModified) noexcept
|
|
{
|
|
// Set modified cells to 0 from the beginning.
|
|
cellsModified = 0;
|
|
|
|
if (lengthToWrite == 0)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
auto& screenBuffer = OutContext.GetActiveBuffer();
|
|
const auto bufferSize = screenBuffer.GetBufferSize();
|
|
if (!bufferSize.IsInBounds(startingCoordinate))
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
try
|
|
{
|
|
TextAttribute useThisAttr(attribute);
|
|
const OutputCellIterator it(useThisAttr, lengthToWrite);
|
|
const auto done = screenBuffer.Write(it, startingCoordinate);
|
|
|
|
cellsModified = done.GetCellDistance(it);
|
|
|
|
// Notify accessibility
|
|
auto endingCoordinate = startingCoordinate;
|
|
bufferSize.MoveInBounds(cellsModified, endingCoordinate);
|
|
screenBuffer.NotifyAccessibilityEventing(startingCoordinate.X, startingCoordinate.Y, endingCoordinate.X, endingCoordinate.Y);
|
|
}
|
|
CATCH_RETURN();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - fills the screen buffer with the specified wchar
|
|
// Arguments:
|
|
// - OutContext - reference to screen buffer information.
|
|
// - character - wchar to fill with
|
|
// - lengthToWrite - the number of elements to write
|
|
// - startingCoordinate - Screen buffer coordinate to begin writing to.
|
|
// - cellsModified - the number of elements written
|
|
// - enablePowershellShim - true iff the client process that's calling this
|
|
// method is "powershell.exe". Used to enable certain compatibility shims for
|
|
// conpty mode. See GH#3126.
|
|
// Return Value:
|
|
// - S_OK or suitable HRESULT code from failure to write (memory issues, invalid arg, etc.)
|
|
[[nodiscard]] HRESULT ApiRoutines::FillConsoleOutputCharacterWImpl(IConsoleOutputObject& OutContext,
|
|
const wchar_t character,
|
|
const size_t lengthToWrite,
|
|
const COORD startingCoordinate,
|
|
size_t& cellsModified,
|
|
const bool enablePowershellShim) noexcept
|
|
{
|
|
// Set modified cells to 0 from the beginning.
|
|
cellsModified = 0;
|
|
|
|
if (lengthToWrite == 0)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
// TODO: does this even need to be here or will it exit quickly?
|
|
auto& screenInfo = OutContext.GetActiveBuffer();
|
|
const auto bufferSize = screenInfo.GetBufferSize();
|
|
if (!bufferSize.IsInBounds(startingCoordinate))
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
try
|
|
{
|
|
const OutputCellIterator it(character, lengthToWrite);
|
|
|
|
// when writing to the buffer, specifically unset wrap if we get to the last column.
|
|
// a fill operation should UNSET wrap in that scenario. See GH #1126 for more details.
|
|
const auto done = screenInfo.Write(it, startingCoordinate, false);
|
|
cellsModified = done.GetInputDistance(it);
|
|
|
|
// Notify accessibility
|
|
auto endingCoordinate = startingCoordinate;
|
|
bufferSize.MoveInBounds(cellsModified, endingCoordinate);
|
|
screenInfo.NotifyAccessibilityEventing(startingCoordinate.X, startingCoordinate.Y, endingCoordinate.X, endingCoordinate.Y);
|
|
|
|
// GH#3126 - This is a shim for powershell's `Clear-Host` function. In
|
|
// the vintage console, `Clear-Host` is supposed to clear the entire
|
|
// buffer. In conpty however, there's no difference between the viewport
|
|
// and the entirety of the buffer. We're going to see if this API call
|
|
// exactly matched the way we expect powershell to call it. If it does,
|
|
// then let's manually emit a ^[[3J to the connected terminal, so that
|
|
// their entire buffer will be cleared as well.
|
|
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
if (enablePowershellShim && gci.IsInVtIoMode())
|
|
{
|
|
const til::size currentBufferDimensions{ screenInfo.GetBufferSize().Dimensions() };
|
|
|
|
const bool wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area<size_t>());
|
|
const bool startedAtOrigin = startingCoordinate == COORD{ 0, 0 };
|
|
const bool wroteSpaces = character == UNICODE_SPACE;
|
|
|
|
if (wroteWholeBuffer && startedAtOrigin && wroteSpaces)
|
|
{
|
|
hr = gci.GetVtIo()->ManuallyClearScrollback();
|
|
}
|
|
}
|
|
}
|
|
CATCH_RETURN();
|
|
|
|
return hr;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - fills the screen buffer with the specified char
|
|
// Arguments:
|
|
// - OutContext - reference to screen buffer information.
|
|
// - character - ascii character to fill with
|
|
// - lengthToWrite - the number of elements to write
|
|
// - startingCoordinate - Screen buffer coordinate to begin writing to.
|
|
// - cellsModified - the number of elements written
|
|
// Return Value:
|
|
// - S_OK or suitable HRESULT code from failure to write (memory issues, invalid arg, etc.)
|
|
[[nodiscard]] HRESULT ApiRoutines::FillConsoleOutputCharacterAImpl(IConsoleOutputObject& OutContext,
|
|
const char character,
|
|
const size_t lengthToWrite,
|
|
const COORD startingCoordinate,
|
|
size_t& cellsModified) noexcept
|
|
try
|
|
{
|
|
// In case ConvertToW throws causing an early return, set modified cells to 0.
|
|
cellsModified = 0;
|
|
|
|
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
// convert to wide chars and call W version
|
|
const auto wchs = ConvertToW(gci.OutputCP, { &character, 1 });
|
|
|
|
LOG_HR_IF(E_UNEXPECTED, wchs.size() > 1);
|
|
|
|
return FillConsoleOutputCharacterWImpl(OutContext, wchs.at(0), lengthToWrite, startingCoordinate, cellsModified);
|
|
}
|
|
CATCH_RETURN()
|