mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-12 08:40:37 -06:00
`TextBuffer::GenHTML` and `TextBuffer::GenRTF` now read directly from the TextBuffer. - Since we're reading from the buffer, we can now read _all_ the attributes saved in the buffer. Formatted copy now copies most (if not all) font/color attributes in the requested format (RTF/HTML). - Use `TextBuffer::CopyRequest` to pass all copy-related options into text generation functions as one unit. - Helper function `TextBuffer::CopyRequest::FromConfig()` generates a copy request based on Selection mode and user configuration. - Both formatted text generation functions now use `std::string` and `fmt::format_to` to generate the required strings. Previously, we were using `std::ostringstream` which is not recommended due to its potential overhead. - Reading attributes from `ROW`'s attribute RLE simplified the logic as we don't have to track attribute change between the text. - On the caller side, we do not have to rebuild the plain text string from the vector of strings anymore. `TextBuffer::GetPlainText()` returns the entire text as one `std::string`. - Removed `TextBuffer::TextAndColors`. - Removed `TextBuffer::GetText()`. `TextBuffer::GetPlainText()` took its place. This PR also fixes two bugs in the formatted copy: - We were applying line breaks after each selected row, even though the row could have been a Wrapped row. This caused the wrapped rows to break when they shouldn't. - We mishandled Unicode text (\uN) within the RTF copy. Every next character that uses a surrogate pair or high codepoint was missing in the copied text when pasted to MSWord. The command `\uc4` should have been `\uc1`, which is used to tell how many fallback characters are used for each Unicode codepoint (\u). We always use one `?` character as the fallback. Closes #16191 **References and Relevant Issues** - #16270 **Validation Steps Performed** - Casual copy-pasting from Terminal or OpenConsole to word editors works as before. - Verified HTML copy by copying the generated HTML string and running it through an HTML viewer. [Sample](https://codepen.io/tusharvickey/pen/wvNXbVN) - Verified RTF copy by copy-pasting the generated RTF string into MSWord. - SingleLine mode works (<kbd>Shift</kbd>+ copy) - BlockSelection mode works (<kbd>Alt</kbd> selection)
306 lines
11 KiB
C++
306 lines
11 KiB
C++
/*++
|
|
|
|
Copyright (c) Microsoft Corporation.
|
|
Licensed under the MIT license.
|
|
|
|
Module Name:
|
|
- CommonState.hpp
|
|
|
|
Abstract:
|
|
- This represents common boilerplate state setup required for unit tests to run
|
|
|
|
Author(s):
|
|
- Michael Niksa (miniksa) 18-Jun-2014
|
|
- Paul Campbell (paulcam) 18-Jun-2014
|
|
|
|
Revision History:
|
|
- Transformed to header-only class so it can be included by multiple
|
|
unit testing projects in the codebase without a bunch of overhead.
|
|
--*/
|
|
|
|
#pragma once
|
|
|
|
#include "../host/globals.h"
|
|
#include "../host/inputReadHandleData.h"
|
|
#include "../interactivity/inc/ServiceLocator.hpp"
|
|
|
|
class CommonState
|
|
{
|
|
public:
|
|
static const til::CoordType s_csWindowWidth = 80;
|
|
static const til::CoordType s_csWindowHeight = 80;
|
|
static const til::CoordType s_csBufferWidth = 80;
|
|
static const til::CoordType s_csBufferHeight = 300;
|
|
|
|
CommonState() :
|
|
m_heap(GetProcessHeap()),
|
|
m_hrTextBufferInfo(E_FAIL),
|
|
m_pFontInfo(nullptr),
|
|
m_backupTextBufferInfo(),
|
|
m_readHandle(nullptr)
|
|
{
|
|
}
|
|
|
|
~CommonState()
|
|
{
|
|
m_heap = nullptr;
|
|
}
|
|
|
|
void InitEvents()
|
|
{
|
|
Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().hInputEvent.create(wil::EventOptions::ManualReset);
|
|
}
|
|
|
|
void PrepareReadHandle()
|
|
{
|
|
m_readHandle = std::make_unique<INPUT_READ_HANDLE_DATA>();
|
|
}
|
|
|
|
void CleanupReadHandle()
|
|
{
|
|
m_readHandle.reset(nullptr);
|
|
}
|
|
|
|
void PrepareGlobalFont(const til::size coordFontSize = { 8, 12 })
|
|
{
|
|
m_pFontInfo = new FontInfo(L"Consolas", 0, 0, coordFontSize, 0);
|
|
}
|
|
|
|
void CleanupGlobalFont()
|
|
{
|
|
if (m_pFontInfo != nullptr)
|
|
{
|
|
delete m_pFontInfo;
|
|
}
|
|
}
|
|
|
|
void PrepareGlobalRenderer()
|
|
{
|
|
Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
g.pRender = new Microsoft::Console::Render::Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
|
|
}
|
|
|
|
void CleanupGlobalRenderer()
|
|
{
|
|
Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
|
|
delete g.pRender;
|
|
g.pRender = nullptr;
|
|
}
|
|
|
|
void PrepareGlobalScreenBuffer(const til::CoordType viewWidth = s_csWindowWidth,
|
|
const til::CoordType viewHeight = s_csWindowHeight,
|
|
const til::CoordType bufferWidth = s_csBufferWidth,
|
|
const til::CoordType bufferHeight = s_csBufferHeight)
|
|
{
|
|
Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
til::size coordWindowSize;
|
|
coordWindowSize.width = viewWidth;
|
|
coordWindowSize.height = viewHeight;
|
|
|
|
til::size coordScreenBufferSize;
|
|
coordScreenBufferSize.width = bufferWidth;
|
|
coordScreenBufferSize.height = bufferHeight;
|
|
|
|
UINT uiCursorSize = 12;
|
|
|
|
THROW_IF_FAILED(SCREEN_INFORMATION::CreateInstance(coordWindowSize,
|
|
*m_pFontInfo,
|
|
coordScreenBufferSize,
|
|
TextAttribute{},
|
|
TextAttribute{ FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED },
|
|
uiCursorSize,
|
|
&gci.pCurrentScreenBuffer));
|
|
|
|
// If we have a renderer, we need to call EnablePainting to initialize
|
|
// the viewport. If not, we mark the text buffer as inactive so that it
|
|
// doesn't try to trigger a redraw on a nonexistent renderer.
|
|
if (g.pRender)
|
|
{
|
|
g.pRender->EnablePainting();
|
|
}
|
|
else
|
|
{
|
|
gci.pCurrentScreenBuffer->_textBuffer->SetAsActiveBuffer(false);
|
|
}
|
|
}
|
|
|
|
void CleanupGlobalScreenBuffer()
|
|
{
|
|
const CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
delete gci.pCurrentScreenBuffer;
|
|
}
|
|
|
|
void PrepareGlobalInputBuffer()
|
|
{
|
|
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.pInputBuffer = new InputBuffer();
|
|
}
|
|
|
|
void CleanupGlobalInputBuffer()
|
|
{
|
|
const CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
delete gci.pInputBuffer;
|
|
}
|
|
|
|
void PrepareCookedReadData(const std::wstring_view initialData = {})
|
|
{
|
|
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
auto* readData = new COOKED_READ_DATA(gci.pInputBuffer,
|
|
m_readHandle.get(),
|
|
gci.GetActiveOutputBuffer(),
|
|
0,
|
|
nullptr,
|
|
0,
|
|
L"",
|
|
initialData,
|
|
nullptr);
|
|
gci.SetCookedReadData(readData);
|
|
}
|
|
|
|
void CleanupCookedReadData()
|
|
{
|
|
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
delete &gci.CookedReadData();
|
|
gci.SetCookedReadData(nullptr);
|
|
}
|
|
|
|
void PrepareNewTextBufferInfo(const bool useDefaultAttributes = false,
|
|
const til::CoordType bufferWidth = s_csBufferWidth,
|
|
const til::CoordType bufferHeight = s_csBufferHeight)
|
|
{
|
|
Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
til::size coordScreenBufferSize;
|
|
coordScreenBufferSize.width = bufferWidth;
|
|
coordScreenBufferSize.height = bufferHeight;
|
|
|
|
UINT uiCursorSize = 12;
|
|
|
|
auto initialAttributes = useDefaultAttributes ? TextAttribute{} :
|
|
TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY };
|
|
|
|
m_backupTextBufferInfo.swap(gci.pCurrentScreenBuffer->_textBuffer);
|
|
try
|
|
{
|
|
std::unique_ptr<TextBuffer> textBuffer = std::make_unique<TextBuffer>(coordScreenBufferSize,
|
|
initialAttributes,
|
|
uiCursorSize,
|
|
true,
|
|
*g.pRender);
|
|
if (textBuffer.get() == nullptr)
|
|
{
|
|
m_hrTextBufferInfo = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
m_hrTextBufferInfo = S_OK;
|
|
}
|
|
gci.pCurrentScreenBuffer->_textBuffer.swap(textBuffer);
|
|
|
|
// If we have a renderer, we need to call EnablePainting to initialize
|
|
// the viewport. If not, we mark the text buffer as inactive so that it
|
|
// doesn't try to trigger a redraw on a nonexistent renderer.
|
|
if (g.pRender)
|
|
{
|
|
g.pRender->EnablePainting();
|
|
}
|
|
else
|
|
{
|
|
gci.pCurrentScreenBuffer->_textBuffer->SetAsActiveBuffer(false);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
m_hrTextBufferInfo = wil::ResultFromCaughtException();
|
|
}
|
|
}
|
|
|
|
void CleanupNewTextBufferInfo()
|
|
{
|
|
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
VERIFY_IS_TRUE(gci.HasActiveOutputBuffer());
|
|
|
|
gci.pCurrentScreenBuffer->_textBuffer.swap(m_backupTextBufferInfo);
|
|
}
|
|
|
|
void FillTextBuffer()
|
|
{
|
|
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
// fill with some assorted text that doesn't consume the whole row
|
|
const til::CoordType cRowsToFill = 4;
|
|
|
|
VERIFY_IS_TRUE(gci.HasActiveOutputBuffer());
|
|
|
|
TextBuffer& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer();
|
|
|
|
for (til::CoordType iRow = 0; iRow < cRowsToFill; iRow++)
|
|
{
|
|
ROW& row = textBuffer.GetMutableRowByOffset(iRow);
|
|
FillRow(&row, iRow & 1);
|
|
}
|
|
|
|
textBuffer.GetCursor().SetYPosition(cRowsToFill);
|
|
}
|
|
|
|
[[nodiscard]] HRESULT GetTextBufferInfoInitResult()
|
|
{
|
|
return m_hrTextBufferInfo;
|
|
}
|
|
|
|
private:
|
|
HANDLE m_heap;
|
|
HRESULT m_hrTextBufferInfo;
|
|
FontInfo* m_pFontInfo;
|
|
std::unique_ptr<TextBuffer> m_backupTextBufferInfo;
|
|
std::unique_ptr<INPUT_READ_HANDLE_DATA> m_readHandle;
|
|
|
|
void FillRow(ROW* pRow, bool wrapForced)
|
|
{
|
|
// fill a row
|
|
// - Each row is populated with L"AB\u304bC\u304dDE "
|
|
// - 7 characters, 6 spaces. 13 total
|
|
// - The characters take up first 9 columns. (The wide glyphs take up 2 columns each)
|
|
// - か = \x304b, き = \x304d
|
|
|
|
uint16_t column = 0;
|
|
for (const auto& ch : std::wstring_view{ L"AB\u304bC\u304dDE " })
|
|
{
|
|
const uint16_t width = ch >= 0x80 ? 2 : 1;
|
|
pRow->ReplaceCharacters(column, width, { &ch, 1 });
|
|
column += width;
|
|
}
|
|
|
|
// A = bright red on dark gray
|
|
// This string starts at index 0
|
|
auto Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
|
|
pRow->SetAttrToEnd(0, Attr);
|
|
|
|
// BかC = dark gold on bright blue
|
|
// This string starts at index 1
|
|
Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY);
|
|
pRow->SetAttrToEnd(1, Attr);
|
|
|
|
// き = bright white on dark purple
|
|
// This string starts at index 5
|
|
Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE);
|
|
pRow->SetAttrToEnd(5, Attr);
|
|
|
|
// DE = black on dark green
|
|
// This string starts at index 7
|
|
Attr = TextAttribute(BACKGROUND_GREEN);
|
|
pRow->SetAttrToEnd(7, Attr);
|
|
|
|
// odd rows forced a wrap
|
|
if (wrapForced)
|
|
{
|
|
pRow->SetWrapForced(true);
|
|
}
|
|
else
|
|
{
|
|
pRow->SetWrapForced(false);
|
|
}
|
|
}
|
|
};
|