Migrate Search module as a shared component for Terminal Search (#3279)

* Make search a shared component for conhost and terminal

* Remove inclusion of deprecated interface file

* Code review changes, remove text buffer modification in Terminal

* remove unreferenced objects to fix build errors

* Fix test failure, guarantee uiaData object is correctly initialized in Search

* minor comment typo fix and format fix

* minor PR comments change

* ColorSeclection directly throw and return

* remove coordAnchor initialization

* minor method signature change
This commit is contained in:
Kaiyu Wang 2019-11-14 14:36:41 -08:00 committed by GitHub
parent 6a4c737686
commit ebdcfbd940
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 124 additions and 74 deletions

View file

@ -19,6 +19,7 @@
<ClCompile Include="..\OutputCellView.cpp" />
<ClCompile Include="..\Row.cpp" />
<ClCompile Include="..\RowCellIterator.cpp" />
<ClCompile Include="..\search.cpp" />
<ClCompile Include="..\TextColor.cpp" />
<ClCompile Include="..\TextAttribute.cpp" />
<ClCompile Include="..\TextAttributeRun.cpp" />
@ -45,6 +46,7 @@
<ClInclude Include="..\OutputCellView.hpp" />
<ClInclude Include="..\Row.hpp" />
<ClInclude Include="..\RowCellIterator.hpp" />
<ClInclude Include="..\search.h" />
<ClInclude Include="..\TextColor.h" />
<ClInclude Include="..\TextAttribute.h" />
<ClInclude Include="..\TextAttributeRun.h" />

View file

@ -5,29 +5,32 @@
#include "search.h"
#include "dbcs.h"
#include "../buffer/out/CharRow.hpp"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/Utf16Parser.hpp"
#include "../types/inc/GlyphWidth.hpp"
using namespace Microsoft::Console::Types;
// Routine Description:
// - Constructs a Search object.
// - Make a Search object then call .FindNext() to locate items.
// - Once you've found something, you can perfom actions like .Select() or .Color()
// Arguments:
// - screenInfo - The screen buffer to search through (the "haystack")
// - textBuffer - The screen text buffer to search through (the "haystack")
// - uiaData - The IUiaData type reference, it is for providing selection methods
// - str - The search term you want to find (the "needle")
// - direction - The direction to search (upward or downward)
// - sensitivity - Whether or not you care about case
Search::Search(const SCREEN_INFORMATION& screenInfo,
Search::Search(IUiaData& uiaData,
const std::wstring& str,
const Direction direction,
const Sensitivity sensitivity) :
_direction(direction),
_sensitivity(sensitivity),
_screenInfo(screenInfo),
_needle(s_CreateNeedleFromString(str)),
_coordAnchor(s_GetInitialAnchor(screenInfo, direction))
_uiaData(uiaData),
_coordAnchor(s_GetInitialAnchor(uiaData, direction))
{
_coordNext = _coordAnchor;
}
@ -37,21 +40,22 @@ Search::Search(const SCREEN_INFORMATION& screenInfo,
// - Make a Search object then call .FindNext() to locate items.
// - Once you've found something, you can perfom actions like .Select() or .Color()
// Arguments:
// - screenInfo - The screen buffer to search through (the "haystack")
// - textBuffer - The screen text buffer to search through (the "haystack")
// - uiaData - The IUiaData type reference, it is for providing selection methods
// - str - The search term you want to find (the "needle")
// - direction - The direction to search (upward or downward)
// - sensitivity - Whether or not you care about case
// - anchor - starting search location in screenInfo
Search::Search(const SCREEN_INFORMATION& screenInfo,
Search::Search(IUiaData& uiaData,
const std::wstring& str,
const Direction direction,
const Sensitivity sensitivity,
const COORD anchor) :
_direction(direction),
_sensitivity(sensitivity),
_screenInfo(screenInfo),
_needle(s_CreateNeedleFromString(str)),
_coordAnchor(anchor)
_coordAnchor(anchor),
_uiaData(uiaData)
{
_coordNext = _coordAnchor;
}
@ -96,12 +100,13 @@ void Search::Select() const
// Only select if we've found something.
if (_coordSelStart != _coordSelEnd)
{
Selection::Instance().SelectNewRegion(_coordSelStart, _coordSelEnd);
_uiaData.SelectNewRegion(_coordSelStart, _coordSelEnd);
}
}
// Routine Description:
// - Takes the found word and applies the given color to it in the screen buffer
// - In console host, we take the found word and apply the given color to it in the screen buffer
// - In Windows Terminal, we just select the found word, but we do not modify the buffer
// Arguments:
// - ulAttr - The legacy color attribute to apply to the word
void Search::Color(const TextAttribute attr) const
@ -109,7 +114,7 @@ void Search::Color(const TextAttribute attr) const
// Only select if we've found something.
if (_coordSelStart != _coordSelEnd)
{
Selection::Instance().ColorSelection(_coordSelStart, _coordSelEnd, attr);
_uiaData.ColorSelection(_coordSelStart, _coordSelEnd, attr);
}
}
@ -130,22 +135,23 @@ std::pair<COORD, COORD> Search::GetFoundLocation() const noexcept
// - If the screen buffer given already has a selection in it, it will be used to determine the anchor.
// - Otherwise, we will choose one of the ends of the screen buffer depending on direction.
// Arguments:
// - screenInfo - The screen buffer for determining the anchor
// - uiaData - The reference to the IUiaData interface type object
// - direction - The intended direction of the search
// Return Value:
// - Coordinate to start the search from.
COORD Search::s_GetInitialAnchor(const SCREEN_INFORMATION& screenInfo, const Direction direction)
COORD Search::s_GetInitialAnchor(IUiaData& uiaData, const Direction direction)
{
if (Selection::Instance().IsInSelectingState())
const auto& textBuffer = uiaData.GetTextBuffer();
if (uiaData.IsSelectionActive())
{
auto anchor = Selection::Instance().GetSelectionAnchor();
auto anchor = uiaData.GetSelectionAnchor();
if (direction == Direction::Forward)
{
screenInfo.GetBufferSize().IncrementInBoundsCircular(anchor);
textBuffer.GetSize().IncrementInBoundsCircular(anchor);
}
else
{
screenInfo.GetBufferSize().DecrementInBoundsCircular(anchor);
textBuffer.GetSize().DecrementInBoundsCircular(anchor);
}
return anchor;
}
@ -157,7 +163,7 @@ COORD Search::s_GetInitialAnchor(const SCREEN_INFORMATION& screenInfo, const Dir
}
else
{
const auto bufferSize = screenInfo.GetBufferSize().Dimensions();
const auto bufferSize = textBuffer.GetSize().Dimensions();
return { bufferSize.X - 1, bufferSize.Y - 1 };
}
}
@ -183,7 +189,7 @@ bool Search::_FindNeedleInHaystackAt(const COORD pos, COORD& start, COORD& end)
for (const auto& needleCell : _needle)
{
// Haystack is the buffer. Needle is the string we were given.
const auto hayIter = _screenInfo.GetTextDataAt(bufferPos);
const auto hayIter = _uiaData.GetTextBuffer().GetTextDataAt(bufferPos);
const auto hayChars = *hayIter;
const auto needleChars = std::wstring_view(needleCell.data(), needleCell.size());
@ -214,7 +220,7 @@ bool Search::_FindNeedleInHaystackAt(const COORD pos, COORD& start, COORD& end)
// - two - String view representing the second string of text
// Return Value:
// - True if they are the same. False otherwise.
bool Search::_CompareChars(const std::wstring_view one, const std::wstring_view two) const
bool Search::_CompareChars(const std::wstring_view one, const std::wstring_view two) const noexcept
{
if (one.size() != two.size())
{
@ -223,7 +229,7 @@ bool Search::_CompareChars(const std::wstring_view one, const std::wstring_view
for (size_t i = 0; i < one.size(); i++)
{
if (_ApplySensitivity(one[i]) != _ApplySensitivity(two[i]))
if (_ApplySensitivity(one.at(i)) != _ApplySensitivity(two.at(i)))
{
return false;
}
@ -239,7 +245,7 @@ bool Search::_CompareChars(const std::wstring_view one, const std::wstring_view
// - wch - Character to adjust if necessary
// Return Value:
// - Adjusted value (or not).
wchar_t Search::_ApplySensitivity(const wchar_t wch) const
wchar_t Search::_ApplySensitivity(const wchar_t wch) const noexcept
{
if (_sensitivity == Sensitivity::CaseInsensitive)
{
@ -257,7 +263,7 @@ wchar_t Search::_ApplySensitivity(const wchar_t wch) const
// - coord - Updated by function to increment one position (will wrap X and Y direction)
void Search::_IncrementCoord(COORD& coord) const
{
_screenInfo.GetBufferSize().IncrementInBoundsCircular(coord);
_uiaData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord);
}
// Routine Description:
@ -266,7 +272,7 @@ void Search::_IncrementCoord(COORD& coord) const
// - coord - Updated by function to decrement one position (will wrap X and Y direction)
void Search::_DecrementCoord(COORD& coord) const
{
_screenInfo.GetBufferSize().DecrementInBoundsCircular(coord);
_uiaData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord);
}
// Routine Description:

View file

@ -17,6 +17,11 @@ Revision History:
#pragma once
#include <WinConTypes.h>
#include "TextAttribute.hpp"
#include "textBuffer.hpp"
#include "../types/IUiaData.h"
// This used to be in find.h.
#define SEARCH_STRING_LENGTH (80)
@ -35,12 +40,12 @@ public:
CaseSensitive
};
Search(const SCREEN_INFORMATION& ScreenInfo,
Search(Microsoft::Console::Types::IUiaData& uiaData,
const std::wstring& str,
const Direction dir,
const Sensitivity sensitivity);
Search(const SCREEN_INFORMATION& ScreenInfo,
Search(Microsoft::Console::Types::IUiaData& uiaData,
const std::wstring& str,
const Direction dir,
const Sensitivity sensitivity,
@ -53,15 +58,16 @@ public:
std::pair<COORD, COORD> GetFoundLocation() const noexcept;
private:
wchar_t _ApplySensitivity(const wchar_t wch) const;
wchar_t _ApplySensitivity(const wchar_t wch) const noexcept;
bool Search::_FindNeedleInHaystackAt(const COORD pos, COORD& start, COORD& end) const;
bool _CompareChars(const std::wstring_view one, const std::wstring_view two) const;
bool _CompareChars(const std::wstring_view one, const std::wstring_view two) const noexcept;
void _UpdateNextPosition();
void _IncrementCoord(COORD& coord) const;
void _DecrementCoord(COORD& coord) const;
static COORD s_GetInitialAnchor(const SCREEN_INFORMATION& screenInfo, const Direction dir);
static COORD s_GetInitialAnchor(Microsoft::Console::Types::IUiaData& uiaData, const Direction dir);
static std::vector<std::vector<wchar_t>> s_CreateNeedleFromString(const std::wstring& wstr);
bool _reachedEnd = false;
@ -73,7 +79,7 @@ private:
const std::vector<std::vector<wchar_t>> _needle;
const Direction _direction;
const Sensitivity _sensitivity;
const SCREEN_INFORMATION& _screenInfo;
Microsoft::Console::Types::IUiaData& _uiaData;
#ifdef UNIT_TESTING
friend class SearchTests;

View file

@ -48,6 +48,7 @@ SOURCES= \
..\CharRowCell.cpp \
..\CharRowCellReference.cpp \
..\UnicodeStorage.cpp \
..\search.cpp \
INCLUDES= \
$(INCLUDES); \

View file

@ -127,7 +127,9 @@ public:
const bool IsSelectionActive() const noexcept;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const override;
const std::wstring GetConsoleTitle() const noexcept override;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
#pragma endregion
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;

View file

@ -107,6 +107,17 @@ SMALL_RECT Terminal::_GetSelectionRow(const SHORT row, const COORD higherCoord,
return selectionRow;
}
// Method Description:
// - Get the current anchor position
// Arguments:
// - None
// Return Value:
// - None
const COORD Terminal::GetSelectionAnchor() const
{
return _selectionAnchor;
}
// Method Description:
// - Expand the selection row according to selection mode and wide glyphs
// - this is particularly useful for box selections (ALT + selection)
@ -455,3 +466,15 @@ COORD Terminal::_ConvertToBufferCell(const COORD viewportPos) const
THROW_IF_FAILED(ShortAdd(positionWithOffsets.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &positionWithOffsets.Y));
return positionWithOffsets;
}
// Method Description:
// - This method won't be used. We just throw and do nothing. For now we
// need this method to implement UiaData interface
// Arguments:
// - coordSelectionStart - Not used
// - coordSelectionEnd - Not used
// - attr - Not used.
void Terminal::ColorSelection(const COORD, const COORD, const TextAttribute)
{
THROW_HR(E_NOTIMPL);
}

View file

@ -44,7 +44,6 @@
<ClCompile Include="..\screenInfo.cpp" />
<ClCompile Include="..\ScreenBufferRenderTarget.cpp" />
<ClCompile Include="..\scrolling.cpp" />
<ClCompile Include="..\search.cpp" />
<ClCompile Include="..\selection.cpp" />
<ClCompile Include="..\selectionInput.cpp" />
<ClCompile Include="..\selectionState.cpp" />
@ -105,7 +104,6 @@
<ClInclude Include="..\screenInfo.hpp" />
<ClInclude Include="..\ScreenBufferRenderTarget.hpp" />
<ClInclude Include="..\scrolling.hpp" />
<ClInclude Include="..\search.h" />
<ClInclude Include="..\selection.hpp" />
<ClInclude Include="..\server.h" />
<ClInclude Include="..\settings.hpp" />

View file

@ -120,9 +120,6 @@
<ClCompile Include="..\ntprivapi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\search.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\init.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -299,9 +296,6 @@
<ClInclude Include="..\ntprivapi.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\search.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\init.hpp">
<Filter>Header Files</Filter>
</ClInclude>
@ -360,4 +354,4 @@
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
</Project>

View file

@ -369,4 +369,28 @@ void RenderData::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
{
Selection::Instance().SelectNewRegion(coordStart, coordEnd);
}
// Routine Description:
// - Gets the current selection anchor position
// Arguments:
// - none
// Return Value:
// - current selection anchor
const COORD RenderData::GetSelectionAnchor() const
{
return Selection::Instance().GetSelectionAnchor();
}
// Routine Description:
// - Given two points in the buffer space, color the selection between the two with the given attribute.
// - This will create an internal selection rectangle covering the two points, assume a line selection,
// and use the first point as the anchor for the selection (as if the mouse click started at that point)
// Arguments:
// - coordSelectionStart - Anchor point (start of selection) for the region to be colored
// - coordSelectionEnd - Other point referencing the rectangle inscribing the selection area
// - attr - Color to apply to region.
void RenderData::ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr)
{
Selection::Instance().ColorSelection(coordSelectionStart, coordSelectionEnd, attr);
}
#pragma endregion

View file

@ -59,5 +59,7 @@ public:
const bool IsSelectionActive() const override;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr);
#pragma endregion
};

View file

@ -3,7 +3,7 @@
#include "precomp.h"
#include "search.h"
#include "..\buffer\out\search.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/convert.hpp"
@ -703,7 +703,7 @@ bool Selection::_HandleColorSelection(const INPUT_KEY_INFO* const pInputKeyInfo)
Telemetry::Instance().LogColorSelectionUsed();
Search search(screenInfo, str, Search::Direction::Forward, Search::Sensitivity::CaseInsensitive);
Search search(gci.renderData, str, Search::Direction::Forward, Search::Sensitivity::CaseInsensitive);
while (search.FindNext())
{
search.Color(TextAttribute{ static_cast<WORD>(ulAttr) });

View file

@ -60,7 +60,6 @@ SOURCES = \
..\VtInputThread.cpp \
..\PtySignalInputThread.cpp \
..\consoleInformation.cpp \
..\search.cpp \
..\directio.cpp \
..\getset.cpp \
..\globals.cpp \

View file

@ -7,7 +7,7 @@
#include "CommonState.hpp"
#include "search.h"
#include "..\buffer\out\search.h"
using namespace WEX::Common;
using namespace WEX::Logging;
@ -87,81 +87,73 @@ class SearchTests
TEST_METHOD(ForwardCaseSensitive)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& outputBuffer = gci.GetActiveOutputBuffer();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordStartExpected = { 0 };
Search s(outputBuffer, L"AB", Search::Direction::Forward, Search::Sensitivity::CaseSensitive);
Search s(gci.renderData, L"AB", Search::Direction::Forward, Search::Sensitivity::CaseSensitive);
DoFoundChecks(s, coordStartExpected, 1);
}
TEST_METHOD(ForwardCaseSensitiveJapanese)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& outputBuffer = gci.GetActiveOutputBuffer();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordStartExpected = { 2, 0 };
Search s(outputBuffer, L"\x304b", Search::Direction::Forward, Search::Sensitivity::CaseSensitive);
Search s(gci.renderData, L"\x304b", Search::Direction::Forward, Search::Sensitivity::CaseSensitive);
DoFoundChecks(s, coordStartExpected, 1);
}
TEST_METHOD(ForwardCaseInsensitive)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& outputBuffer = gci.GetActiveOutputBuffer();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordStartExpected = { 0 };
Search s(outputBuffer, L"ab", Search::Direction::Forward, Search::Sensitivity::CaseInsensitive);
Search s(gci.renderData, L"ab", Search::Direction::Forward, Search::Sensitivity::CaseInsensitive);
DoFoundChecks(s, coordStartExpected, 1);
}
TEST_METHOD(ForwardCaseInsensitiveJapanese)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& outputBuffer = gci.GetActiveOutputBuffer();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordStartExpected = { 2, 0 };
Search s(outputBuffer, L"\x304b", Search::Direction::Forward, Search::Sensitivity::CaseInsensitive);
Search s(gci.renderData, L"\x304b", Search::Direction::Forward, Search::Sensitivity::CaseInsensitive);
DoFoundChecks(s, coordStartExpected, 1);
}
TEST_METHOD(BackwardCaseSensitive)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& outputBuffer = gci.GetActiveOutputBuffer();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordStartExpected = { 0, 3 };
Search s(outputBuffer, L"AB", Search::Direction::Backward, Search::Sensitivity::CaseSensitive);
Search s(gci.renderData, L"AB", Search::Direction::Backward, Search::Sensitivity::CaseSensitive);
DoFoundChecks(s, coordStartExpected, -1);
}
TEST_METHOD(BackwardCaseSensitiveJapanese)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& outputBuffer = gci.GetActiveOutputBuffer();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordStartExpected = { 2, 3 };
Search s(outputBuffer, L"\x304b", Search::Direction::Backward, Search::Sensitivity::CaseSensitive);
Search s(gci.renderData, L"\x304b", Search::Direction::Backward, Search::Sensitivity::CaseSensitive);
DoFoundChecks(s, coordStartExpected, -1);
}
TEST_METHOD(BackwardCaseInsensitive)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& outputBuffer = gci.GetActiveOutputBuffer();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordStartExpected = { 0, 3 };
Search s(outputBuffer, L"ab", Search::Direction::Backward, Search::Sensitivity::CaseInsensitive);
Search s(gci.renderData, L"ab", Search::Direction::Backward, Search::Sensitivity::CaseInsensitive);
DoFoundChecks(s, coordStartExpected, -1);
}
TEST_METHOD(BackwardCaseInsensitiveJapanese)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& outputBuffer = gci.GetActiveOutputBuffer();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
COORD coordStartExpected = { 2, 3 };
Search s(outputBuffer, L"\x304b", Search::Direction::Backward, Search::Sensitivity::CaseInsensitive);
Search s(gci.renderData, L"\x304b", Search::Direction::Backward, Search::Sensitivity::CaseInsensitive);
DoFoundChecks(s, coordStartExpected, -1);
}
};

View file

@ -9,7 +9,7 @@
#include "..\..\host\dbcs.h"
#include "..\..\host\handle.h"
#include "..\..\host\search.h"
#include "..\buffer\out\search.h"
#include "..\inc\ServiceLocator.hpp"
@ -56,7 +56,7 @@ INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM l
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
Search search(ScreenInfo,
Search search(gci.renderData,
wstr,
Reverse ? Search::Direction::Backward : Search::Direction::Forward,
IgnoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive);

View file

@ -5,7 +5,7 @@
#include "uiaTextRange.hpp"
#include "screenInfoUiaProvider.hpp"
#include "..\host\search.h"
#include "..\buffer\out\search.h"
#include "..\interactivity\inc\ServiceLocator.hpp"
using namespace Microsoft::Console::Types;
@ -230,8 +230,7 @@ IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text,
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
THROW_HR_IF(E_POINTER, !gci.HasActiveOutputBuffer());
const auto& screenInfo = gci.GetActiveOutputBuffer().GetActiveBuffer();
Search searcher{ screenInfo, wstr, searchDirection, sensitivity, _endpointToCoord(_pData, searchAnchor) };
Search searcher{ gci.renderData, wstr, searchDirection, sensitivity, _endpointToCoord(_pData, searchAnchor) };
HRESULT hr = S_OK;
if (searcher.FindNext())

View file

@ -36,6 +36,8 @@ namespace Microsoft::Console::Types
virtual const bool IsSelectionActive() const = 0;
virtual void ClearSelection() = 0;
virtual void SelectNewRegion(const COORD coordStart, const COORD coordEnd) = 0;
virtual const COORD GetSelectionAnchor() const = 0;
virtual void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr) = 0;
};
// See docs/virtual-dtors.md for an explanation of why this is weird.