Repository: microsoft/terminal (language: cpp)

Issue:
Further simplification of the ConGetSet interface
# Description of the new feature/enhancement

I've been working for a while now on a refactoring the `ConhostInternalGetSet` code (as a follow up to PR #12247), trying to move as much as possible into the `AdaptDispatch` class, and condensing the `ConGetSet` interface down to a minimal set of operations.

The idea is that the the `AdaptDispatch` class could then be used in Windows Terminal in place of the existing `TerminalDispatch` (i.e. issue #3849), and only have a handful of methods in the `ConGetSet` interface needing terminal-specific implementations.

I'm not done yet, but I'm far enough along that I'm satisfied that this is the right approach, and I wanted to let others know my plans, and check whether you're happy with what I'm doing.

# Proposed technical implementation details (optional)

The general idea is that we provide the means for `AdaptDispatch` to access the high-level structures that are common to both conhost and terminal (like the `Renderer` and the `TextBuffer`), and then a lot of the operations like `EraseAll`, `InsertLines`, and `ScrollRegion` can be implemented entirely in `AdaptDispatch`, avoiding the need to implement them twice.

In some cases, the structures we need are static, and can be passed through to `AdaptDispatch` in the constructor (e.g. the `Renderer`, `RenderSettings`, and `TerminalInput`). Others are dynamic, but can easily be retrieve via new methods in the `ConGetSet` interface (e.g. the active `TextBuffer`, and the active virtual viewport).

Taking this approach, I've so far been able to eliminate 31 methods from the `ConGetSet` interface (adding only 4 new ones), and I think there are still more that could be nuked eventually.

My current plan was to submit this as two PRs: the first covering the refactoring of `ConhostInternalGetSet`, and the second dealing with the reuse of `Adaptdispatch` in the terminal (I haven't looked at that in much detail yet, so it might be more complicated than I imagine). Even just the first bit is quite a large change, though, so maybe you'd prefer it split up even more?


Discussion / hints:

Fix patch:
diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
index 082c2d80707..0ac3822d4e0 100644
--- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
@@ -66,8 +66,9 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
 
         m_state->InitEvents();
         m_state->PrepareGlobalFont();
-        m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight);
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalRenderer();
+        m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight);
 
         return true;
     }
@@ -75,6 +76,7 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
     TEST_CLASS_CLEANUP(ClassCleanup)
     {
         m_state->CleanupGlobalScreenBuffer();
+        m_state->CleanupGlobalRenderer();
         m_state->CleanupGlobalFont();
         m_state->CleanupGlobalInputBuffer();
 
@@ -101,8 +103,6 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
         gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
         gci.CalculateDefaultColorIndices();
 
-        g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
-
         m_state->PrepareNewTextBufferInfo(true, TerminalViewWidth, TerminalViewHeight);
         auto& currentBuffer = gci.GetActiveOutputBuffer();
         // Make sure a test hasn't left us in the alt buffer on accident
@@ -154,8 +154,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
     {
         m_state->CleanupNewTextBufferInfo();
 
-        auto& g = ServiceLocator::LocateGlobals();
-        delete g.pRender;
+        auto& engines = ServiceLocator::LocateGlobals().pRender->_engines;
+        std::fill(engines.begin(), engines.end(), nullptr);
 
         VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer.");
 
@@ -308,7 +308,8 @@ void ConptyRoundtripTests::_resizeConpty(const unsigned short sx,
 void ConptyRoundtripTests::_clearConpty()
 {
     // Taken verbatim from implementation in PtySignalInputThread::_DoClearBuffer
-    _pConApi->ClearBuffer();
+    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
+    THROW_IF_FAILED(gci.GetActiveOutputBuffer().ClearBuffer());
 }
 
 [[nodiscard]] std::tuple<TextBuffer*, TextBuffer*> ConptyRoundtripTests::_performResize(const til::size newSize)
diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp
index cf564d0c9fb..b4701d64605 100644
--- a/src/host/PtySignalInputThread.cpp
+++ b/src/host/PtySignalInputThread.cpp
@@ -170,13 +170,15 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
 {
     if (_pConApi->ResizeWindow(data.sx, data.sy))
     {
-        _pConApi->SuppressResizeRepaint();
+        auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
+        THROW_IF_FAILED(gci.GetVtIo()->SuppressResizeRepaint());
     }
 }
 
 void PtySignalInputThread::_DoClearBuffer()
 {
-    _pConApi->ClearBuffer();
+    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
+    THROW_IF_FAILED(gci.GetActiveOutputBuffer().ClearBuffer());
 }
 
 // Method Description:
diff --git a/src/host/getset.cpp b/src/host/getset.cpp
index d95e4e13ecd..0fcbbd2a26c 100644
--- a/src/host/getset.cpp
+++ b/src/host/getset.cpp
@@ -700,9 +700,6 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
                                     position.Y < 0));
         // clang-format on
 
-        // MSFT: 15813316 - Try to use this SetCursorPosition call to inherit the cursor position.
-        RETURN_IF_FAILED(gci.GetVtIo()->SetCursorPosition(position));
-
         RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true));
 
         LOG_IF_FAILED(ConsoleImeResizeCompStrView());
diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp
index 6a4244926e6..3955cfd9350 100644
--- a/src/host/outputStream.cpp
+++ b/src/host/outputStream.cpp
@@ -17,7 +17,6 @@
 using namespace Microsoft::Console;
 using Microsoft::Console::Interactivity::ServiceLocator;
 using Microsoft::Console::VirtualTerminal::StateMachine;
-using Microsoft::Console::VirtualTerminal::TerminalInput;
 
 ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) :
     _io{ io }
@@ -58,49 +57,50 @@ void ConhostInternalGetSet::PrintString(const std::wstring_view string)
 }
 
 // Routine Description:
-// - Connects the GetConsoleScreenBufferInfoEx API call directly into our Driver Message servicing call inside Conhost.exe
+// - Retrieves the state machine for the active output buffer.
 // Arguments:
-// - screenBufferInfo - Structure to hold screen buffer information like the public API call.
-// Return Value:
 // - <none>
-void ConhostInternalGetSet::GetConsoleScreenBufferInfoEx(CONSOLE_SCREEN_BUFFER_INFOEX& screenBufferInfo) const
+// Return Value:
+// - a reference to the StateMachine instance.
+StateMachine& ConhostInternalGetSet::GetStateMachine()
 {
-    ServiceLocator::LocateGlobals().api->GetConsoleScreenBufferInfoExImpl(_io.GetActiveOutputBuffer(), screenBufferInfo);
+    return _io.GetActiveOutputBuffer().GetStateMachine();
 }
 
 // Routine Description:
-// - Connects the SetConsoleScreenBufferInfoEx API call directly into our Driver Message servicing call inside Conhost.exe
+// - Retrieves the text buffer for the active output buffer.
 // Arguments:
-// - screenBufferInfo - Structure containing screen buffer information like the public API call.
-// Return Value:
 // - <none>
-void ConhostInternalGetSet::SetConsoleScreenBufferInfoEx(const CONSOLE_SCREEN_BUFFER_INFOEX& screenBufferInfo)
+// Return Value:
+// - a reference to the TextBuffer instance.
+TextBuffer& ConhostInternalGetSet::GetTextBuffer()
 {
-    THROW_IF_FAILED(ServiceLocator::LocateGlobals().api->SetConsoleScreenBufferInfoExImpl(_io.GetActiveOutputBuffer(), screenBufferInfo));
+    return _io.GetActiveOutputBuffer().GetTextBuffer();
 }
 
 // Routine Description:
-// - Connects the SetCursorPosition API call directly into our Driver Message servicing call inside Conhost.exe
+// - Retrieves the virtual viewport of the active output buffer.
 // Arguments:
-// - position - new cursor position to set like the public API call.
-// Return Value:
 // - <none>
-void ConhostInternalGetSet::SetCursorPosition(const COORD position)
+// Return Value:
+// - the exclusive coordinates of the viewport.
+til::rect ConhostInternalGetSet::GetViewport() const
 {
-    auto& info = _io.GetActiveOutputBuffer();
-    const auto clampedPosition = info.GetTextBuffer().ClampPositionWithinLine(position);
-    THROW_IF_FAILED(ServiceLocator::LocateGlobals().api->SetConsoleCursorPositionImpl(info, clampedPosition));
+    return til::rect{ _io.GetActiveOutputBuffer().GetVirtualViewport().ToInclusive() };
 }
 
-// Method Description:
-// - Retrieves the current TextAttribute of the active screen buffer.
+// Routine Description:
+// - Sets the position of the window viewport.
 // Arguments:
-// - <none>
+// - position - the new position of the viewport.
 // Return Value:
-// - the TextAttribute value.
-TextAttribute ConhostInternalGetSet::GetTextAttributes() const
+// - <none>
+void ConhostInternalGetSet::SetViewportPosition(const til::point position)
 {
-    return _io.GetActiveOutputBuffer().GetAttributes();
+    auto& info = _io.GetActiveOutputBuffer();
+    const auto dimensions = til::size{ info.GetViewport().Dimensions() };
+    const auto windowRect = til::rect{ position, dimensions }.to_small_rect();
+    THROW_IF_FAILED(ServiceLocator::LocateGlobals().api->SetConsoleWindowInfoImpl(info, true, windowRect));
 }
 
 // Method Description:
@@ -115,46 +115,6 @@ void ConhostInternalGetSet::SetTextAttributes(const TextAttribute& attrs)
     _io.GetActiveOutputBuffer().SetAttributes(attrs);
 }
 
-// Method Description:
-// - Sets the line rendition attribute for the current row of the active screen
-//   buffer. This controls how character cells are scaled when the row is rendered.
-// Arguments:
-// - lineRendition: The new LineRendition attribute to use
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::SetCurrentLineRendition(const LineRendition lineRendition)
-{
-    auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
-    textBuffer.SetCurrentLineRendition(lineRendition);
-}
-
-// Method Description:
-// - Resets the line rendition attribute to SingleWidth for a specified range
-//   of row numbers.
-// Arguments:
-// - startRow: The row number of first line to be modified
-// - endRow: The row number following the last line to be modified
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::ResetLineRenditionRange(const size_t startRow, const size_t endRow)
-{
-    auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
-    textBuffer.ResetLineRenditionRange(startRow, endRow);
-}
-
-// Method Description:
-// - Returns the number of cells that will fit on the specified row when
-//   rendered with its current line rendition.
-// Arguments:
-// - row: The row number of the line to measure
-// Return Value:
-// - the number of cells that will fit on the line
-SHORT ConhostInternalGetSet::GetLineWidth(const size_t row) const
-{
-    const auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
-    return textBuffer.GetLineWidth(row);
-}
-
 // Routine Description:
 // - Writes events to the input buffer already formed into IInputEvents
 // Arguments:
@@ -168,91 +128,6 @@ void ConhostInternalGetSet::WriteInput(std::deque<std::unique_ptr<IInputEvent>>&
     eventsWritten = _io.GetActiveInputBuffer()->Write(events);
 }
 
-// Routine Description:
-// - Connects the SetWindowInfo API call directly into our Driver Message servicing call inside Conhost.exe
-// Arguments:
-// - absolute - Should the window be moved to an absolute position? If false, the movement is relative to the current pos.
-// - window - Info about how to move the viewport
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::SetWindowInfo(const bool absolute, const SMALL_RECT& window)
-{
-    THROW_IF_FAILED(ServiceLocator::LocateGlobals().api->SetConsoleWindowInfoImpl(_io.GetActiveOutputBuffer(), absolute, window));
-}
-
-// Routine Description:
-// - Sets the various terminal input modes.
-//   SetInputMode is an internal-only "API" call that the vt commands can execute,
-//     but it is not represented as a function call on out public API surface.
-// Arguments:
-// - mode - the input mode to change.
-// - enabled - set to true to enable the mode, false to disable it.
-// Return Value:
-// - true if successful. false otherwise.
-bool ConhostInternalGetSet::SetInputMode(const TerminalInput::Mode mode, const bool enabled)
-{
-    auto& terminalInput = _io.GetActiveInputBuffer()->GetTerminalInput();
-    terminalInput.SetInputMode(mode, enabled);
-
-    // If we're a conpty, AND WE'RE IN VT INPUT MODE, always pass input mode requests
-    // The VT Input mode check is to work around ssh.exe v7.7, which uses VT
-    // output, but not Input.
-    // The original comment said, "Once the conpty supports these types of input,
-    // this check can be removed. See GH#4911". Unfortunately, time has shown
-    // us that SSH 7.7 _also_ requests mouse input and that can have a user interface
-    // impact on the actual connected terminal. We can't remove this check,
-    // because SSH <=7.7 is out in the wild on all versions of Windows <=2004.
-    return !(IsConsolePty() && IsVtInputEnabled());
-}
-
-// Routine Description:
-// - Sets the various StateMachine parser modes.
-//   SetParserMode is an internal-only "API" call that the vt commands can execute,
-//     but it is not represented as a function call on out public API surface.
-// Arguments:
-// - mode - the parser mode to change.
-// - enabled - set to true to enable the mode, false to disable it.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::SetParserMode(const StateMachine::Mode mode, const bool enabled)
-{
-    auto& stateMachine = _io.GetActiveOutputBuffer().GetStateMachine();
-    stateMachine.SetParserMode(mode, enabled);
-}
-
-// Routine Description:
-// - Retrieves the various StateMachine parser modes.
-//   GetParserMode is an internal-only "API" call that the vt commands can execute,
-//     but it is not represented as a function call on out public API surface.
-// Arguments:
-// - mode - the parser mode to query.
-// Return Value:
-// - true if the mode is enabled. false if disabled.
-bool ConhostInternalGetSet::GetParserMode(const Microsoft::Console::VirtualTerminal::StateMachine::Mode mode) const
-{
-    auto& stateMachine = _io.GetActiveOutputBuffer().GetStateMachine();
-    return stateMachine.GetParserMode(mode);
-}
-
-// Routine Description:
-// - Sets the various render modes.
-// Arguments:
-// - mode - the render mode to change.
-// - enabled - set to true to enable the mode, false to disable it.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::SetRenderMode(const RenderSettings::Mode mode, const bool enabled)
-{
-    auto& g = ServiceLocator::LocateGlobals();
-    auto& renderSettings = g.getConsoleInformation().GetRenderSettings();
-    renderSettings.SetRenderMode(mode, enabled);
-
-    if (g.pRender)
-    {
-        g.pRender->TriggerRedrawAll();
-    }
-}
-
 // Routine Description:
 // - Sets the ENABLE_WRAP_AT_EOL_OUTPUT mode. This controls whether the cursor moves
 //     to the beginning of the next row when it reaches the end of the current row.
@@ -266,42 +141,6 @@ void ConhostInternalGetSet::SetAutoWrapMode(const bool wrapAtEOL)
     WI_UpdateFlag(outputMode, ENABLE_WRAP_AT_EOL_OUTPUT, wrapAtEOL);
 }
 
-// Routine Description:
-// - Makes the cursor visible or hidden. Does not modify blinking state.
-// Arguments:
-// - visible - set to true to make the cursor visible, false to hide.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::SetCursorVisibility(const bool visible)
-{
-    auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
-    textBuffer.GetCursor().SetIsVisible(visible);
-}
-
-// Routine Description:
-// - Enables or disables the cursor blinking.
-// Arguments:
-// - fEnable - set to true to enable blinking, false to disable
-// Return Value:
-// - true if successful. false otherwise.
-bool ConhostInternalGetSet::EnableCursorBlinking(const bool fEnable)
-{
-    auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
-    textBuffer.GetCursor().SetBlinkingAllowed(fEnable);
-
-    // GH#2642 - From what we've gathered from other terminals, when blinking is
-    // disabled, the cursor should remain On always, and have the visibility
-    // controlled by the IsVisible property. So when you do a printf "\e[?12l"
-    // to disable blinking, the cursor stays stuck On. At this point, only the
-    // cursor visibility property controls whether the user can see it or not.
-    // (Yes, the cursor can be On and NOT Visible)
-    textBuffer.GetCursor().SetIsOn(true);
-
-    // If we are connected to a pty, return that we could not handle this
-    // so that the VT sequence gets flushed to terminal.
-    return !IsConsolePty();
-}
-
 // Routine Description:
 // - Sets the top and bottom scrolling margins for the current page. This creates
 //     a subsection of the screen that scrolls when input reaches the end of the
@@ -375,58 +214,6 @@ void ConhostInternalGetSet::WarningBell()
     _io.GetActiveOutputBuffer().SendNotifyBeep();
 }
 
-// Routine Description:
-// - Performs a "Reverse line feed", essentially, the opposite of '\n'.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::ReverseLineFeed()
-{
-    auto& screenInfo = _io.GetActiveOutputBuffer();
-    const SMALL_RECT viewport = screenInfo.GetViewport().ToInclusive();
-    const COORD oldCursorPosition = screenInfo.GetTextBuffer().GetCursor().GetPosition();
-    COORD newCursorPosition = { oldCursorPosition.X, oldCursorPosition.Y - 1 };
-    newCursorPosition = screenInfo.GetTextBuffer().ClampPositionWithinLine(newCursorPosition);
-
-    // If the cursor is at the top of the viewport, we don't want to shift the viewport up.
-    // We want it to stay exactly where it is.
-    // In that case, shift the buffer contents down, to emulate inserting a line
-    //      at the top of the buffer.
-    if (oldCursorPosition.Y > viewport.Top)
-    {
-        // Cursor is below the top line of the viewport
-        THROW_IF_NTSTATUS_FAILED(AdjustCursorPosition(screenInfo, newCursorPosition, TRUE, nullptr));
-    }
-    else
-    {
-        // If we don't have margins, or the cursor is within the boundaries of the margins
-        // It's important to check if the cursor is in the margins,
-        //      If it's not, but the margins are set, then we don't want to scroll anything
-        if (screenInfo.IsCursorInMargins(oldCursorPosition))
-        {
-            // Cursor is at the top of the viewport
-            // Rectangle to cut out of the existing buffer. This is inclusive.
-            // It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width.
-            SMALL_RECT srScroll;
-            srScroll.Left = 0;
-            srScroll.Right = SHORT_MAX;
-            srScroll.Top = viewport.Top;
-            srScroll.Bottom = viewport.Bottom;
-            // Clip to the DECSTBM margin boundary
-            if (screenInfo.AreMarginsSet())
-            {
-                srScroll.Bottom = screenInfo.GetAbsoluteScrollMargins().BottomInclusive();
-            }
-            // Paste coordinate for cut text above
-            COORD coordDestination;
-            coordDestination.X = 0;
-            coordDestination.Y = viewport.Top + 1;
-
-            // Note the revealed lines are filled with the standard erase attributes.
-            ScrollRegion(srScroll, srScroll, coordDestination, true);
-        }
-    }
-}
-
 // Routine Description:
 // - Sets the title of the console window.
 // Arguments:
@@ -459,27 +246,6 @@ void ConhostInternalGetSet::UseMainScreenBuffer()
     _io.GetActiveOutputBuffer().UseMainScreenBuffer();
 }
 
-// Routine Description:
-// - Performs a VT-style erase all operation on the buffer.
-//      See SCREEN_INFORMATION::VtEraseAll's description for details.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::EraseAll()
-{
-    THROW_IF_FAILED(_io.GetActiveOutputBuffer().VtEraseAll());
-}
-
-// Routine Description:
-// - Clears the entire contents of the viewport, except for the cursor's row,
-//     which is moved to the top line of the viewport.
-//     See SCREEN_INFORMATION::ClearBuffer's description for details.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::ClearBuffer()
-{
-    THROW_IF_FAILED(_io.GetActiveOutputBuffer().ClearBuffer());
-}
-
 // Method Description:
 // - Retrieves the current user default cursor style.
 // Arguments:
@@ -492,52 +258,6 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const
     return gci.GetCursorType();
 }
 
-// Routine Description:
-// - Sets the current cursor style.
-// Arguments:
-// - style: The style of cursor to change the cursor to.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::SetCursorStyle(const CursorType style)
-{
-    _io.GetActiveOutputBuffer().GetTextBuffer().GetCursor().SetType(style);
-}
-
-// Routine Description:
-// - Forces the renderer to repaint the screen. If the input screen buffer is
-//      not the active one, then just do nothing. We only want to redraw the
-//      screen buffer that requested the repaint, and switching screen buffers
-//      will already force a repaint.
-// Arguments:
-// - <none>
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::RefreshWindow()
-{
-    auto& g = ServiceLocator::LocateGlobals();
-    if (&_io.GetActiveOutputBuffer() == &g.getConsoleInformation().GetActiveOutputBuffer())
-    {
-        g.pRender->TriggerRedrawAll();
-    }
-}
-
-// Routine Description:
-// - Writes the input KeyEvent to the console as a console control event. This
-//      can be used for potentially generating Ctrl-C events, as
-//      HandleGenericKeyEvent will correctly generate the Ctrl-C response in
-//      the same way that it'd be handled from the window proc, with the proper
-//      processed vs raw input handling.
-//  If the input key is *not* a Ctrl-C key, then it will get written to the
-//      buffer just the same as any other KeyEvent.
-// Arguments:
-// - key - The keyevent to send to the console.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::WriteControlInput(const KeyEvent key)
-{
-    HandleGenericKeyEvent(key, false);
-}
-
 // Routine Description:
 // - Connects the SetConsoleOutputCP API call directly into our Driver Message servicing call inside Conhost.exe
 // Arguments:
@@ -577,9 +297,12 @@ bool ConhostInternalGetSet::ResizeWindow(const size_t width, const size_t height
     // We should do nothing if 0 is passed in for a size.
     RETURN_BOOL_IF_FALSE(width > 0 && height > 0);
 
+    auto api = ServiceLocator::LocateGlobals().api;
+    auto& screenInfo = _io.GetActiveOutputBuffer();
+
     CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
     csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    GetConsoleScreenBufferInfoEx(csbiex);
+    api->GetConsoleScreenBufferInfoExImpl(screenInfo, csbiex);
 
     const Viewport oldViewport = Viewport::FromInclusive(csbiex.srWindow);
     const Viewport newViewport = Viewport::FromDimensions(oldViewport.Origin(), sColumns, sRows);
@@ -599,24 +322,11 @@ bool ConhostInternalGetSet::ResizeWindow(const size_t width, const size_t height
     const auto sre = newViewport.ToExclusive();
     csbiex.srWindow = sre;
 
-    SetConsoleScreenBufferInfoEx(csbiex);
-    SetWindowInfo(true, sri);
+    THROW_IF_FAILED(api->SetConsoleScreenBufferInfoExImpl(screenInfo, csbiex));
+    THROW_IF_FAILED(api->SetConsoleWindowInfoImpl(screenInfo, true, sri));
     return true;
 }
 
-// Routine Description:
-// - Forces the VT Renderer to NOT paint the next resize event. This is used by
-//      InteractDispatch, to prevent resizes from echoing between terminal and host.
-// Arguments:
-// - <none>
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::SuppressResizeRepaint()
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    THROW_IF_FAILED(gci.GetVtIo()->SuppressResizeRepaint());
-}
-
 // Routine Description:
 // - Checks if the console host is acting as a pty.
 // Arguments:
@@ -628,214 +338,6 @@ bool ConhostInternalGetSet::IsConsolePty() const
     return ServiceLocator::LocateGlobals().getConsoleInformation().IsInVtIoMode();
 }
 
-// Routine Description:
-// - Deletes lines in the active screen buffer.
-// Parameters:
-// - count - the number of lines to delete
-void ConhostInternalGetSet::DeleteLines(const size_t count)
-{
-    _modifyLines(count, false);
-}
-
-// Routine Description:
-// - Inserts lines in the active screen buffer.
-// Parameters:
-// - count - the number of lines to insert
-void ConhostInternalGetSet::InsertLines(const size_t count)
-{
-    _modifyLines(count, true);
-}
-
-// Routine Description:
-// - internal logic for adding or removing lines in the active screen buffer
-//   this also moves the cursor to the left margin, which is expected behavior for IL and DL
-// Parameters:
-// - count - the number of lines to modify
-// - insert - true if inserting lines, false if deleting lines
-void ConhostInternalGetSet::_modifyLines(const size_t count, const bool insert)
-{
-    auto& screenInfo = _io.GetActiveOutputBuffer();
-    auto& textBuffer = screenInfo.GetTextBuffer();
-    const auto cursorPosition = textBuffer.GetCursor().GetPosition();
-    if (screenInfo.IsCursorInMargins(cursorPosition))
-    {
-        // Rectangle to cut out of the existing buffer. This is inclusive.
-        // It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width.
-        SMALL_RECT srScroll;
-        srScroll.Left = 0;
-        srScroll.Right = SHORT_MAX;
-        srScroll.Top = cursorPosition.Y;
-        srScroll.Bottom = screenInfo.GetViewport().BottomInclusive();
-        // Clip to the DECSTBM margin boundary
-        if (screenInfo.AreMarginsSet())
-        {
-            srScroll.Bottom = screenInfo.GetAbsoluteScrollMargins().BottomInclusive();
-        }
-        // Paste coordinate for cut text above
-        COORD coordDestination;
-        coordDestination.X = 0;
-        if (insert)
-        {
-            coordDestination.Y = (cursorPosition.Y) + gsl::narrow<short>(count);
-        }
-        else
-        {
-            coordDestination.Y = (cursorPosition.Y) - gsl::narrow<short>(count);
-        }
-
-        // Note the revealed lines are filled with the standard erase attributes.
-        ScrollRegion(srScroll, srScroll, coordDestination, true);
-
-        // The IL and DL controls are also expected to move the cursor to the left margin.
-        // For now this is just column 0, since we don't yet support DECSLRM.
-        THROW_IF_NTSTATUS_FAILED(screenInfo.SetCursorPosition({ 0, cursorPosition.Y }, false));
-    }
-}
-
-// Method Description:
-// - Snaps the screen buffer's viewport to the "virtual bottom", the last place
-//      the viewport was before the user scrolled it (with the mouse or scrollbar)
-// Arguments:
-// - <none>
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::MoveToBottom()
-{
-    _io.GetActiveOutputBuffer().MoveToBottom();
-}
-
-// Method Description:
-// - Retrieves the value in the colortable at the specified index.
-// Arguments:
-// - tableIndex: the index of the color table to retrieve.
-// Return Value:
-// - the COLORREF value for the color at that index in the table.
-COLORREF ConhostInternalGetSet::GetColorTableEntry(const size_t tableIndex) const
-{
-    auto& g = ServiceLocator::LocateGlobals();
-    auto& gci = g.getConsoleInformation();
-    return gci.GetColorTableEntry(tableIndex);
-}
-
-// Method Description:
-// - Updates the value in the colortable at index tableIndex to the new color
-//   color. color is a COLORREF, format 0x00BBGGRR.
-// Arguments:
-// - tableIndex: the index of the color table to update.
-// - color: the new COLORREF to use as that color table value.
-// Return Value:
-// - true if successful. false otherwise.
-bool ConhostInternalGetSet::SetColorTableEntry(const size_t tableIndex, const COLORREF color)
-{
-    auto& g = ServiceLocator::LocateGlobals();
-    auto& gci = g.getConsoleInformation();
-    gci.SetColorTableEntry(tableIndex, color);
-
-    // Update the screen colors if we're not a pty
-    // No need to force a redraw in pty mode.
-    if (g.pRender && !gci.IsInVtIoMode())
-    {
-        g.pRender->TriggerRedrawAll();
-    }
-
-    // If we're a conpty, always return false, so that we send the updated color
-    //      value to the terminal. Still handle the sequence so apps that use
-    //      the API or VT to query the values of the color table still read the
-    //      correct color.
-    return !gci.IsInVtIoMode();
-}
-
-// Routine Description:
-// - Sets the position in the color table for the given color alias.
-// Arguments:
-// - alias: the color alias to update.
-// - tableIndex: the new position of the alias in the color table.
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::SetColorAliasIndex(const ColorAlias alias, const size_t tableIndex)
-{
-    auto& renderSettings = ServiceLocator::LocateGlobals().getConsoleInformation().GetRenderSettings();
-    renderSettings.SetColorAliasIndex(alias, tableIndex);
-}
-
-// Routine Description:
-// - Fills a region of the screen buffer.
-// Arguments:
-// - startPosition - The position to begin filling at.
-// - fillLength - The number of characters to fill.
-// - fillChar - Character to fill the target region with.
-// - standardFillAttrs - If true, fill with the standard erase attributes.
-//                       If false, fill with the default attributes.
-// Return value:
-// - <none>
-void ConhostInternalGetSet::FillRegion(const COORD startPosition,
-                                       const size_t fillLength,
-                                       const wchar_t fillChar,
-                                       const bool standardFillAttrs)
-{
-    auto& screenInfo = _io.GetActiveOutputBuffer();
-
-    if (fillLength == 0)
-    {
-        return;
-    }
-
-    // For most VT erasing operations, the standard requires that the
-    // erased area be filled with the current background color, but with
-    // no additional meta attributes set. For all other cases, we just
-    // fill with the default attributes.
-    auto fillAttrs = TextAttribute{};
-    if (standardFillAttrs)
-    {
-        fillAttrs = screenInfo.GetAttributes();
-        fillAttrs.SetStandardErase();
-    }
-
-    const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
-    screenInfo.Write(fillData, startPosition, false);
-
-    // Notify accessibility
-    if (screenInfo.HasAccessibilityEventing())
-    {
-        auto endPosition = startPosition;
-        const auto bufferSize = screenInfo.GetBufferSize();
-        bufferSize.MoveInBounds(fillLength - 1, endPosition);
-        screenInfo.NotifyAccessibilityEventing(startPosition.X, startPosition.Y, endPosition.X, endPosition.Y);
-    }
-}
-
-// Routine Description:
-// - Moves a block of data in the screen buffer, optionally limiting the effects
-//      of the move to a clipping rectangle.
-// Arguments:
-// - scrollRect - Region to copy/move (source and size).
-// - clipRect - Optional clip region to contain buffer change effects.
-// - destinationOrigin - Upper left corner of target region.
-// - standardFillAttrs - If true, fill with the standard erase attributes.
-//                       If false, fill with the default attributes.
-// Return value:
-// - <none>
-void ConhostInternalGetSet::ScrollRegion(const SMALL_RECT scrollRect,
-                                         const std::optional<SMALL_RECT> clipRect,
-                                         const COORD destinationOrigin,
-                                         const bool standardFillAttrs)
-{
-    auto& screenInfo = _io.GetActiveOutputBuffer();
-
-    // For most VT scrolling operations, the standard requires that the
-    // erased area be filled with the current background color, but with
-    // no additional meta attributes set. For all other cases, we just
-    // fill with the default attributes.
-    auto fillAttrs = TextAttribute{};
-    if (standardFillAttrs)
-    {
-        fillAttrs = screenInfo.GetAttributes();
-        fillAttrs.SetStandardErase();
-    }
-
-    ::ScrollRegion(screenInfo, scrollRect, clipRect, destinationOrigin, UNICODE_SPACE, fillAttrs);
-}
-
 // Routine Description:
 // - Checks if the InputBuffer is willing to accept VT Input directly
 //   IsVtInputEnabled is an internal-only "API" call that the vt commands can execute,
@@ -849,47 +351,22 @@ bool ConhostInternalGetSet::IsVtInputEnabled() const
     return _io.GetActiveInputBuffer()->IsInVirtualTerminalInputMode();
 }
 
-// Method Description:
-// - Updates the buffer's current text attributes depending on whether we are
-//   starting/ending a hyperlink
-// Arguments:
-// - The hyperlink URI
-// Return Value:
-// - <none>
-void ConhostInternalGetSet::AddHyperlink(const std::wstring_view uri, const std::wstring_view params) const
-{
-    auto& screenInfo = _io.GetActiveOutputBuffer();
-    auto attr = screenInfo.GetAttributes();
-    const auto id = screenInfo.GetTextBuffer().GetHyperlinkId(uri, params);
-    attr.SetHyperlinkId(id);
-    screenInfo.GetTextBuffer().SetCurrentAttributes(attr);
-    screenInfo.GetTextBuffer().AddHyperlinkToMap(uri, id);
-}
-
-void ConhostInternalGetSet::EndHyperlink() const
-{
-    auto& screenInfo = _io.GetActiveOutputBuffer();
-    auto attr = screenInfo.GetAttributes();
-    attr.SetHyperlinkId(0);
-    screenInfo.GetTextBuffer().SetCurrentAttributes(attr);
-}
-
 // Routine Description:
-// - Replaces the active soft font with the given bit pattern.
+// - Lets accessibility apps know when an area of the screen has changed.
 // Arguments:
-// - bitPattern - An array of scanlines representing all the glyphs in the font.
-// - cellSize - The cell size for an individual glyph.
-// - centeringHint - The horizontal extent that glyphs are offset from center.
-// Return Value:
+// - changedRect - the area that has changed.
+// Return value:
 // - <none>
-void ConhostInternalGetSet::UpdateSoftFont(const gsl::span<const uint16_t> bitPattern,
-                                           const SIZE cellSize,
-                                           const size_t centeringHint)
+void ConhostInternalGetSet::NotifyAccessibilityChange(const til::rect& changedRect)
 {
-    auto* pRender = ServiceLocator::LocateGlobals().pRender;
-    if (pRender)
+    auto& screenInfo = _io.GetActiveOutputBuffer();
+    if (screenInfo.HasAccessibilityEventing())
     {
-        pRender->UpdateSoftFont(bitPattern, cellSize, centeringHint);
+        screenInfo.NotifyAccessibilityEventing(
+            gsl::narrow_cast<short>(changedRect.left),
+            gsl::narrow_cast<short>(changedRect.top),
+            gsl::narrow_cast<short>(changedRect.right - 1),
+            gsl::narrow_cast<short>(changedRect.bottom - 1));
     }
 }
 
diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp
index 75db5f59076..1fc1db2cfb1 100644
--- a/src/host/outputStream.hpp
+++ b/src/host/outputStream.hpp
@@ -31,39 +31,23 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::
 
     void PrintString(const std::wstring_view string) override;
 
-    void GetConsoleScreenBufferInfoEx(CONSOLE_SCREEN_BUFFER_INFOEX& screenBufferInfo) const override;
-    void SetConsoleScreenBufferInfoEx(const CONSOLE_SCREEN_BUFFER_INFOEX& screenBufferInfo) override;
+    Microsoft::Console::VirtualTerminal::StateMachine& GetStateMachine() override;
+    TextBuffer& GetTextBuffer() override;
+    til::rect GetViewport() const override;
+    void SetViewportPosition(const til::point position) override;
 
-    void SetCursorPosition(const COORD position) override;
-
-    TextAttribute GetTextAttributes() const override;
     void SetTextAttributes(const TextAttribute& attrs) override;
 
-    void SetCurrentLineRendition(const LineRendition lineRendition) override;
-    void ResetLineRenditionRange(const size_t startRow, const size_t endRow) override;
-    SHORT GetLineWidth(const size_t row) const override;
-
     void WriteInput(std::deque<std::unique_ptr<IInputEvent>>& events, size_t& eventsWritten) override;
 
-    void SetWindowInfo(bool const absolute, const SMALL_RECT& window) override;
-
-    bool SetInputMode(const Microsoft::Console::VirtualTerminal::TerminalInput::Mode mode, const bool enabled) override;
-    void SetParserMode(const Microsoft::Console::VirtualTerminal::StateMachine::Mode mode, const bool enabled) override;
-    bool GetParserMode(const Microsoft::Console::VirtualTerminal::StateMachine::Mode mode) const override;
-    void SetRenderMode(const RenderSettings::Mode mode, const bool enabled) override;
-
     void SetAutoWrapMode(const bool wrapAtEOL) override;
 
-    void SetCursorVisibility(const bool visible) override;
-    bool EnableCursorBlinking(const bool enable) override;
-
     void SetScrollingRegion(const SMALL_RECT& scrollMargins) override;
 
     void WarningBell() override;
 
     bool GetLineFeedMode() const override;
     void LineFeed(const bool withReturn) override;
-    void ReverseLineFeed() override;
 
     void SetWindowTitle(const std::wstring_view title) override;
 
@@ -71,55 +55,20 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::
 
     void UseMainScreenBuffer() override;
 
-    void EraseAll() override;
-    void ClearBuffer() override;
-
     CursorType GetUserDefaultCursorStyle() const override;
-    void SetCursorStyle(CursorType const style) override;
 
-    void RefreshWindow() override;
     bool ResizeWindow(const size_t width, const size_t height) override;
-    void SuppressResizeRepaint() override;
-
-    void WriteControlInput(const KeyEvent key) override;
 
     void SetConsoleOutputCP(const unsigned int codepage) override;
     unsigned int GetConsoleOutputCP() const override;
 
     bool IsConsolePty() const override;
-
-    void DeleteLines(const size_t count) override;
-    void InsertLines(const size_t count) override;
-
-    void MoveToBottom() override;
-
-    COLORREF GetColorTableEntry(const size_t tableIndex) const override;
-    bool SetColorTableEntry(const size_t tableIndex, const COLORREF color) override;
-    void SetColorAliasIndex(const ColorAlias alias, const size_t tableIndex) override;
-
-    void FillRegion(const COORD startPosition,
-                    const size_t fillLength,
-                    const wchar_t fillChar,
-                    const bool standardFillAttrs) override;
-
-    void ScrollRegion(const SMALL_RECT scrollRect,
-                      const std::optional<SMALL_RECT> clipRect,
-                      const COORD destinationOrigin,
-                      const bool standardFillAttrs) override;
-
     bool IsVtInputEnabled() const override;
 
-    void AddHyperlink(const std::wstring_view uri, const std::wstring_view params) const override;
-    void EndHyperlink() const override;
-
-    void UpdateSoftFont(const gsl::span<const uint16_t> bitPattern,
-                        const SIZE cellSize,
-                        const size_t centeringHint) override;
+    void NotifyAccessibilityChange(const til::rect& changedRect) override;
 
     void ReparentWindow(const uint64_t handle);
 
 private:
-    void _modifyLines(const size_t count, const bool insert);
-
     Microsoft::Console::IIoProvider& _io;
 };
diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp
index 351e877ecd6..c1f8d90ec57 100644
--- a/src/host/screenInfo.cpp
+++ b/src/host/screenInfo.cpp
@@ -254,8 +254,13 @@ void SCREEN_INFORMATION::s_RemoveScreenBuffer(_In_ SCREEN_INFORMATION* const pSc
 {
     try
     {
+        auto& g = ServiceLocator::LocateGlobals();
+        auto& gci = g.getConsoleInformation();
+        auto& renderer = *g.pRender;
+        auto& renderSettings = gci.GetRenderSettings();
+        auto& terminalInput = gci.GetActiveInputBuffer()->GetTerminalInput();
         auto getset = std::make_unique<ConhostInternalGetSet>(*this);
-        auto adapter = std::make_unique<AdaptDispatch>(std::move(getset));
+        auto adapter = std::make_unique<AdaptDispatch>(std::move(getset), renderer, renderSettings, terminalInput);
         auto engine = std::make_unique<OutputStateMachineEngine>(std::move(adapter));
         // Note that at this point in the setup, we haven't determined if we're
         //      in VtIo mode or not yet. We'll set the OutputStateMachine's
@@ -2223,57 +2228,6 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
     Tracing::s_TraceWindowViewport(_viewport);
 }
 
-// Method Description:
-// - Performs a VT Erase All operation. In most terminals, this is done by
-//      moving the viewport into the scrollback, clearing out the current screen.
-//      For them, there can never be any characters beneath the viewport, as the
-//      viewport is always at the bottom. So, we can accomplish the same behavior
-//      by using the LastNonspaceCharacter as the "bottom", and placing the new
-//      viewport underneath that character.
-// Parameters:
-//  <none>
-// Return value:
-// - S_OK if we succeeded, or another status if there was a failure.
-[[nodiscard]] HRESULT SCREEN_INFORMATION::VtEraseAll()
-{
-    const COORD coordLastChar = _textBuffer->GetLastNonSpaceCharacter();
-    short sNewTop = coordLastChar.Y + 1;
-    const Viewport oldViewport = _viewport;
-    // Stash away the current position of the cursor within the viewport.
-    // We'll need to restore the cursor to that same relative position, after
-    //      we move the viewport.
-    const COORD oldCursorPos = _textBuffer->GetCursor().GetPosition();
-    COORD relativeCursor = oldCursorPos;
-    oldViewport.ConvertToOrigin(&relativeCursor);
-
-    short delta = (sNewTop + _viewport.Height()) - (GetBufferSize().Height());
-    for (auto i = 0; i < delta; i++)
-    {
-        _textBuffer->IncrementCircularBuffer();
-        sNewTop--;
-    }
-
-    const COORD coordNewOrigin = { 0, sNewTop };
-    RETURN_IF_FAILED(SetViewportOrigin(true, coordNewOrigin, true));
-    // Restore the relative cursor position
-    _viewport.ConvertFromOrigin(&relativeCursor);
-    RETURN_IF_FAILED(SetCursorPosition(relativeCursor, false));
-
-    // Update all the rows in the current viewport with the standard erase attributes,
-    // i.e. the current background color, but with no meta attributes set.
-    auto fillAttributes = GetAttributes();
-    fillAttributes.SetStandardErase();
-    auto fillPosition = COORD{ 0, _viewport.Top() };
-    auto fillLength = gsl::narrow_cast<size_t>(_viewport.Height() * GetBufferSize().Width());
-    auto fillData = OutputCellIterator{ fillAttributes, fillLength };
-    Write(fillData, fillPosition, false);
-
-    // Also reset the line rendition for the erased rows.
-    _textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
-
-    return S_OK;
-}
-
 // Method Description:
 // - Clear the entire contents of the viewport, except for the cursor's row,
 //   which is moved to the top line of the viewport.
@@ -2651,24 +2605,6 @@ void SCREEN_INFORMATION::InitializeCursorRowAttributes()
     }
 }
 
-// Method Description:
-// - Moves the viewport to where we last believed the "virtual bottom" was. This
-//      emulates linux terminal behavior, where there's no buffer, only a
-//      viewport. This is called by WriteChars, on output from an application in
-//      VT mode, before the output is processed by the state machine.
-//   This ensures that if a user scrolls around in the buffer, and a client
-//      application uses VT to control the cursor/buffer, those commands are
-//      still processed relative to the coordinates before the user scrolled the buffer.
-// Arguments:
-// - <none>
-// Return Value:
-// - <none>
-void SCREEN_INFORMATION::MoveToBottom()
-{
-    const auto virtualView = GetVirtualViewport();
-    LOG_IF_NTSTATUS_FAILED(SetViewportOrigin(true, virtualView.Origin(), true));
-}
-
 // Method Description:
 // - Returns the "virtual" Viewport - the viewport with its bottom at
 //      `_virtualBottom`. For VT operations, this is essentially the mutable
@@ -2738,35 +2674,6 @@ const FontInfoDesired& SCREEN_INFORMATION::GetDesiredFont() const noexcept
     return _desiredFont;
 }
 
-// Method Description:
-// - Returns true iff the scroll margins have been set.
-// Arguments:
-// - <none>
-// Return Value:
-// - true iff the scroll margins have been set.
-bool SCREEN_INFORMATION::AreMarginsSet() const noexcept
-{
-    return _scrollMargins.BottomInclusive() > _scrollMargins.Top();
-}
-
-// Routine Description:
-// - Determines whether a cursor position is within the vertical bounds of the
-//      scroll margins, or the margins aren't set.
-// Parameters:
-// - cursorPosition - The cursor position to test
-// Return value:
-// - true iff the position is in bounds.
-bool SCREEN_INFORMATION::IsCursorInMargins(const COORD cursorPosition) const noexcept
-{
-    // If the margins aren't set, then any position is considered in bounds.
-    if (!AreMarginsSet())
-    {
-        return true;
-    }
-    const auto margins = GetAbsoluteScrollMargins().ToInclusive();
-    return cursorPosition.Y <= margins.Bottom && cursorPosition.Y >= margins.Top;
-}
-
 // Routine Description:
 // - Engages the legacy VT handling quirk; see TextAttribute::StripErroneousVT16VersionsOfLegacyDefaults
 void SCREEN_INFORMATION::SetIgnoreLegacyEquivalentVTAttributes() noexcept
diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp
index 56554954bf0..2be6fc87ea8 100644
--- a/src/host/screenInfo.hpp
+++ b/src/host/screenInfo.hpp
@@ -196,8 +196,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console
     Microsoft::Console::Types::Viewport GetRelativeScrollMargins() const;
     Microsoft::Console::Types::Viewport GetAbsoluteScrollMargins() const;
     void SetScrollMargins(const Microsoft::Console::Types::Viewport margins);
-    bool AreMarginsSet() const noexcept;
-    bool IsCursorInMargins(const COORD cursorPosition) const noexcept;
 
     [[nodiscard]] NTSTATUS UseAlternateScreenBuffer();
     void UseMainScreenBuffer();
@@ -216,13 +214,11 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console
     void SetDefaultAttributes(const TextAttribute& attributes,
                               const TextAttribute& popupAttributes);
 
-    [[nodiscard]] HRESULT VtEraseAll();
     [[nodiscard]] HRESULT ClearBuffer();
 
     void SetTerminalConnection(_In_ Microsoft::Console::Render::VtEngine* const pTtyConnection);
 
     void UpdateBottom();
-    void MoveToBottom();
 
     FontInfo& GetCurrentFont() noexcept;
     const FontInfo& GetCurrentFont() const noexcept;
diff --git a/src/host/ut_host/ApiRoutinesTests.cpp b/src/host/ut_host/ApiRoutinesTests.cpp
index 36e8a156c01..3ca496909de 100644
--- a/src/host/ut_host/ApiRoutinesTests.cpp
+++ b/src/host/ut_host/ApiRoutinesTests.cpp
@@ -34,9 +34,8 @@ class ApiRoutinesTests
         m_state = std::make_unique<CommonState>();
 
         m_state->PrepareGlobalFont();
-        m_state->PrepareGlobalScreenBuffer();
-
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
 
         m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
         if (!m_pHistory)
diff --git a/src/host/ut_host/ClipboardTests.cpp b/src/host/ut_host/ClipboardTests.cpp
index 3cc38e2c2f6..486bd94e7a1 100644
--- a/src/host/ut_host/ClipboardTests.cpp
+++ b/src/host/ut_host/ClipboardTests.cpp
@@ -37,8 +37,8 @@ class ClipboardTests
         m_state = new CommonState();
 
         m_state->PrepareGlobalFont();
-        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
 
         return true;
     }
diff --git a/src/host/ut_host/CommandLineTests.cpp b/src/host/ut_host/CommandLineTests.cpp
index 6062690ea2a..4445eb0478a 100644
--- a/src/host/ut_host/CommandLineTests.cpp
+++ b/src/host/ut_host/CommandLineTests.cpp
@@ -40,8 +40,8 @@ class CommandLineTests
 
     TEST_METHOD_SETUP(MethodSetup)
     {
-        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareReadHandle();
         m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
         if (!m_pHistory)
diff --git a/src/host/ut_host/CommandListPopupTests.cpp b/src/host/ut_host/CommandListPopupTests.cpp
index b0bfe74255e..2b78f86942c 100644
--- a/src/host/ut_host/CommandListPopupTests.cpp
+++ b/src/host/ut_host/CommandListPopupTests.cpp
@@ -46,8 +46,8 @@ class CommandListPopupTests
 
     TEST_METHOD_SETUP(MethodSetup)
     {
-        m_state->PrepareGlobalScreenBuffer();
         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
diff --git a/src/host/ut_host/CommandNumberPopupTests.cpp b/src/host/ut_host/CommandNumberPopupTests.cpp
index 871ed5f46fe..dd71f072968 100644
--- a/src/host/ut_host/CommandNumberPopupTests.cpp
+++ b/src/host/ut_host/CommandNumberPopupTests.cpp
@@ -42,8 +42,8 @@ class CommandNumberPopupTests
 
     TEST_METHOD_SETUP(MethodSetup)
     {
-        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareReadHandle();
         m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
         if (!m_pHistory)
diff --git a/src/host/ut_host/ConptyOutputTests.cpp b/src/host/ut_host/ConptyOutputTests.cpp
index 6e4a9d558a8..d6458d10cdd 100644
--- a/src/host/ut_host/ConptyOutputTests.cpp
+++ b/src/host/ut_host/ConptyOutputTests.cpp
@@ -45,8 +45,8 @@ class ConptyOutputTests
 
         m_state->InitEvents();
         m_state->PrepareGlobalFont();
-        m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight);
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight);
 
         return true;
     }
diff --git a/src/host/ut_host/CopyFromCharPopupTests.cpp b/src/host/ut_host/CopyFromCharPopupTests.cpp
index e4a42ee5ba8..ccc54172a18 100644
--- a/src/host/ut_host/CopyFromCharPopupTests.cpp
+++ b/src/host/ut_host/CopyFromCharPopupTests.cpp
@@ -41,8 +41,8 @@ class CopyFromCharPopupTests
 
     TEST_METHOD_SETUP(MethodSetup)
     {
-        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareReadHandle();
         m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
         if (!m_pHistory)
diff --git a/src/host/ut_host/CopyToCharPopupTests.cpp b/src/host/ut_host/CopyToCharPopupTests.cpp
index 752922a91f7..31726cc872a 100644
--- a/src/host/ut_host/CopyToCharPopupTests.cpp
+++ b/src/host/ut_host/CopyToCharPopupTests.cpp
@@ -41,8 +41,8 @@ class CopyToCharPopupTests
 
     TEST_METHOD_SETUP(MethodSetup)
     {
-        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareReadHandle();
         m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
         if (!m_pHistory)
diff --git a/src/host/ut_host/ObjectTests.cpp b/src/host/ut_host/ObjectTests.cpp
index 17f89d6cd5f..4e2b3eeccb1 100644
--- a/src/host/ut_host/ObjectTests.cpp
+++ b/src/host/ut_host/ObjectTests.cpp
@@ -24,8 +24,8 @@ class ObjectTests
 
         m_state->InitEvents();
         m_state->PrepareGlobalFont();
-        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
 
         return true;
     }
diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp
index 68aec670704..f0e4684ebc6 100644
--- a/src/host/ut_host/ScreenBufferTests.cpp
+++ b/src/host/ut_host/ScreenBufferTests.cpp
@@ -43,8 +43,8 @@ class ScreenBufferTests
         m_state->InitEvents();
         m_state->PrepareGlobalFont({ 1, 1 });
         m_state->PrepareGlobalRenderer();
-        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
 
         return true;
     }
@@ -902,6 +902,8 @@ void ScreenBufferTests::EraseAllTests()
     auto& stateMachine = si.GetStateMachine();
     auto& cursor = si._textBuffer->GetCursor();
 
+    const auto eraseAll = L"\033[2J";
+
     VERIFY_ARE_EQUAL(si.GetViewport().Top(), 0);
 
     ////////////////////////////////////////////////////////////////////////
@@ -912,7 +914,7 @@ void ScreenBufferTests::EraseAllTests()
     VERIFY_ARE_EQUAL(si.GetViewport().Top(), 0);
     VERIFY_ARE_EQUAL(cursor.GetPosition(), originalRelativePosition);
 
-    VERIFY_SUCCEEDED(si.VtEraseAll());
+    stateMachine.ProcessString(eraseAll);
 
     auto viewport = si._viewport;
     VERIFY_ARE_EQUAL(viewport.Top(), 1);
@@ -941,7 +943,7 @@ void ScreenBufferTests::EraseAllTests()
         viewport.RightInclusive(),
         viewport.BottomInclusive()));
 
-    VERIFY_SUCCEEDED(si.VtEraseAll());
+    stateMachine.ProcessString(eraseAll);
     viewport = si._viewport;
     VERIFY_ARE_EQUAL(viewport.Top(), 4);
     newRelativePos = originalRelativePosition;
@@ -972,7 +974,7 @@ void ScreenBufferTests::EraseAllTests()
         viewport.Top(),
         viewport.RightInclusive(),
         viewport.BottomInclusive()));
-    VERIFY_SUCCEEDED(si.VtEraseAll());
+    stateMachine.ProcessString(eraseAll);
 
     viewport = si._viewport;
     auto heightFromBottom = si.GetBufferSize().Height() - (viewport.Height());
@@ -1225,6 +1227,10 @@ void ScreenBufferTests::VtResizeDECCOLM()
     auto getRelativeCursorPosition = [&]() {
         return si.GetTextBuffer().GetCursor().GetPosition() - si.GetViewport().Origin();
     };
+    auto areMarginsSet = [&]() {
+        const auto margins = si.GetRelativeScrollMargins();
+        return margins.BottomInclusive() > margins.Top();
+    };
 
     stateMachine.ProcessString(setInitialMargins);
     stateMachine.ProcessString(setInitialCursor);
@@ -1244,7 +1250,7 @@ void ScreenBufferTests::VtResizeDECCOLM()
     auto newViewHeight = si.GetViewport().Height();
     auto newViewWidth = si.GetViewport().Width();
 
-    VERIFY_IS_TRUE(si.AreMarginsSet());
+    VERIFY_IS_TRUE(areMarginsSet());
     VERIFY_ARE_EQUAL(initialMargins, si.GetRelativeScrollMargins());
     VERIFY_ARE_EQUAL(initialCursorPosition, getRelativeCursorPosition());
     VERIFY_ARE_EQUAL(initialSbHeight, newSbHeight);
@@ -1272,7 +1278,7 @@ void ScreenBufferTests::VtResizeDECCOLM()
     newViewHeight = si.GetViewport().Height();
     newViewWidth = si.GetViewport().Width();
 
-    VERIFY_IS_FALSE(si.AreMarginsSet());
+    VERIFY_IS_FALSE(areMarginsSet());
     VERIFY_ARE_EQUAL(COORD({ 0, 0 }), getRelativeCursorPosition());
     VERIFY_ARE_EQUAL(initialSbHeight, newSbHeight);
     VERIFY_ARE_EQUAL(initialViewHeight, newViewHeight);
@@ -1298,7 +1304,7 @@ void ScreenBufferTests::VtResizeDECCOLM()
     newViewHeight = si.GetViewport().Height();
     newViewWidth = si.GetViewport().Width();
 
-    VERIFY_IS_TRUE(si.AreMarginsSet());
+    VERIFY_IS_TRUE(areMarginsSet());
     VERIFY_ARE_EQUAL(initialMargins, si.GetRelativeScrollMargins());
     VERIFY_ARE_EQUAL(initialCursorPosition, getRelativeCursorPosition());
     VERIFY_ARE_EQUAL(initialSbHeight, newSbHeight);
@@ -1326,7 +1332,7 @@ void ScreenBufferTests::VtResizeDECCOLM()
     newViewHeight = si.GetViewport().Height();
     newViewWidth = si.GetViewport().Width();
 
-    VERIFY_IS_FALSE(si.AreMarginsSet());
+    VERIFY_IS_FALSE(areMarginsSet());
     VERIFY_ARE_EQUAL(COORD({ 0, 0 }), getRelativeCursorPosition());
     VERIFY_ARE_EQUAL(initialSbHeight, newSbHeight);
     VERIFY_ARE_EQUAL(initialViewHeight, newViewHeight);
@@ -5892,6 +5898,11 @@ void ScreenBufferTests::ScreenAlignmentPattern()
     const auto& cursor = si.GetTextBuffer().GetCursor();
     WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
 
+    auto areMarginsSet = [&]() {
+        const auto margins = si.GetRelativeScrollMargins();
+        return margins.BottomInclusive() > margins.Top();
+    };
+
     Log::Comment(L"Set the initial buffer state.");
 
     const auto bufferWidth = si.GetBufferSize().Width();
@@ -5914,7 +5925,7 @@ void ScreenBufferTests::ScreenAlignmentPattern()
 
     // Set some margins.
     stateMachine.ProcessString(L"\x1b[10;20r");
-    VERIFY_IS_TRUE(si.AreMarginsSet());
+    VERIFY_IS_TRUE(areMarginsSet());
 
     // Place the cursor in the center.
     auto cursorPos = COORD{ bufferWidth / 2, (viewportStart + viewportEnd) / 2 };
@@ -5932,7 +5943,7 @@ void ScreenBufferTests::ScreenAlignmentPattern()
     VERIFY_IS_TRUE(_ValidateLinesContain(viewportEnd, bufferHeight, L'Z', bufferAttr));
 
     Log::Comment(L"Margins should not be set.");
-    VERIFY_IS_FALSE(si.AreMarginsSet());
+    VERIFY_IS_FALSE(areMarginsSet());
 
     Log::Comment(L"Cursor position should be moved to home.");
     auto homePosition = COORD{ 0, viewportStart };
@@ -6112,8 +6123,8 @@ void ScreenBufferTests::UpdateVirtualBottomWhenCursorMovesBelowIt()
     Log::Comment(L"The viewport itself should not have changed at this point");
     VERIFY_ARE_EQUAL(initialOrigin, si.GetViewport().Origin());
 
-    Log::Comment(L"But after MoveToBottom, the viewport should align with the new virtual bottom");
-    si.MoveToBottom();
+    Log::Comment(L"But after moving to the virtual viewport, we should align with the new virtual bottom");
+    VERIFY_SUCCEEDED(si.SetViewportOrigin(true, si.GetVirtualViewport().Origin(), true));
     VERIFY_ARE_EQUAL(newVirtualBottom, si.GetViewport().BottomInclusive());
 }
 
@@ -6148,7 +6159,7 @@ void ScreenBufferTests::RetainHorizontalOffsetWhenMovingToBottom()
     VERIFY_ARE_EQUAL(initialOrigin.X, si.GetViewport().Left());
 
     Log::Comment(L"Move the viewport back to the virtual bottom");
-    si.MoveToBottom();
+    VERIFY_SUCCEEDED(si.SetViewportOrigin(true, si.GetVirtualViewport().Origin(), true));
 
     Log::Comment(L"Verify Y offset has moved back and X is unchanged");
     VERIFY_ARE_EQUAL(initialOrigin.Y, si.GetViewport().Top());
diff --git a/src/host/ut_host/SelectionTests.cpp b/src/host/ut_host/SelectionTests.cpp
index 46df9aaeb89..9f02f3dfc00 100644
--- a/src/host/ut_host/SelectionTests.cpp
+++ b/src/host/ut_host/SelectionTests.cpp
@@ -419,8 +419,8 @@ class SelectionInputTests
         m_state = new CommonState();
 
         m_state->PrepareGlobalFont();
-        m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
+        m_state->PrepareGlobalScreenBuffer();
         m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
         if (!m_pHistory)
         {
diff --git a/src/interactivity/inc/IConsoleInputThread.hpp b/src/interactivity/inc/IConsoleInputThread.hpp
index c23d09515d1..bbc25afa041 100644
--- a/src/interactivity/inc/IConsoleInputThread.hpp
+++ b/src/interactivity/inc/IConsoleInputThread.hpp
@@ -34,7 +34,7 @@ namespace Microsoft::Console::Interactivity
         // .ctor
         IConsoleInputThread() :
             _hThread(nullptr),
-            _dwThreadId((DWORD)(-1)) {}
+            _dwThreadId(gsl::narrow_cast<DWORD>(-1)) {}
 
         // Protected Variables
         HANDLE _hThread;
diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp
index e47d5a21450..6d2b33afa80 100644
--- a/src/renderer/base/renderer.hpp
+++ b/src/renderer/base/renderer.hpp
@@ -24,6 +24,14 @@ Author(s):
 #include "../../buffer/out/textBuffer.hpp"
 #include "../../buffer/out/CharRow.hpp"
 
+// fwdecl unittest classes
+#ifdef UNIT_TESTING
+namespace TerminalCoreUnitTests
+{
+    class ConptyRoundtripTests;
+};
+#endif
+
 namespace Microsoft::Console::Render
 {
     class Renderer
@@ -118,6 +126,7 @@ namespace Microsoft::Console::Render
 
 #ifdef UNIT_TESTING
         friend class ConptyOutputTests;
+        friend class TerminalCoreUnitTests::ConptyRoundtripTests;
 #endif
     };
 }
diff --git a/src/renderer/inc/DummyRenderer.hpp b/src/renderer/inc/DummyRenderer.hpp
index 0d168311a6e..b1e43b9d5fc 100644
--- a/src/renderer/inc/DummyRenderer.hpp
+++ b/src/renderer/inc/DummyRenderer.hpp
@@ -20,6 +20,5 @@ class DummyRenderer final : public Microsoft::Console::Render::Renderer
     DummyRenderer(Microsoft::Console::Render::IRenderData* pData = nullptr) :
         Microsoft::Console::Render::Renderer(_renderSettings, pData, nullptr, 0, nullptr) {}
 
-private:
     Microsoft::Console::Render::RenderSettings _renderSettings;
 };
diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp
index 81b7290dc7c..32a30c6f927 100644
--- a/src/terminal/adapter/InteractDispatch.cpp
+++ b/src/terminal/adapter/InteractDispatch.cpp
@@ -3,12 +3,21 @@
 
 #include "precomp.h"
 
+// Some of the interactivity classes pulled in by ServiceLocator.hpp are not
+// yet audit-safe, so for now we'll need to disable a bunch of warnings.
+#pragma warning(disable : 26432)
+#pragma warning(disable : 26440)
+#pragma warning(disable : 26455)
+
 #include "InteractDispatch.hpp"
 #include "conGetSet.hpp"
+#include "../../host/conddkrefs.h"
+#include "../../interactivity/inc/ServiceLocator.hpp"
 #include "../../interactivity/inc/EventSynthesis.hpp"
 #include "../../types/inc/Viewport.hpp"
 #include "../../inc/unicode.hpp"
 
+using namespace Microsoft::Console::Interactivity;
 using namespace Microsoft::Console::Types;
 using namespace Microsoft::Console::VirtualTerminal;
 
@@ -47,7 +56,7 @@ bool InteractDispatch::WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inpu
 // - True.
 bool InteractDispatch::WriteCtrlKey(const KeyEvent& event)
 {
-    _pConApi->WriteControlInput(event);
+    HandleGenericKeyEvent(event, false);
     return true;
 }
 
@@ -67,7 +76,7 @@ bool InteractDispatch::WriteString(const std::wstring_view string)
 
         for (const auto& wch : string)
         {
-            std::deque<std::unique_ptr<KeyEvent>> convertedEvents = Microsoft::Console::Interactivity::CharToKeyEvents(wch, codepage);
+            std::deque<std::unique_ptr<KeyEvent>> convertedEvents = CharToKeyEvents(wch, codepage);
 
             std::move(convertedEvents.begin(),
                       convertedEvents.end(),
@@ -101,14 +110,15 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio
     switch (function)
     {
     case DispatchTypes::WindowManipulationType::RefreshWindow:
-        _pConApi->RefreshWindow();
+        _pConApi->GetTextBuffer().TriggerRedrawAll();
         return true;
     case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters:
         // TODO:GH#1765 We should introduce a better `ResizeConpty` function to
         // the ConGetSet interface, that specifically handles a conpty resize.
         if (_pConApi->ResizeWindow(parameter2.value_or(0), parameter1.value_or(0)))
         {
-            _pConApi->SuppressResizeRepaint();
+            auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
+            THROW_IF_FAILED(gci.GetVtIo()->SuppressResizeRepaint());
         }
         return true;
     default:
@@ -135,26 +145,29 @@ bool InteractDispatch::MoveCursor(const size_t row, const size_t col)
     const size_t colFixed = col - 1;
 
     // First retrieve some information about the buffer
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    COORD coordCursor = csbiex.dwCursorPosition;
+    const auto viewport = _pConApi->GetViewport().to_small_rect();
+    auto& cursor = _pConApi->GetTextBuffer().GetCursor();
+    auto coordCursor = cursor.GetPosition();
 
     // Safely convert the size_t positions we were given into shorts (which is the size the console deals with)
     THROW_IF_FAILED(SizeTToShort(rowFixed, &coordCursor.Y));
     THROW_IF_FAILED(SizeTToShort(colFixed, &coordCursor.X));
 
     // Set the line and column values as offsets from the viewport edge. Use safe math to prevent overflow.
-    THROW_IF_FAILED(ShortAdd(coordCursor.Y, csbiex.srWindow.Top, &coordCursor.Y));
-    THROW_IF_FAILED(ShortAdd(coordCursor.X, csbiex.srWindow.Left, &coordCursor.X));
+    THROW_IF_FAILED(ShortAdd(coordCursor.Y, viewport.Top, &coordCursor.Y));
+    THROW_IF_FAILED(ShortAdd(coordCursor.X, viewport.Left, &coordCursor.X));
 
     // Apply boundary tests to ensure the cursor isn't outside the viewport rectangle.
-    coordCursor.Y = std::clamp(coordCursor.Y, csbiex.srWindow.Top, gsl::narrow<SHORT>(csbiex.srWindow.Bottom - 1));
-    coordCursor.X = std::clamp(coordCursor.X, csbiex.srWindow.Left, gsl::narrow<SHORT>(csbiex.srWindow.Right - 1));
+    coordCursor.Y = std::clamp(coordCursor.Y, viewport.Top, viewport.Bottom);
+    coordCursor.X = std::clamp(coordCursor.X, viewport.Left, viewport.Right);
+
+    // MSFT: 15813316 - Try to use this MoveCursor call to inherit the cursor position.
+    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
+    RETURN_IF_FAILED(gci.GetVtIo()->SetCursorPosition(coordCursor));
 
     // Finally, attempt to set the adjusted cursor position back into the console.
-    _pConApi->SetCursorPosition(coordCursor);
+    cursor.SetPosition(coordCursor);
+    cursor.SetHasMoved(true);
     return true;
 }
 
diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp
index 773087fa539..53b54f3b5ee 100644
--- a/src/terminal/adapter/adaptDispatch.cpp
+++ b/src/terminal/adapter/adaptDispatch.cpp
@@ -5,6 +5,7 @@
 
 #include "adaptDispatch.hpp"
 #include "conGetSet.hpp"
+#include "../../renderer/base/renderer.hpp"
 #include "../../types/inc/Viewport.hpp"
 #include "../../types/inc/utils.hpp"
 #include "../../inc/unicode.hpp"
@@ -26,8 +27,11 @@ bool NoOp() noexcept
 }
 
 // Note: AdaptDispatch will take ownership of pConApi and pDefaults
-AdaptDispatch::AdaptDispatch(std::unique_ptr<ConGetSet> pConApi) :
+AdaptDispatch::AdaptDispatch(std::unique_ptr<ConGetSet> pConApi, Renderer& renderer, RenderSettings& renderSettings, TerminalInput& terminalInput) :
     _pConApi{ std::move(pConApi) },
+    _renderer{ renderer },
+    _renderSettings{ renderSettings },
+    _terminalInput{ terminalInput },
     _usingAltBuffer(false),
     _isOriginModeRelative(false), // by default, the DECOM origin mode is absolute.
     _isDECCOLMAllowed(false), // by default, DECCOLM is not allowed.
@@ -159,6 +163,36 @@ bool AdaptDispatch::CursorPrevLine(const size_t distance)
     return _CursorMovePosition(Offset::Backward(distance), Offset::Absolute(1), true);
 }
 
+// Routine Description:
+// - Returns the coordinates of the vertical scroll margins.
+// Arguments:
+// - viewport - The viewport rect (exclusive).
+// - absolute - Should coordinates be absolute or relative to the viewport.
+// Return Value:
+// - A std::pair containing the top and bottom coordinates (inclusive).
+std::pair<int, int> AdaptDispatch::_GetVerticalMargins(const til::rect& viewport, const bool absolute)
+{
+    // If the top is out of range, reset the margins completely.
+    const auto bottommostRow = viewport.bottom - viewport.top - 1;
+    if (_scrollMargins.Top >= bottommostRow)
+    {
+        _scrollMargins.Top = _scrollMargins.Bottom = 0;
+        _pConApi->SetScrollingRegion(_scrollMargins);
+    }
+    // If margins aren't set, use the full extent of the viewport.
+    const auto marginsSet = _scrollMargins.Top < _scrollMargins.Bottom;
+    auto topMargin = marginsSet ? _scrollMargins.Top : 0;
+    auto bottomMargin = marginsSet ? _scrollMargins.Bottom : bottommostRow;
+    // If the bottom is out of range, clamp it to the bottommost row.
+    bottomMargin = std::min(bottomMargin, bottommostRow);
+    if (absolute)
+    {
+        topMargin += viewport.top;
+        bottomMargin += viewport.top;
+    }
+    return { topMargin, bottomMargin };
+}
+
 // Routine Description:
 // - Generalizes cursor movement to a specific position, which can be absolute or relative.
 // Arguments:
@@ -167,36 +201,25 @@ bool AdaptDispatch::CursorPrevLine(const size_t distance)
 // - clampInMargins - Should the position be clamped within the scrolling margins
 // Return Value:
 // - True.
-bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins) const
+bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins)
 {
     // First retrieve some information about the buffer
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    // Calculate the viewport boundaries as inclusive values.
-    // srWindow is exclusive so we need to subtract 1 from the bottom.
-    const int viewportTop = csbiex.srWindow.Top;
-    const int viewportBottom = csbiex.srWindow.Bottom - 1;
-
-    // Calculate the absolute margins of the scrolling area.
-    const int topMargin = viewportTop + _scrollMargins.Top;
-    const int bottomMargin = viewportTop + _scrollMargins.Bottom;
-    const bool marginsSet = topMargin < bottomMargin;
+    const auto viewport = _pConApi->GetViewport();
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    auto& cursor = textBuffer.GetCursor();
+    const auto cursorPosition = cursor.GetPosition();
+    const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
 
     // For relative movement, the given offsets will be relative to
     // the current cursor position.
-    int row = csbiex.dwCursorPosition.Y;
-    int col = csbiex.dwCursorPosition.X;
+    int row = cursorPosition.Y;
+    int col = cursorPosition.X;
 
     // But if the row is absolute, it will be relative to the top of the
     // viewport, or the top margin, depending on the origin mode.
     if (rowOffset.IsAbsolute)
     {
-        row = _isOriginModeRelative ? topMargin : viewportTop;
+        row = _isOriginModeRelative ? topMargin : viewport.top;
     }
 
     // And if the column is absolute, it'll be relative to column 0.
@@ -209,13 +232,13 @@ bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
     // Adjust the base position by the given offsets and clamp the results.
     // The row is constrained within the viewport's vertical boundaries,
     // while the column is constrained by the buffer width.
-    row = std::clamp(row + rowOffset.Value, viewportTop, viewportBottom);
-    col = std::clamp(col + colOffset.Value, 0, csbiex.dwSize.X - 1);
+    row = std::clamp<int>(row + rowOffset.Value, viewport.top, viewport.bottom - 1);
+    col = std::clamp<int>(col + colOffset.Value, 0, textBuffer.GetSize().Width() - 1);
 
     // If the operation needs to be clamped inside the margins, or the origin
     // mode is relative (which always requires margin clamping), then the row
     // may need to be adjusted further.
-    if (marginsSet && (clampInMargins || _isOriginModeRelative))
+    if (clampInMargins || _isOriginModeRelative)
     {
         // See microsoft/terminal#2929 - If the cursor is _below_ the top
         // margin, it should stay below the top margin. If it's _above_ the
@@ -225,11 +248,11 @@ bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
         // to the bottom margin. See
         // ScreenBufferTests::CursorUpDownOutsideMargins for a test of that
         // behavior.
-        if (csbiex.dwCursorPosition.Y >= topMargin)
+        if (cursorPosition.Y >= topMargin)
         {
             row = std::max(row, topMargin);
         }
-        if (csbiex.dwCursorPosition.Y <= bottomMargin)
+        if (cursorPosition.Y <= bottomMargin)
         {
             row = std::min(row, bottomMargin);
         }
@@ -237,11 +260,29 @@ bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
 
     // Finally, attempt to set the adjusted cursor position back into the console.
     const COORD newPos = { gsl::narrow_cast<SHORT>(col), gsl::narrow_cast<SHORT>(row) };
-    _pConApi->SetCursorPosition(newPos);
+    cursor.SetPosition(textBuffer.ClampPositionWithinLine(newPos));
+    _ApplyCursorMovementFlags(cursor);
 
     return true;
 }
 
+// Routine Description:
+// - Helper method which applies a bunch of flags that are typically set whenever
+//   the cursor is moved. The IsOn flag is set to true, and the Delay flag to false,
+//   to force a blinking cursor to be visible, so the user can immediately see the
+//   new position. The HasMoved flag is set to let the accessibility notifier know
+//   that there was movement that needs to be reported.
+// Arguments:
+// - cursor - The cursor instance to be updated
+// Return Value:
+// - <none>
+void AdaptDispatch::_ApplyCursorMovementFlags(Cursor& cursor) noexcept
+{
+    cursor.SetDelay(false);
+    cursor.SetIsOn(true);
+    cursor.SetHasMoved(true);
+}
+
 // Routine Description:
 // - CHA - Moves the cursor to an exact X/Column position on the current line.
 // Arguments:
@@ -311,28 +352,23 @@ bool AdaptDispatch::CursorPosition(const size_t line, const size_t column)
 bool AdaptDispatch::CursorSaveState()
 {
     // First retrieve some information about the buffer
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    const auto attributes = _pConApi->GetTextAttributes();
+    const auto viewport = _pConApi->GetViewport();
+    const auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto attributes = textBuffer.GetCurrentAttributes();
 
     // The cursor is given to us by the API as relative to the whole buffer.
     // But in VT speak, the cursor row should be relative to the current viewport top.
-    COORD coordCursor = csbiex.dwCursorPosition;
-    coordCursor.Y -= csbiex.srWindow.Top;
+    auto cursorPosition = textBuffer.GetCursor().GetPosition();
+    cursorPosition.Y -= gsl::narrow_cast<short>(viewport.top);
 
     // VT is also 1 based, not 0 based, so correct by 1.
     auto& savedCursorState = _savedCursorState.at(_usingAltBuffer);
-    savedCursorState.Column = coordCursor.X + 1;
-    savedCursorState.Row = coordCursor.Y + 1;
+    savedCursorState.Column = cursorPosition.X + 1;
+    savedCursorState.Row = cursorPosition.Y + 1;
     savedCursorState.IsOriginModeRelative = _isOriginModeRelative;
     savedCursorState.Attributes = attributes;
     savedCursorState.TermOutput = _termOutput;
-    savedCursorState.C1ControlsAccepted = _pConApi->GetParserMode(StateMachine::Mode::AcceptC1);
+    savedCursorState.C1ControlsAccepted = _GetParserMode(StateMachine::Mode::AcceptC1);
     savedCursorState.CodePage = _pConApi->GetConsoleOutputCP();
 
     return true;
@@ -353,12 +389,14 @@ bool AdaptDispatch::CursorRestoreState()
     auto row = savedCursorState.Row;
     const auto col = savedCursorState.Column;
 
-    // If the origin mode is relative, and the scrolling region is set (the bottom is non-zero),
-    // we need to make sure the restored position is clamped within the margins.
-    if (savedCursorState.IsOriginModeRelative && _scrollMargins.Bottom != 0)
+    // If the origin mode is relative, we need to make sure the restored
+    // position is clamped within the margins.
+    if (savedCursorState.IsOriginModeRelative)
     {
+        const auto viewport = _pConApi->GetViewport();
+        const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, false);
         // VT origin is at 1,1 so we need to add 1 to these margins.
-        row = std::clamp(row, _scrollMargins.Top + 1u, _scrollMargins.Bottom + 1u);
+        row = std::clamp(row, topMargin + 1u, bottomMargin + 1u);
     }
 
     // The saved coordinates are always absolute, so we need reset the origin mode temporarily.
@@ -394,60 +432,100 @@ bool AdaptDispatch::CursorRestoreState()
 // - True.
 bool AdaptDispatch::CursorVisibility(const bool fIsVisible)
 {
-    // This uses a private API instead of the public one, because the public API
-    //      will set the cursor shape back to legacy.
-    _pConApi->SetCursorVisibility(fIsVisible);
+    _pConApi->GetTextBuffer().GetCursor().SetIsVisible(fIsVisible);
     return true;
 }
 
 // Routine Description:
-// - This helper will do the work of performing an insert or delete character operation
-// - Both operations are similar in that they cut text and move it left or right in the buffer, padding the leftover area with spaces.
+// - Scrolls an area of the buffer in a vertical direction.
 // Arguments:
-// - count - The number of characters to insert
-// - isInsert - TRUE if insert mode (cut and paste to the right, away from the cursor). FALSE if delete mode (cut and paste to the left, toward the cursor)
+// - textBuffer - Target buffer to be scrolled.
+// - fillRect - Area of the buffer that will be affected.
+// - delta - Distance to move (positive is down, negative is up).
 // Return Value:
 // - <none>
-void AdaptDispatch::_InsertDeleteHelper(const size_t count, const bool isInsert) const
-{
-    // We'll be doing short math on the distance since all console APIs use shorts. So check that we can successfully convert the uint into a short first.
-    SHORT distance;
-    THROW_IF_FAILED(SizeTToShort(count, &distance));
-
-    // get current cursor, attributes
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    const auto cursor = csbiex.dwCursorPosition;
-    // Rectangle to cut out of the existing buffer. This is inclusive.
-    SMALL_RECT srScroll;
-    srScroll.Left = cursor.X;
-    srScroll.Right = _pConApi->GetLineWidth(cursor.Y) - 1;
-    srScroll.Top = cursor.Y;
-    srScroll.Bottom = srScroll.Top;
-
-    // Paste coordinate for cut text above
-    COORD coordDestination;
-    coordDestination.Y = cursor.Y;
-    coordDestination.X = cursor.X;
-
-    if (isInsert)
+void AdaptDispatch::_ScrollRectVertically(TextBuffer& textBuffer, const til::rect& scrollRect, const int32_t delta)
+{
+    const auto absoluteDelta = std::min(std::abs(delta), scrollRect.height());
+    if (absoluteDelta < scrollRect.height())
     {
-        // Insert makes space by moving characters out to the right. So move the destination of the cut/paste region.
-        THROW_IF_FAILED(ShortAdd(coordDestination.X, distance, &coordDestination.X));
+        // For now we're assuming the scrollRect is always the full width of the
+        // buffer, but this will likely need to be extended to support scrolling
+        // of arbitrary widths at some point in the future.
+        const auto top = gsl::narrow_cast<short>(delta > 0 ? scrollRect.top : (scrollRect.top + absoluteDelta));
+        const auto height = gsl::narrow_cast<short>(scrollRect.height() - absoluteDelta);
+        const auto actualDelta = gsl::narrow_cast<short>(delta > 0 ? absoluteDelta : -absoluteDelta);
+        textBuffer.ScrollRows(top, height, actualDelta);
+        textBuffer.TriggerRedraw(Viewport::FromInclusive(scrollRect.to_small_rect()));
     }
-    else
+
+    // Rows revealed by the scroll are filled with standard erase attributes.
+    auto eraseRect = scrollRect;
+    eraseRect.top = delta > 0 ? scrollRect.top : (scrollRect.bottom - absoluteDelta);
+    eraseRect.bottom = eraseRect.top + absoluteDelta;
+    auto eraseAttributes = textBuffer.GetCurrentAttributes();
+    eraseAttributes.SetStandardErase();
+    _FillRect(textBuffer, eraseRect, L' ', eraseAttributes);
+
+    // Also reset the line rendition for the erased rows.
+    textBuffer.ResetLineRenditionRange(eraseRect.top, eraseRect.bottom);
+}
+
+// Routine Description:
+// - Scrolls an area of the buffer in a horizontal direction.
+// Arguments:
+// - textBuffer - Target buffer to be scrolled.
+// - fillRect - Area of the buffer that will be affected.
+// - delta - Distance to move (positive is right, negative is left).
+// Return Value:
+// - <none>
+void AdaptDispatch::_ScrollRectHorizontally(TextBuffer& textBuffer, const til::rect& scrollRect, const int32_t delta)
+{
+    const auto absoluteDelta = std::min(std::abs(delta), scrollRect.width());
+    if (absoluteDelta < scrollRect.width())
     {
-        // Delete scrolls the affected region to the left, relying on the clipping rect to actually delete the characters.
-        THROW_IF_FAILED(ShortSub(coordDestination.X, distance, &coordDestination.X));
+        const auto left = gsl::narrow_cast<short>(delta > 0 ? scrollRect.left : (scrollRect.left + absoluteDelta));
+        const auto top = gsl::narrow_cast<short>(scrollRect.top);
+        const auto width = gsl::narrow_cast<short>(scrollRect.width() - absoluteDelta);
+        const auto height = gsl::narrow_cast<short>(scrollRect.height());
+        const auto actualDelta = gsl::narrow_cast<short>(delta > 0 ? absoluteDelta : -absoluteDelta);
+
+        const auto source = Viewport::FromDimensions({ left, top }, width, height);
+        const auto target = Viewport::Offset(source, { actualDelta, 0 });
+        const auto walkDirection = Viewport::DetermineWalkDirection(source, target);
+        auto sourcePos = source.GetWalkOrigin(walkDirection);
+        auto targetPos = target.GetWalkOrigin(walkDirection);
+        do
+        {
+            const auto data = OutputCell(*textBuffer.GetCellDataAt(sourcePos));
+            textBuffer.Write(OutputCellIterator({ &data, 1 }), targetPos);
+            source.WalkInBounds(sourcePos, walkDirection);
+        } while (target.WalkInBounds(targetPos, walkDirection));
     }
 
-    // Note the revealed characters are filled with the standard erase attributes.
-    _pConApi->ScrollRegion(srScroll, srScroll, coordDestination, true);
+    // Columns revealed by the scroll are filled with standard erase attributes.
+    auto eraseRect = scrollRect;
+    eraseRect.left = delta > 0 ? scrollRect.left : (scrollRect.right - absoluteDelta);
+    eraseRect.right = eraseRect.left + absoluteDelta;
+    auto eraseAttributes = textBuffer.GetCurrentAttributes();
+    eraseAttributes.SetStandardErase();
+    _FillRect(textBuffer, eraseRect, L' ', eraseAttributes);
+}
+
+// Routine Description:
+// - This helper will do the work of performing an insert or delete character operation
+// - Both operations are similar in that they cut text and move it left or right in the buffer, padding the leftover area with spaces.
+// Arguments:
+// - delta - Number of characters to modify (positive if inserting, negative if deleting).
+// Return Value:
+// - <none>
+void AdaptDispatch::_InsertDeleteCharacterHelper(const int32_t delta)
+{
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto row = textBuffer.GetCursor().GetPosition().Y;
+    const auto startCol = textBuffer.GetCursor().GetPosition().X;
+    const auto endCol = textBuffer.GetLineWidth(row);
+    _ScrollRectHorizontally(textBuffer, { startCol, row, endCol, row + 1 }, delta);
 }
 
 // Routine Description:
@@ -459,7 +537,7 @@ void AdaptDispatch::_InsertDeleteHelper(const size_t count, const bool isInsert)
 // - True.
 bool AdaptDispatch::InsertCharacter(const size_t count)
 {
-    _InsertDeleteHelper(count, true);
+    _InsertDeleteCharacterHelper(gsl::narrow_cast<int32_t>(count));
     return true;
 }
 
@@ -472,58 +550,32 @@ bool AdaptDispatch::InsertCharacter(const size_t count)
 // - True.
 bool AdaptDispatch::DeleteCharacter(const size_t count)
 {
-    _InsertDeleteHelper(count, false);
+    _InsertDeleteCharacterHelper(-gsl::narrow_cast<int32_t>(count));
     return true;
 }
 
 // Routine Description:
-// - Internal helper to erase one particular line of the buffer. Either from beginning to the cursor, from the cursor to the end, or the entire line.
-// - Used by both erase line (used just once) and by erase screen (used in a loop) to erase a portion of the buffer.
+// - Fills an area of the buffer with a given character and attributes.
 // Arguments:
-// - csbiex - Pointer to the console screen buffer that we will be erasing (and getting cursor data from within)
-// - eraseType - Enumeration mode of which kind of erase to perform: beginning to cursor, cursor to end, or entire line.
-// - lineId - The line number (array index value, starts at 0) of the line to operate on within the buffer.
-//           - This is not aware of circular buffer. Line 0 is always the top visible line if you scrolled the whole way up the window.
+// - textBuffer - Target buffer to be filled.
+// - fillRect - Area of the buffer that will be affected.
+// - fillChar - Character to be written to the buffer.
+// - fillAttrs - Attributes to be written to the buffer.
 // Return Value:
 // - <none>
-void AdaptDispatch::_EraseSingleLineHelper(const CONSOLE_SCREEN_BUFFER_INFOEX& csbiex,
-                                           const DispatchTypes::EraseType eraseType,
-                                           const size_t lineId) const
+void AdaptDispatch::_FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const wchar_t fillChar, const TextAttribute fillAttrs)
 {
-    COORD coordStartPosition = { 0 };
-    THROW_IF_FAILED(SizeTToShort(lineId, &coordStartPosition.Y));
-
-    // determine start position from the erase type
-    // remember that erases are inclusive of the current cursor position.
-    switch (eraseType)
+    if (fillRect.left < fillRect.right && fillRect.top < fillRect.bottom)
     {
-    case DispatchTypes::EraseType::FromBeginning:
-    case DispatchTypes::EraseType::All:
-        coordStartPosition.X = 0; // from beginning and the whole line start from the left most edge of the buffer.
-        break;
-    case DispatchTypes::EraseType::ToEnd:
-        coordStartPosition.X = csbiex.dwCursorPosition.X; // from the current cursor position (including it)
-        break;
-    }
-
-    DWORD nLength = 0;
-
-    // determine length of erase from erase type
-    switch (eraseType)
-    {
-    case DispatchTypes::EraseType::FromBeginning:
-        // +1 because if cursor were at the left edge, the length would be 0 and we want to paint at least the 1 character the cursor is on.
-        nLength = csbiex.dwCursorPosition.X + 1;
-        break;
-    case DispatchTypes::EraseType::ToEnd:
-    case DispatchTypes::EraseType::All:
-        // Remember the .X value is 1 farther than the right most column in the buffer. Therefore no +1.
-        nLength = _pConApi->GetLineWidth(lineId) - coordStartPosition.X;
-        break;
+        const auto fillWidth = gsl::narrow_cast<size_t>(fillRect.right - fillRect.left);
+        const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillWidth };
+        const auto col = gsl::narrow_cast<short>(fillRect.left);
+        for (auto row = gsl::narrow_cast<short>(fillRect.top); row < fillRect.bottom; row++)
+        {
+            textBuffer.WriteLine(fillData, COORD{ col, row }, false);
+        }
+        _pConApi->NotifyAccessibilityChange(fillRect);
     }
-
-    // Note that the region is filled with the standard erase attributes.
-    _pConApi->FillRegion(coordStartPosition, nLength, L' ', true);
 }
 
 // Routine Description:
@@ -537,19 +589,14 @@ void AdaptDispatch::_EraseSingleLineHelper(const CONSOLE_SCREEN_BUFFER_INFOEX& c
 // - True.
 bool AdaptDispatch::EraseCharacters(const size_t numChars)
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    const COORD startPosition = csbiex.dwCursorPosition;
-
-    const SHORT remainingSpaces = csbiex.dwSize.X - startPosition.X;
-    const size_t actualRemaining = gsl::narrow_cast<size_t>((remainingSpaces < 0) ? 0 : remainingSpaces);
-    // erase at max the number of characters remaining in the line from the current position.
-    const auto eraseLength = (numChars <= actualRemaining) ? numChars : actualRemaining;
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto row = textBuffer.GetCursor().GetPosition().Y;
+    const auto startCol = textBuffer.GetCursor().GetPosition().X;
+    const auto endCol = std::min<size_t>(startCol + numChars, textBuffer.GetLineWidth(row));
 
-    // Note that the region is filled with the standard erase attributes.
-    _pConApi->FillRegion(startPosition, eraseLength, L' ', true);
+    auto eraseAttributes = textBuffer.GetCurrentAttributes();
+    eraseAttributes.SetStandardErase();
+    _FillRect(textBuffer, { startCol, row, gsl::narrow_cast<til::CoordType>(endCol), row + 1 }, L' ', eraseAttributes);
 
     return true;
 }
@@ -594,12 +641,14 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
         return !_pConApi->IsConsolePty();
     }
 
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
+    const auto viewport = _pConApi->GetViewport();
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto bufferWidth = textBuffer.GetSize().Width();
+    const auto row = textBuffer.GetCursor().GetPosition().Y;
+    const auto col = textBuffer.GetCursor().GetPosition().X;
+
+    auto eraseAttributes = textBuffer.GetCurrentAttributes();
+    eraseAttributes.SetStandardErase();
 
     // When erasing the display, every line that is erased in full should be
     // reset to single width. When erasing to the end, this could include
@@ -609,46 +658,15 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
     // the line is double width).
     if (eraseType == DispatchTypes::EraseType::FromBeginning)
     {
-        const auto endRow = csbiex.dwCursorPosition.Y;
-        _pConApi->ResetLineRenditionRange(csbiex.srWindow.Top, endRow);
-    }
-    if (eraseType == DispatchTypes::EraseType::ToEnd)
-    {
-        const auto startRow = csbiex.dwCursorPosition.Y + (csbiex.dwCursorPosition.X > 0 ? 1 : 0);
-        _pConApi->ResetLineRenditionRange(startRow, csbiex.srWindow.Bottom);
-    }
-
-    // What we need to erase is grouped into 3 types:
-    // 1. Lines before cursor
-    // 2. Cursor Line
-    // 3. Lines after cursor
-    // We erase one or more of these based on the erase type:
-    // A. FromBeginning - Erase 1 and Some of 2.
-    // B. ToEnd - Erase some of 2 and 3.
-    // C. All - Erase 1, 2, and 3.
-
-    // 1. Lines before cursor line
-    if (eraseType == DispatchTypes::EraseType::FromBeginning)
-    {
-        // For beginning and all, erase all complete lines before (above vertically) from the cursor position.
-        for (SHORT startLine = csbiex.srWindow.Top; startLine < csbiex.dwCursorPosition.Y; startLine++)
-        {
-            _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine);
-        }
+        textBuffer.ResetLineRenditionRange(viewport.top, row);
+        _FillRect(textBuffer, { 0, viewport.top, bufferWidth, row }, L' ', eraseAttributes);
+        _FillRect(textBuffer, { 0, row, col + 1, row + 1 }, L' ', eraseAttributes);
     }
-
-    // 2. Cursor Line
-    _EraseSingleLineHelper(csbiex, eraseType, csbiex.dwCursorPosition.Y);
-
-    // 3. Lines after cursor line
     if (eraseType == DispatchTypes::EraseType::ToEnd)
     {
-        // For beginning and all, erase all complete lines after (below vertically) the cursor position.
-        // Remember that the viewport bottom value is 1 beyond the viewable area of the viewport.
-        for (SHORT startLine = csbiex.dwCursorPosition.Y + 1; startLine < csbiex.srWindow.Bottom; startLine++)
-        {
-            _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine);
-        }
+        textBuffer.ResetLineRenditionRange(col > 0 ? row + 1 : row, viewport.bottom);
+        _FillRect(textBuffer, { col, row, bufferWidth, row + 1 }, L' ', eraseAttributes);
+        _FillRect(textBuffer, { 0, row + 1, bufferWidth, viewport.bottom }, L' ', eraseAttributes);
     }
 
     return true;
@@ -662,15 +680,26 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
 // - True if handled successfully. False otherwise.
 bool AdaptDispatch::EraseInLine(const DispatchTypes::EraseType eraseType)
 {
-    RETURN_BOOL_IF_FALSE(eraseType <= DispatchTypes::EraseType::All);
-
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    _EraseSingleLineHelper(csbiex, eraseType, csbiex.dwCursorPosition.Y);
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto row = textBuffer.GetCursor().GetPosition().Y;
+    const auto col = textBuffer.GetCursor().GetPosition().X;
 
-    return true;
+    auto eraseAttributes = textBuffer.GetCurrentAttributes();
+    eraseAttributes.SetStandardErase();
+    switch (eraseType)
+    {
+    case DispatchTypes::EraseType::FromBeginning:
+        _FillRect(textBuffer, { 0, row, col + 1, row + 1 }, L' ', eraseAttributes);
+        return true;
+    case DispatchTypes::EraseType::ToEnd:
+        _FillRect(textBuffer, { col, row, textBuffer.GetLineWidth(row), row + 1 }, L' ', eraseAttributes);
+        return true;
+    case DispatchTypes::EraseType::All:
+        _FillRect(textBuffer, { 0, row, textBuffer.GetLineWidth(row), row + 1 }, L' ', eraseAttributes);
+        return true;
+    default:
+        return false;
+    }
 }
 
 // Routine Description:
@@ -682,7 +711,7 @@ bool AdaptDispatch::EraseInLine(const DispatchTypes::EraseType eraseType)
 // - True.
 bool AdaptDispatch::SetLineRendition(const LineRendition rendition)
 {
-    _pConApi->SetCurrentLineRendition(rendition);
+    _pConApi->GetTextBuffer().SetCurrentLineRendition(rendition);
     return true;
 }
 
@@ -818,34 +847,31 @@ void AdaptDispatch::_OperatingStatus() const
 // - <none>
 // Return Value:
 // - <none>
-void AdaptDispatch::_CursorPositionReport() const
+void AdaptDispatch::_CursorPositionReport()
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
+    const auto viewport = _pConApi->GetViewport();
+    const auto& textBuffer = _pConApi->GetTextBuffer();
 
     // First pull the cursor position relative to the entire buffer out of the console.
-    COORD coordCursorPos = csbiex.dwCursorPosition;
+    auto cursorPosition = textBuffer.GetCursor().GetPosition();
 
     // Now adjust it for its position in respect to the current viewport top.
-    coordCursorPos.Y -= csbiex.srWindow.Top;
+    cursorPosition.Y -= gsl::narrow_cast<short>(viewport.top);
 
     // NOTE: 1,1 is the top-left corner of the viewport in VT-speak, so add 1.
-    coordCursorPos.X++;
-    coordCursorPos.Y++;
+    cursorPosition.X++;
+    cursorPosition.Y++;
 
     // If the origin mode is relative, line numbers start at top margin of the scrolling region.
     if (_isOriginModeRelative)
     {
-        coordCursorPos.Y -= _scrollMargins.Top;
+        const auto topMargin = _GetVerticalMargins(viewport, false).first;
+        cursorPosition.Y -= gsl::narrow_cast<short>(topMargin);
     }
 
     // Now send it back into the input channel of the console.
     // First format the response string.
-    const auto response = wil::str_printf<std::wstring>(L"\x1b[%d;%dR", coordCursorPos.Y, coordCursorPos.X);
+    const auto response = wil::str_printf<std::wstring>(L"\x1b[%d;%dR", cursorPosition.Y, cursorPosition.X);
     _WriteResponse(response);
 }
 
@@ -884,45 +910,16 @@ void AdaptDispatch::_WriteResponse(const std::wstring_view reply) const
 // Routine Description:
 // - Generalizes scrolling movement for up/down
 // Arguments:
-// - scrollDirection - Specific direction to move
-// - distance - Magnitude of the move
+// - delta - Distance to move (positive is down, negative is up)
 // Return Value:
 // - <none>
-void AdaptDispatch::_ScrollMovement(const ScrollDirection scrollDirection, const size_t distance) const
-{
-    // We'll be doing short math on the distance since all console APIs use shorts. So check that we can successfully convert the size_t into a short first.
-    SHORT dist;
-    THROW_IF_FAILED(SizeTToShort(distance, &dist));
-
-    // get current cursor
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    // Rectangle to cut out of the existing buffer. This is inclusive.
-    // It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width.
-    SMALL_RECT srScreen;
-    srScreen.Left = 0;
-    srScreen.Right = SHORT_MAX;
-    srScreen.Top = csbiex.srWindow.Top;
-    srScreen.Bottom = csbiex.srWindow.Bottom - 1; // srWindow is exclusive, hence the - 1
-    // Clip to the DECSTBM margin boundaries
-    if (_scrollMargins.Top < _scrollMargins.Bottom)
-    {
-        srScreen.Top = csbiex.srWindow.Top + _scrollMargins.Top;
-        srScreen.Bottom = csbiex.srWindow.Top + _scrollMargins.Bottom;
-    }
-
-    // Paste coordinate for cut text above
-    COORD coordDestination;
-    coordDestination.X = srScreen.Left;
-    coordDestination.Y = srScreen.Top + dist * (scrollDirection == ScrollDirection::Up ? -1 : 1);
-
-    // Note the revealed lines are filled with the standard erase attributes.
-    _pConApi->ScrollRegion(srScreen, srScreen, coordDestination, true);
+void AdaptDispatch::_ScrollMovement(const int32_t delta)
+{
+    const auto viewport = _pConApi->GetViewport();
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto bufferWidth = textBuffer.GetSize().Width();
+    const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
+    _ScrollRectVertically(textBuffer, { 0, topMargin, bufferWidth, bottomMargin + 1 }, delta);
 }
 
 // Routine Description:
@@ -933,7 +930,7 @@ void AdaptDispatch::_ScrollMovement(const ScrollDirection scrollDirection, const
 // - True.
 bool AdaptDispatch::ScrollUp(const size_t uiDistance)
 {
-    _ScrollMovement(ScrollDirection::Up, uiDistance);
+    _ScrollMovement(-gsl::narrow_cast<int32_t>(uiDistance));
     return true;
 }
 
@@ -945,7 +942,7 @@ bool AdaptDispatch::ScrollUp(const size_t uiDistance)
 // - True.
 bool AdaptDispatch::ScrollDown(const size_t uiDistance)
 {
-    _ScrollMovement(ScrollDirection::Down, uiDistance);
+    _ScrollMovement(gsl::narrow_cast<int32_t>(uiDistance));
     return true;
 }
 
@@ -959,15 +956,9 @@ bool AdaptDispatch::ScrollDown(const size_t uiDistance)
 // - True.
 bool AdaptDispatch::SetColumns(const size_t columns)
 {
-    SHORT col;
-    THROW_IF_FAILED(SizeTToShort(columns, &col));
-
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-    csbiex.dwSize.X = col;
-    _pConApi->SetConsoleScreenBufferInfoEx(csbiex);
-
+    const auto viewport = _pConApi->GetViewport();
+    const auto viewportHeight = viewport.bottom - viewport.top;
+    _pConApi->ResizeWindow(columns, viewportHeight);
     return true;
 }
 
@@ -992,6 +983,51 @@ bool AdaptDispatch::_DoDECCOLMHelper(const size_t columns)
     return true;
 }
 
+// Routine Description :
+// - Retrieves the various StateMachine parser modes.
+// Arguments:
+// - mode - the parser mode to query.
+// Return Value:
+// - true if the mode is enabled. false if disabled.
+bool AdaptDispatch::_GetParserMode(const StateMachine::Mode mode) const
+{
+    return _pConApi->GetStateMachine().GetParserMode(mode);
+}
+
+// Routine Description:
+// - Sets the various StateMachine parser modes.
+// Arguments:
+// - mode - the parser mode to change.
+// - enable - set to true to enable the mode, false to disable it.
+// Return Value:
+// - <none>
+void AdaptDispatch::_SetParserMode(const StateMachine::Mode mode, const bool enable)
+{
+    _pConApi->GetStateMachine().SetParserMode(mode, enable);
+}
+
+// Routine Description:
+// - Sets the various terminal input modes.
+// Arguments:
+// - mode - the input mode to change.
+// - enable - set to true to enable the mode, false to disable it.
+// Return Value:
+// - true if successful. false otherwise.
+bool AdaptDispatch::_SetInputMode(const TerminalInput::Mode mode, const bool enable)
+{
+    _terminalInput.SetInputMode(mode, enable);
+
+    // If we're a conpty, AND WE'RE IN VT INPUT MODE, always pass input mode requests
+    // The VT Input mode check is to work around ssh.exe v7.7, which uses VT
+    // output, but not Input.
+    // The original comment said, "Once the conpty supports these types of input,
+    // this check can be removed. See GH#4911". Unfortunately, time has shown
+    // us that SSH 7.7 _also_ requests mouse input and that can have a user interface
+    // impact on the actual connected terminal. We can't remove this check,
+    // because SSH <=7.7 is out in the wild on all versions of Windows <=2004.
+    return !(_pConApi->IsConsolePty() && _pConApi->IsVtInputEnabled());
+}
+
 // Routine Description:
 // - Support routine for routing private mode parameters to be set/reset as flags
 // Arguments:
@@ -1094,7 +1130,7 @@ bool AdaptDispatch::ResetMode(const DispatchTypes::ModeParams param)
 // - True if handled successfully. False otherwise.
 bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::Keypad, fApplicationMode);
+    return _SetInputMode(TerminalInput::Mode::Keypad, fApplicationMode);
 }
 
 // Method Description:
@@ -1106,7 +1142,7 @@ bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode)
 // - True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableWin32InputMode(const bool win32InputMode)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::Win32, win32InputMode);
+    return _SetInputMode(TerminalInput::Mode::Win32, win32InputMode);
 }
 
 // - DECCKM - Sets the cursor keys input mode to either Application mode or Normal mode (true, false respectively)
@@ -1116,7 +1152,7 @@ bool AdaptDispatch::EnableWin32InputMode(const bool win32InputMode)
 // - True if handled successfully. False otherwise.
 bool AdaptDispatch::SetCursorKeysMode(const bool applicationMode)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::CursorKey, applicationMode);
+    return _SetInputMode(TerminalInput::Mode::CursorKey, applicationMode);
 }
 
 // - att610 - Enables or disables the cursor blinking.
@@ -1126,7 +1162,47 @@ bool AdaptDispatch::SetCursorKeysMode(const bool applicationMode)
 // - True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableCursorBlinking(const bool enable)
 {
-    return _pConApi->EnableCursorBlinking(enable);
+    auto& cursor = _pConApi->GetTextBuffer().GetCursor();
+    cursor.SetBlinkingAllowed(enable);
+
+    // GH#2642 - From what we've gathered from other terminals, when blinking is
+    // disabled, the cursor should remain On always, and have the visibility
+    // controlled by the IsVisible property. So when you do a printf "\e[?12l"
+    // to disable blinking, the cursor stays stuck On. At this point, only the
+    // cursor visibility property controls whether the user can see it or not.
+    // (Yes, the cursor can be On and NOT Visible)
+    cursor.SetIsOn(true);
+
+    return !_pConApi->IsConsolePty();
+}
+
+// Routine Description:
+// - Internal logic for adding or removing lines in the active screen buffer.
+//   This also moves the cursor to the left margin, which is expected behavior for IL and DL.
+// Parameters:
+// - delta - Number of lines to modify (positive if inserting, negative if deleting).
+// Return Value:
+// - <none>
+void AdaptDispatch::_InsertDeleteLineHelper(const int32_t delta)
+{
+    const auto viewport = _pConApi->GetViewport();
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto bufferWidth = textBuffer.GetSize().Width();
+
+    auto& cursor = textBuffer.GetCursor();
+    const auto row = cursor.GetPosition().Y;
+
+    const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
+    if (row >= topMargin && row <= bottomMargin)
+    {
+        // We emulate inserting and deleting by scrolling the area between the cursor and the bottom margin.
+        _ScrollRectVertically(textBuffer, { 0, row, bufferWidth, bottomMargin + 1 }, delta);
+
+        // The IL and DL controls are also expected to move the cursor to the left margin.
+        // For now this is just column 0, since we don't yet support DECSLRM.
+        cursor.SetXPosition(0);
+        _ApplyCursorMovementFlags(cursor);
+    }
 }
 
 // Routine Description:
@@ -1139,7 +1215,7 @@ bool AdaptDispatch::EnableCursorBlinking(const bool enable)
 // - True.
 bool AdaptDispatch::InsertLine(const size_t distance)
 {
-    _pConApi->InsertLines(distance);
+    _InsertDeleteLineHelper(gsl::narrow_cast<int32_t>(distance));
     return true;
 }
 
@@ -1157,7 +1233,7 @@ bool AdaptDispatch::InsertLine(const size_t distance)
 // - True.
 bool AdaptDispatch::DeleteLine(const size_t distance)
 {
-    _pConApi->DeleteLines(distance);
+    _InsertDeleteLineHelper(-gsl::narrow_cast<int32_t>(distance));
     return true;
 }
 
@@ -1172,10 +1248,10 @@ bool AdaptDispatch::SetAnsiMode(const bool ansiMode)
     // need to be reset to defaults, even if the mode doesn't actually change.
     _termOutput = {};
 
-    _pConApi->SetParserMode(StateMachine::Mode::Ansi, ansiMode);
-    _pConApi->SetInputMode(TerminalInput::Mode::Ansi, ansiMode);
+    _SetParserMode(StateMachine::Mode::Ansi, ansiMode);
+    _SetInputMode(TerminalInput::Mode::Ansi, ansiMode);
 
-    // We don't check the SetInputMode return value, because we'll never want
+    // We don't check the _SetInputMode return value, because we'll never want
     // to forward a DECANM mode change over conpty.
     return true;
 }
@@ -1194,8 +1270,8 @@ bool AdaptDispatch::SetScreenMode(const bool reverseMode)
     {
         return false;
     }
-
-    _pConApi->SetRenderMode(RenderSettings::Mode::ScreenReversed, reverseMode);
+    _renderSettings.SetRenderMode(RenderSettings::Mode::ScreenReversed, reverseMode);
+    _renderer.TriggerRedrawAll();
     return true;
 }
 
@@ -1240,13 +1316,6 @@ bool AdaptDispatch::SetAutoWrapMode(const bool wrapAtEOL)
 void AdaptDispatch::_DoSetTopBottomScrollingMargins(const size_t topMargin,
                                                     const size_t bottomMargin)
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
     // so notes time: (input -> state machine out -> adapter out -> conhost internal)
     // having only a top param is legal         ([3;r   -> 3,0   -> 3,h  -> 3,h,true)
     // having only a bottom param is legal      ([;3r   -> 0,3   -> 1,3  -> 1,3,true)
@@ -1257,7 +1326,8 @@ void AdaptDispatch::_DoSetTopBottomScrollingMargins(const size_t topMargin,
     THROW_IF_FAILED(SizeTToShort(topMargin, &actualTop));
     THROW_IF_FAILED(SizeTToShort(bottomMargin, &actualBottom));
 
-    const SHORT screenHeight = csbiex.srWindow.Bottom - csbiex.srWindow.Top;
+    const auto viewport = _pConApi->GetViewport();
+    const auto screenHeight = gsl::narrow_cast<short>(viewport.bottom - viewport.top);
     // The default top margin is line 1
     if (actualTop == 0)
     {
@@ -1372,7 +1442,26 @@ bool AdaptDispatch::LineFeed(const DispatchTypes::LineFeedType lineFeedType)
 // - True.
 bool AdaptDispatch::ReverseLineFeed()
 {
-    _pConApi->ReverseLineFeed();
+    const auto viewport = _pConApi->GetViewport();
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    auto& cursor = textBuffer.GetCursor();
+    const auto cursorPosition = cursor.GetPosition();
+    const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
+
+    // If the cursor is at the top of the margin area, we shift the buffer
+    // contents down, to emulate inserting a line at that point.
+    if (cursorPosition.Y == topMargin)
+    {
+        const auto bufferWidth = textBuffer.GetSize().Width();
+        _ScrollRectVertically(textBuffer, { 0, topMargin, bufferWidth, bottomMargin + 1 }, 1);
+    }
+    else if (cursorPosition.Y > viewport.top)
+    {
+        // Otherwise we move the cursor up, but not past the top of the viewport.
+        const COORD newCursorPosition{ cursorPosition.X, cursorPosition.Y - 1 };
+        cursor.SetPosition(textBuffer.ClampPositionWithinLine(newCursorPosition));
+        _ApplyCursorMovementFlags(cursor);
+    }
     return true;
 }
 
@@ -1426,12 +1515,9 @@ bool AdaptDispatch::UseMainScreenBuffer()
 // - True.
 bool AdaptDispatch::HorizontalTabSet()
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    const auto width = csbiex.dwSize.X;
-    const auto column = csbiex.dwCursorPosition.X;
+    const auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto width = textBuffer.GetSize().Dimensions().X;
+    const auto column = textBuffer.GetCursor().GetPosition().X;
 
     _InitTabStopsForWidth(width);
     _tabStopColumns.at(column) = true;
@@ -1450,13 +1536,10 @@ bool AdaptDispatch::HorizontalTabSet()
 // - True.
 bool AdaptDispatch::ForwardTab(const size_t numTabs)
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    const auto width = csbiex.dwSize.X;
-    const auto row = csbiex.dwCursorPosition.Y;
-    auto column = csbiex.dwCursorPosition.X;
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    auto& cursor = textBuffer.GetCursor();
+    const auto width = textBuffer.GetLineWidth(cursor.GetPosition().Y);
+    auto column = cursor.GetPosition().X;
     auto tabsPerformed = 0u;
 
     _InitTabStopsForWidth(width);
@@ -1469,7 +1552,8 @@ bool AdaptDispatch::ForwardTab(const size_t numTabs)
         }
     }
 
-    _pConApi->SetCursorPosition({ column, row });
+    cursor.SetXPosition(column);
+    _ApplyCursorMovementFlags(cursor);
     return true;
 }
 
@@ -1482,13 +1566,10 @@ bool AdaptDispatch::ForwardTab(const size_t numTabs)
 // - True.
 bool AdaptDispatch::BackwardsTab(const size_t numTabs)
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    const auto width = csbiex.dwSize.X;
-    const auto row = csbiex.dwCursorPosition.Y;
-    auto column = csbiex.dwCursorPosition.X;
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    auto& cursor = textBuffer.GetCursor();
+    const auto width = textBuffer.GetLineWidth(cursor.GetPosition().Y);
+    auto column = cursor.GetPosition().X;
     auto tabsPerformed = 0u;
 
     _InitTabStopsForWidth(width);
@@ -1501,7 +1582,8 @@ bool AdaptDispatch::BackwardsTab(const size_t numTabs)
         }
     }
 
-    _pConApi->SetCursorPosition({ column, row });
+    cursor.SetXPosition(column);
+    _ApplyCursorMovementFlags(cursor);
     return true;
 }
 
@@ -1536,12 +1618,9 @@ bool AdaptDispatch::TabClear(const DispatchTypes::TabClearType clearType)
 // - <none>
 void AdaptDispatch::_ClearSingleTabStop()
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    const auto width = csbiex.dwSize.X;
-    const auto column = csbiex.dwCursorPosition.X;
+    const auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto width = textBuffer.GetSize().Dimensions().X;
+    const auto column = textBuffer.GetCursor().GetPosition().X;
 
     _InitTabStopsForWidth(width);
     _tabStopColumns.at(column) = false;
@@ -1707,7 +1786,7 @@ bool AdaptDispatch::SingleShift(const size_t gsetNumber)
 // - True.
 bool AdaptDispatch::AcceptC1Controls(const bool enabled)
 {
-    _pConApi->SetParserMode(StateMachine::Mode::AcceptC1, enabled);
+    _SetParserMode(StateMachine::Mode::AcceptC1, enabled);
     return true;
 }
 
@@ -1826,7 +1905,7 @@ bool AdaptDispatch::HardReset()
     _ResetTabStops();
 
     // Clear the soft font in the renderer and delete the font buffer.
-    _pConApi->UpdateSoftFont({}, {}, false);
+    _renderer.UpdateSoftFont({}, {}, false);
     _fontBuffer = nullptr;
 
     // GH#2715 - If all this succeeded, but we're in a conpty, return `false` to
@@ -1846,21 +1925,16 @@ bool AdaptDispatch::HardReset()
 // - True.
 bool AdaptDispatch::ScreenAlignmentPattern()
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
+    const auto viewport = _pConApi->GetViewport();
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto bufferWidth = textBuffer.GetSize().Dimensions().X;
 
     // Fill the screen with the letter E using the default attributes.
-    auto fillPosition = COORD{ 0, csbiex.srWindow.Top };
-    const auto fillLength = (csbiex.srWindow.Bottom - csbiex.srWindow.Top) * csbiex.dwSize.X;
-    _pConApi->FillRegion(fillPosition, fillLength, L'E', false);
+    _FillRect(textBuffer, { 0, viewport.top, bufferWidth, viewport.bottom }, L'E', {});
     // Reset the line rendition for all of these rows.
-    _pConApi->ResetLineRenditionRange(csbiex.srWindow.Top, csbiex.srWindow.Bottom);
+    textBuffer.ResetLineRenditionRange(viewport.top, viewport.bottom);
     // Reset the meta/extended attributes (but leave the colors unchanged).
-    TextAttribute attr = _pConApi->GetTextAttributes();
+    auto attr = textBuffer.GetCurrentAttributes();
     attr.SetStandardErase();
     _pConApi->SetTextAttributes(attr);
     // Reset the origin mode to absolute.
@@ -1887,65 +1961,74 @@ bool AdaptDispatch::ScreenAlignmentPattern()
 // - <none>
 void AdaptDispatch::_EraseScrollback()
 {
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(csbiex);
-    // Make sure to reset the viewport (with MoveToBottom )to where it was
-    //      before the user scrolled the console output
-    _pConApi->MoveToBottom();
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    const SMALL_RECT screen = csbiex.srWindow;
-    const SHORT height = screen.Bottom - screen.Top;
-    THROW_HR_IF(E_UNEXPECTED, height <= 0);
-    const COORD cursor = csbiex.dwCursorPosition;
-
-    // Rectangle to cut out of the existing buffer
-    // It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width.
-    SMALL_RECT scroll = screen;
-    scroll.Left = 0;
-    scroll.Right = SHORT_MAX;
-    // Paste coordinate for cut text above
-    COORD destination;
-    destination.X = 0;
-    destination.Y = 0;
-
-    // Typically a scroll operation should fill with standard erase attributes, but in
-    // this case we need to use the default attributes, hence standardFillAttrs is false.
-    _pConApi->ScrollRegion(scroll, std::nullopt, destination, false);
+    const auto viewport = _pConApi->GetViewport();
+    const auto top = gsl::narrow_cast<short>(viewport.top);
+    const auto height = gsl::narrow_cast<short>(viewport.bottom - viewport.top);
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto bufferSize = textBuffer.GetSize().Dimensions();
+    auto& cursor = textBuffer.GetCursor();
+    const auto row = cursor.GetPosition().Y;
+
+    // Scroll the viewport content to the top of the buffer.
+    textBuffer.ScrollRows(top, height, -top);
     // Clear everything after the viewport.
-    const DWORD totalAreaBelow = csbiex.dwSize.X * (csbiex.dwSize.Y - height);
-    const COORD coordBelowStartPosition = { 0, height };
-    // Again we need to use the default attributes, hence standardFillAttrs is false.
-    _pConApi->FillRegion(coordBelowStartPosition, totalAreaBelow, L' ', false);
+    _FillRect(textBuffer, { 0, height, bufferSize.X, bufferSize.Y }, L' ', {});
     // Also reset the line rendition for all of the cleared rows.
-    _pConApi->ResetLineRenditionRange(height, csbiex.dwSize.Y);
-    // Move the viewport (CAN'T be done in one call with SetConsolescreenBufferInfoEx, because legacy)
-    SMALL_RECT newViewport;
-    newViewport.Left = screen.Left;
-    newViewport.Top = 0;
-    // SetWindowInfo uses an inclusive rect, while GetConsolescreenBufferInfo is exclusive
-    newViewport.Right = screen.Right - 1;
-    newViewport.Bottom = height - 1;
-    _pConApi->SetWindowInfo(true, newViewport);
+    textBuffer.ResetLineRenditionRange(height, bufferSize.Y);
+    // Move the viewport
+    _pConApi->SetViewportPosition({ viewport.left, 0 });
     // Move the cursor to the same relative location.
-    const COORD newcursor = { cursor.X, cursor.Y - screen.Top };
-    _pConApi->SetCursorPosition(newcursor);
+    cursor.SetYPosition(row - top);
+    cursor.SetHasMoved(true);
 }
 
 //Routine Description:
-// Erase All (^[[2J - ED)
-//  Erase the current contents of the viewport. In most terminals, because they
-//      only have a scrollback (and not a buffer per-se), they implement this
-//      by scrolling the current contents of the buffer off of the screen.
-//  We can't properly replicate this behavior with only the public API, because
-//      we need to know where the last character in the buffer is. (it may be below the viewport)
-//Arguments:
-// <none>
+// - Erase All (^[[2J - ED)
+//   Performs a VT Erase All operation. In most terminals, this is done by
+//   moving the viewport into the scrollback, clearing out the current screen.
+//   For them, there can never be any characters beneath the viewport, as the
+//   viewport is always at the bottom. So, we can accomplish the same behavior
+//   by using the LastNonspaceCharacter as the "bottom", and placing the new
+//   viewport underneath that character.
+// Arguments:
+// - <none>
 // Return value:
 // - <none>
 void AdaptDispatch::_EraseAll()
 {
-    _pConApi->EraseAll();
+    const auto viewport = _pConApi->GetViewport();
+    const auto viewportHeight = gsl::narrow_cast<short>(viewport.bottom - viewport.top);
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    const auto bufferSize = textBuffer.GetSize();
+
+    // Stash away the current position of the cursor within the viewport.
+    // We'll need to restore the cursor to that same relative position, after
+    //      we move the viewport.
+    auto& cursor = textBuffer.GetCursor();
+    const auto row = cursor.GetPosition().Y - viewport.top;
+
+    // Calculate new viewport position
+    short newViewportTop = textBuffer.GetLastNonSpaceCharacter().Y + 1;
+    const short newViewportBottom = newViewportTop + viewportHeight;
+    const auto delta = newViewportBottom - (bufferSize.Height());
+    for (auto i = 0; i < delta; i++)
+    {
+        textBuffer.IncrementCircularBuffer();
+        newViewportTop--;
+    }
+    // Move the viewport
+    _pConApi->SetViewportPosition({ viewport.left, newViewportTop });
+    // Restore the relative cursor position
+    cursor.SetYPosition(row + newViewportTop);
+    cursor.SetHasMoved(true);
+
+    // Erase all the rows in the current viewport.
+    auto eraseAttributes = textBuffer.GetCurrentAttributes();
+    eraseAttributes.SetStandardErase();
+    _FillRect(textBuffer, { 0, newViewportTop, bufferSize.Width(), newViewportBottom }, L' ', eraseAttributes);
+
+    // Also reset the line rendition for the erased rows.
+    textBuffer.ResetLineRenditionRange(newViewportTop, newViewportBottom);
 }
 
 // Routine Description:
@@ -1968,7 +2051,7 @@ bool AdaptDispatch::EnableDECCOLMSupport(const bool enabled) noexcept
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableVT200MouseMode(const bool enabled)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::DefaultMouseTracking, enabled);
+    return _SetInputMode(TerminalInput::Mode::DefaultMouseTracking, enabled);
 }
 
 //Routine Description:
@@ -1980,7 +2063,7 @@ bool AdaptDispatch::EnableVT200MouseMode(const bool enabled)
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableUTF8ExtendedMouseMode(const bool enabled)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::Utf8MouseEncoding, enabled);
+    return _SetInputMode(TerminalInput::Mode::Utf8MouseEncoding, enabled);
 }
 
 //Routine Description:
@@ -1992,7 +2075,7 @@ bool AdaptDispatch::EnableUTF8ExtendedMouseMode(const bool enabled)
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableSGRExtendedMouseMode(const bool enabled)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::SgrMouseEncoding, enabled);
+    return _SetInputMode(TerminalInput::Mode::SgrMouseEncoding, enabled);
 }
 
 //Routine Description:
@@ -2003,7 +2086,7 @@ bool AdaptDispatch::EnableSGRExtendedMouseMode(const bool enabled)
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableButtonEventMouseMode(const bool enabled)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, enabled);
+    return _SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, enabled);
 }
 
 //Routine Description:
@@ -2015,7 +2098,7 @@ bool AdaptDispatch::EnableButtonEventMouseMode(const bool enabled)
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableAnyEventMouseMode(const bool enabled)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, enabled);
+    return _SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, enabled);
 }
 
 //Routine Description:
@@ -2027,7 +2110,7 @@ bool AdaptDispatch::EnableAnyEventMouseMode(const bool enabled)
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableAlternateScroll(const bool enabled)
 {
-    return _pConApi->SetInputMode(TerminalInput::Mode::AlternateScroll, enabled);
+    return _SetInputMode(TerminalInput::Mode::AlternateScroll, enabled);
 }
 
 //Routine Description:
@@ -2091,8 +2174,10 @@ bool AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
         return false;
     }
 
-    _pConApi->SetCursorStyle(actualType);
-    _pConApi->EnableCursorBlinking(fEnableBlinking);
+    auto& cursor = _pConApi->GetTextBuffer().GetCursor();
+    cursor.SetType(actualType);
+    cursor.SetBlinkingAllowed(fEnableBlinking);
+    cursor.SetIsOn(true);
 
     // If we're a conpty, always return false, so that this cursor state will be
     // sent to the connected terminal
@@ -2108,7 +2193,7 @@ bool AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::SetCursorColor(const COLORREF cursorColor)
 {
-    return _pConApi->SetColorTableEntry(TextColor::CURSOR_COLOR, cursorColor);
+    return SetColorTableEntry(TextColor::CURSOR_COLOR, cursorColor);
 }
 
 // Routine Description:
@@ -2131,7 +2216,21 @@ bool AdaptDispatch::SetClipboard(const std::wstring_view /*content*/) noexcept
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwColor)
 {
-    return _pConApi->SetColorTableEntry(tableIndex, dwColor);
+    _renderSettings.SetColorTableEntry(tableIndex, dwColor);
+
+    // If we're a conpty, always return false, so that we send the updated color
+    //      value to the terminal. Still handle the sequence so apps that use
+    //      the API or VT to query the values of the color table still read the
+    //      correct color.
+    if (_pConApi->IsConsolePty())
+    {
+        return false;
+    }
+
+    // Update the screen colors if we're not a pty
+    // No need to force a redraw in pty mode.
+    _renderer.TriggerRedrawAll();
+    return true;
 }
 
 // Method Description:
@@ -2142,8 +2241,8 @@ bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwCo
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::SetDefaultForeground(const DWORD dwColor)
 {
-    _pConApi->SetColorAliasIndex(ColorAlias::DefaultForeground, TextColor::DEFAULT_FOREGROUND);
-    return _pConApi->SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, dwColor);
+    _renderSettings.SetColorAliasIndex(ColorAlias::DefaultForeground, TextColor::DEFAULT_FOREGROUND);
+    return SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, dwColor);
 }
 
 // Method Description:
@@ -2154,8 +2253,8 @@ bool AdaptDispatch::SetDefaultForeground(const DWORD dwColor)
 // True if handled successfully. False otherwise.
 bool AdaptDispatch::SetDefaultBackground(const DWORD dwColor)
 {
-    _pConApi->SetColorAliasIndex(ColorAlias::DefaultBackground, TextColor::DEFAULT_BACKGROUND);
-    return _pConApi->SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, dwColor);
+    _renderSettings.SetColorAliasIndex(ColorAlias::DefaultBackground, TextColor::DEFAULT_BACKGROUND);
+    return SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, dwColor);
 }
 
 //Routine Description:
@@ -2180,7 +2279,7 @@ bool AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationTy
     switch (function)
     {
     case DispatchTypes::WindowManipulationType::RefreshWindow:
-        _pConApi->RefreshWindow();
+        _pConApi->GetTextBuffer().TriggerRedrawAll();
         return true;
     case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters:
         _pConApi->ResizeWindow(parameter2.value_or(0), parameter1.value_or(0));
@@ -2198,7 +2297,12 @@ bool AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationTy
 // - true
 bool AdaptDispatch::AddHyperlink(const std::wstring_view uri, const std::wstring_view params)
 {
-    _pConApi->AddHyperlink(uri, params);
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    auto attr = textBuffer.GetCurrentAttributes();
+    const auto id = textBuffer.GetHyperlinkId(uri, params);
+    attr.SetHyperlinkId(id);
+    textBuffer.SetCurrentAttributes(attr);
+    textBuffer.AddHyperlinkToMap(uri, id);
     return true;
 }
 
@@ -2208,7 +2312,10 @@ bool AdaptDispatch::AddHyperlink(const std::wstring_view uri, const std::wstring
 // - true
 bool AdaptDispatch::EndHyperlink()
 {
-    _pConApi->EndHyperlink();
+    auto& textBuffer = _pConApi->GetTextBuffer();
+    auto attr = textBuffer.GetCurrentAttributes();
+    attr.SetHyperlinkId(0);
+    textBuffer.SetCurrentAttributes(attr);
     return true;
 }
 
@@ -2298,7 +2405,7 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const size_t fontNumber
             const auto bitPattern = _fontBuffer->GetBitPattern();
             const auto cellSize = _fontBuffer->GetCellSize();
             const auto centeringHint = _fontBuffer->GetTextCenteringHint();
-            _pConApi->UpdateSoftFont(bitPattern, cellSize.to_win32_size(), centeringHint);
+            _renderer.UpdateSoftFont(bitPattern, cellSize.to_win32_size(), centeringHint);
         }
         return true;
     };
@@ -2365,7 +2472,7 @@ void AdaptDispatch::_ReportSGRSetting() const
     fmt::basic_memory_buffer<wchar_t, 64> response;
     response.append(L"\033P1$r0"sv);
 
-    const auto attr = _pConApi->GetTextAttributes();
+    const auto attr = _pConApi->GetTextBuffer().GetCurrentAttributes();
     // For each boolean attribute that is set, we add the appropriate
     // parameter value to the response string.
     const auto addAttribute = [&](const auto& parameter, const auto enabled) {
@@ -2421,7 +2528,7 @@ void AdaptDispatch::_ReportSGRSetting() const
 // - None
 // Return Value:
 // - None
-void AdaptDispatch::_ReportDECSTBMSetting() const
+void AdaptDispatch::_ReportDECSTBMSetting()
 {
     using namespace std::string_view_literals;
 
@@ -2429,21 +2536,10 @@ void AdaptDispatch::_ReportDECSTBMSetting() const
     fmt::basic_memory_buffer<wchar_t, 64> response;
     response.append(L"\033P1$r"sv);
 
-    CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
-    csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
-    _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
-
-    auto marginTop = _scrollMargins.Top + 1;
-    auto marginBottom = _scrollMargins.Bottom + 1;
-    // If the margin top is greater than or equal to the bottom, then the
-    // margins aren't actually set, so we need to return the full height
-    // of the window for the margin range.
-    if (marginTop >= marginBottom)
-    {
-        marginTop = 1;
-        marginBottom = csbiex.srWindow.Bottom - csbiex.srWindow.Top;
-    }
-    fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"{};{}"), marginTop, marginBottom);
+    const auto viewport = _pConApi->GetViewport();
+    const auto [marginTop, marginBottom] = _GetVerticalMargins(viewport, false);
+    // VT origin is at 1,1 so we need to add 1 to these margins.
+    fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"{};{}"), marginTop + 1, marginBottom + 1);
 
     // The 'r' indicates this is an DECSTBM response, and ST ends the sequence.
     response.append(L"r\033\\"sv);
diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp
index 928a4dd2acb..89bc2eb3647 100644
--- a/src/terminal/adapter/adaptDispatch.hpp
+++ b/src/terminal/adapter/adaptDispatch.hpp
@@ -18,14 +18,18 @@ Author(s):
 #include "conGetSet.hpp"
 #include "FontBuffer.hpp"
 #include "terminalOutput.hpp"
-#include "..\..\types\inc\sgrStack.hpp"
+#include "../input/terminalInput.hpp"
+#include "../../types/inc/sgrStack.hpp"
 
 namespace Microsoft::Console::VirtualTerminal
 {
     class AdaptDispatch : public ITermDispatch
     {
+        using Renderer = Microsoft::Console::Render::Renderer;
+        using RenderSettings = Microsoft::Console::Render::RenderSettings;
+
     public:
-        AdaptDispatch(std::unique_ptr<ConGetSet> pConApi);
+        AdaptDispatch(std::unique_ptr<ConGetSet> pConApi, Renderer& renderer, RenderSettings& renderSettings, TerminalInput& terminalInput);
 
         void Print(const wchar_t wchPrintable) override;
         void PrintString(const std::wstring_view string) override;
@@ -162,21 +166,27 @@ namespace Microsoft::Console::VirtualTerminal
             static constexpr Offset Unchanged() { return Forward(0); };
         };
 
-        bool _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins) const;
-        void _EraseSingleLineHelper(const CONSOLE_SCREEN_BUFFER_INFOEX& csbiex,
-                                    const DispatchTypes::EraseType eraseType,
-                                    const size_t lineId) const;
+        std::pair<int, int> _GetVerticalMargins(const til::rect& viewport, const bool absolute);
+        bool _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins);
+        void _ApplyCursorMovementFlags(Cursor& cursor) noexcept;
+        void _FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const wchar_t fillChar, const TextAttribute fillAttrs);
         void _EraseScrollback();
         void _EraseAll();
-        void _InsertDeleteHelper(const size_t count, const bool isInsert) const;
-        void _ScrollMovement(const ScrollDirection dir, const size_t distance) const;
+        void _ScrollRectVertically(TextBuffer& textBuffer, const til::rect& scrollRect, const int32_t delta);
+        void _ScrollRectHorizontally(TextBuffer& textBuffer, const til::rect& scrollRect, const int32_t delta);
+        void _InsertDeleteCharacterHelper(const int32_t delta);
+        void _InsertDeleteLineHelper(const int32_t delta);
+        void _ScrollMovement(const int32_t delta);
 
         void _DoSetTopBottomScrollingMargins(const size_t topMargin,
                                              const size_t bottomMargin);
         void _OperatingStatus() const;
-        void _CursorPositionReport() const;
+        void _CursorPositionReport();
 
         void _WriteResponse(const std::wstring_view reply) const;
+        bool _GetParserMode(const StateMachine::Mode mode) const;
+        void _SetParserMode(const StateMachine::Mode mode, const bool enable);
+        bool _SetInputMode(const TerminalInput::Mode mode, const bool enable);
         bool _ModeParamsHelper(const DispatchTypes::ModeParams param, const bool enable);
         bool _DoDECCOLMHelper(const size_t columns);
 
@@ -186,12 +196,15 @@ namespace Microsoft::Console::VirtualTerminal
         void _InitTabStopsForWidth(const size_t width);
 
         void _ReportSGRSetting() const;
-        void _ReportDECSTBMSetting() const;
+        void _ReportDECSTBMSetting();
 
         std::vector<bool> _tabStopColumns;
         bool _initDefaultTabStops = true;
 
         std::unique_ptr<ConGetSet> _pConApi;
+        Renderer& _renderer;
+        RenderSettings& _renderSettings;
+        TerminalInput& _terminalInput;
         TerminalOutput _termOutput;
         std::unique_ptr<FontBuffer> _fontBuffer;
         std::optional<unsigned int> _initialCodePage;
diff --git a/src/terminal/adapter/adaptDispatchGraphics.cpp b/src/terminal/adapter/adaptDispatchGraphics.cpp
index e955291d94a..0a03c1524e6 100644
--- a/src/terminal/adapter/adaptDispatchGraphics.cpp
+++ b/src/terminal/adapter/adaptDispatchGraphics.cpp
@@ -75,7 +75,7 @@ size_t AdaptDispatch::_SetRgbColorsHelper(const VTParameters options,
 // - True.
 bool AdaptDispatch::SetGraphicsRendition(const VTParameters options)
 {
-    TextAttribute attr = _pConApi->GetTextAttributes();
+    TextAttribute attr = _pConApi->GetTextBuffer().GetCurrentAttributes();
 
     // Run through the graphics options and apply them
     for (size_t i = 0; i < options.size(); i++)
@@ -270,7 +270,7 @@ bool AdaptDispatch::SetGraphicsRendition(const VTParameters options)
 // - True.
 bool AdaptDispatch::PushGraphicsRendition(const VTParameters options)
 {
-    const auto currentAttributes = _pConApi->GetTextAttributes();
+    const auto currentAttributes = _pConApi->GetTextBuffer().GetCurrentAttributes();
     _sgrStack.Push(currentAttributes, options);
     return true;
 }
@@ -284,7 +284,7 @@ bool AdaptDispatch::PushGraphicsRendition(const VTParameters options)
 // - True.
 bool AdaptDispatch::PopGraphicsRendition()
 {
-    const auto currentAttributes = _pConApi->GetTextAttributes();
+    const auto currentAttributes = _pConApi->GetTextBuffer().GetCurrentAttributes();
     _pConApi->SetTextAttributes(_sgrStack.Pop(currentAttributes));
     return true;
 }
diff --git a/src/terminal/adapter/conGetSet.hpp b/src/terminal/adapter/conGetSet.hpp
index 6dd127c2825..8c62e932c93 100644
--- a/src/terminal/adapter/conGetSet.hpp
+++ b/src/terminal/adapter/conGetSet.hpp
@@ -15,11 +15,10 @@ Author(s):
 
 #pragma once
 
-#include "../input/terminalInput.hpp"
 #include "../parser/stateMachine.hpp"
 #include "../../types/inc/IInputEvent.hpp"
 #include "../../buffer/out/LineRendition.hpp"
-#include "../../buffer/out/TextAttribute.hpp"
+#include "../../buffer/out/textBuffer.hpp"
 #include "../../renderer/inc/RenderSettings.hpp"
 #include "../../inc/conattrs.hpp"
 
@@ -37,80 +36,36 @@ namespace Microsoft::Console::VirtualTerminal
 
         virtual void PrintString(const std::wstring_view string) = 0;
 
-        virtual void GetConsoleScreenBufferInfoEx(CONSOLE_SCREEN_BUFFER_INFOEX& screenBufferInfo) const = 0;
-        virtual void SetConsoleScreenBufferInfoEx(const CONSOLE_SCREEN_BUFFER_INFOEX& screenBufferInfo) = 0;
-        virtual void SetCursorPosition(const COORD position) = 0;
+        virtual StateMachine& GetStateMachine() = 0;
+        virtual TextBuffer& GetTextBuffer() = 0;
+        virtual til::rect GetViewport() const = 0;
+        virtual void SetViewportPosition(const til::point position) = 0;
 
         virtual bool IsVtInputEnabled() const = 0;
 
-        virtual TextAttribute GetTextAttributes() const = 0;
         virtual void SetTextAttributes(const TextAttribute& attrs) = 0;
 
-        virtual void SetCurrentLineRendition(const LineRendition lineRendition) = 0;
-        virtual void ResetLineRenditionRange(const size_t startRow, const size_t endRow) = 0;
-        virtual SHORT GetLineWidth(const size_t row) const = 0;
-
         virtual void WriteInput(std::deque<std::unique_ptr<IInputEvent>>& events, size_t& eventsWritten) = 0;
-        virtual void SetWindowInfo(const bool absolute, const SMALL_RECT& window) = 0;
-
-        virtual bool SetInputMode(const TerminalInput::Mode mode, const bool enabled) = 0;
-        virtual void SetParserMode(const StateMachine::Mode mode, const bool enabled) = 0;
-        virtual bool GetParserMode(const StateMachine::Mode mode) const = 0;
-        virtual void SetRenderMode(const RenderSettings::Mode mode, const bool enabled) = 0;
 
         virtual void SetAutoWrapMode(const bool wrapAtEOL) = 0;
 
-        virtual void SetCursorVisibility(const bool visible) = 0;
-        virtual bool EnableCursorBlinking(const bool enable) = 0;
-
         virtual void SetScrollingRegion(const SMALL_RECT& scrollMargins) = 0;
         virtual void WarningBell() = 0;
         virtual bool GetLineFeedMode() const = 0;
         virtual void LineFeed(const bool withReturn) = 0;
-        virtual void ReverseLineFeed() = 0;
         virtual void SetWindowTitle(const std::wstring_view title) = 0;
         virtual void UseAlternateScreenBuffer() = 0;
         virtual void UseMainScreenBuffer() = 0;
 
-        virtual void EraseAll() = 0;
-        virtual void ClearBuffer() = 0;
         virtual CursorType GetUserDefaultCursorStyle() const = 0;
-        virtual void SetCursorStyle(const CursorType style) = 0;
-        virtual void WriteControlInput(const KeyEvent key) = 0;
-        virtual void RefreshWindow() = 0;
 
         virtual void SetConsoleOutputCP(const unsigned int codepage) = 0;
         virtual unsigned int GetConsoleOutputCP() const = 0;
 
         virtual bool ResizeWindow(const size_t width, const size_t height) = 0;
-        virtual void SuppressResizeRepaint() = 0;
         virtual bool IsConsolePty() const = 0;
 
-        virtual void DeleteLines(const size_t count) = 0;
-        virtual void InsertLines(const size_t count) = 0;
-
-        virtual void MoveToBottom() = 0;
-
-        virtual COLORREF GetColorTableEntry(const size_t tableIndex) const = 0;
-        virtual bool SetColorTableEntry(const size_t tableIndex, const COLORREF color) = 0;
-        virtual void SetColorAliasIndex(const ColorAlias alias, const size_t tableIndex) = 0;
-
-        virtual void FillRegion(const COORD startPosition,
-                                const size_t fillLength,
-                                const wchar_t fillChar,
-                                const bool standardFillAttrs) = 0;
-
-        virtual void ScrollRegion(const SMALL_RECT scrollRect,
-                                  const std::optional<SMALL_RECT> clipRect,
-                                  const COORD destinationOrigin,
-                                  const bool standardFillAttrs) = 0;
-
-        virtual void AddHyperlink(const std::wstring_view uri, const std::wstring_view params) const = 0;
-        virtual void EndHyperlink() const = 0;
-
-        virtual void UpdateSoftFont(const gsl::span<const uint16_t> bitPattern,
-                                    const SIZE cellSize,
-                                    const size_t centeringHint) = 0;
+        virtual void NotifyAccessibilityChange(const til::rect& changedRect) = 0;
 
         virtual void ReparentWindow(const uint64_t handle) = 0;
     };
diff --git a/src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj b/src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj
index 8ee87c5ba50..2d4b31d28c0 100644
--- a/src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj
+++ b/src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj
@@ -28,6 +28,9 @@
     <ProjectReference Include="..\..\..\interactivity\base\lib\InteractivityBase.vcxproj">
       <Project>{06ec74cb-9a12-429c-b551-8562ec964846}</Project>
     </ProjectReference>
+    <ProjectReference Include="..\..\..\renderer\base\lib\base.vcxproj">
+      <Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
+    </ProjectReference>
     <ProjectReference Include="..\..\..\types\lib\types.vcxproj">
       <Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
     </ProjectReference>
diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp
index 5fb239682a4..1483429e25b 100644
--- a/src/terminal/adapter/ut_adapter/adapterTest.cpp
+++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp
@@ -4,6 +4,8 @@
 #include "precomp.h"
 #include <wextestclass.h>
 #include "../../inc/consoletaeftemplates.hpp"
+#include "../../parser/OutputStateMachineEngine.hpp"
+#include "../../../renderer/inc/DummyRenderer.hpp"
 
 #include "adaptDispatch.hpp"
 
@@ -62,79 +64,24 @@ class TestGetSet final : public ConGetSet
     {
     }
 
-    void GetConsoleScreenBufferInfoEx(CONSOLE_SCREEN_BUFFER_INFOEX& sbiex) const override
+    StateMachine& GetStateMachine() override
     {
-        Log::Comment(L"GetConsoleScreenBufferInfoEx MOCK returning data...");
-
-        THROW_HR_IF(E_FAIL, !_getConsoleScreenBufferInfoExResult);
-        sbiex.dwSize = _bufferSize;
-        sbiex.srWindow = _viewport;
-        sbiex.dwCursorPosition = _cursorPos;
-        sbiex.wAttributes = _attribute.GetLegacyAttributes();
-    }
-    void SetConsoleScreenBufferInfoEx(const CONSOLE_SCREEN_BUFFER_INFOEX& sbiex) override
-    {
-        Log::Comment(L"SetConsoleScreenBufferInfoEx MOCK returning data...");
-
-        THROW_HR_IF(E_FAIL, !_setConsoleScreenBufferInfoExResult);
-        VERIFY_ARE_EQUAL(_expectedCursorPos, sbiex.dwCursorPosition);
-        VERIFY_ARE_EQUAL(_expectedScreenBufferSize, sbiex.dwSize);
-        VERIFY_ARE_EQUAL(_expectedScreenBufferViewport, sbiex.srWindow);
-        VERIFY_ARE_EQUAL(_expectedAttribute, TextAttribute{ sbiex.wAttributes });
-    }
-    void SetCursorPosition(const COORD position) override
-    {
-        Log::Comment(L"SetCursorPosition MOCK called...");
-
-        THROW_HR_IF(E_FAIL, !_setCursorPositionResult);
-        VERIFY_ARE_EQUAL(_expectedCursorPos, position);
-        _cursorPos = position;
-    }
-
-    void SetWindowInfo(const bool absolute, const SMALL_RECT& window) override
-    {
-        Log::Comment(L"SetWindowInfo MOCK called...");
-
-        THROW_HR_IF(E_FAIL, !_setWindowInfoResult);
-        VERIFY_ARE_EQUAL(_expectedWindowAbsolute, absolute);
-        VERIFY_ARE_EQUAL(_expectedConsoleWindow, window);
-        _viewport = window;
-    }
-
-    bool SetInputMode(const TerminalInput::Mode mode, const bool enabled) override
-    {
-        Log::Comment(L"SetInputMode MOCK called...");
-
-        if (_setInputModeResult)
-        {
-            VERIFY_ARE_EQUAL(_expectedInputMode, mode);
-            VERIFY_ARE_EQUAL(_expectedInputModeEnabled, enabled);
-        }
-
-        return _setInputModeResult;
+        return *_stateMachine;
     }
 
-    void SetParserMode(const StateMachine::Mode mode, const bool enabled) override
+    TextBuffer& GetTextBuffer() override
     {
-        Log::Comment(L"SetParserMode MOCK called...");
-
-        if (_setParserModeResult)
-        {
-            VERIFY_ARE_EQUAL(_expectedParserMode, mode);
-            VERIFY_ARE_EQUAL(_expectedParserModeEnabled, enabled);
-        }
+        return *_textBuffer.get();
     }
 
-    bool GetParserMode(const StateMachine::Mode /*mode*/) const override
+    til::rect GetViewport() const override
     {
-        Log::Comment(L"GetParserMode MOCK called...");
-
-        return false;
+        return til::rect{ _viewport.Left, _viewport.Top, _viewport.Right, _viewport.Bottom };
     }
 
-    void SetRenderMode(const RenderSettings::Mode /*mode*/, const bool /*enabled*/) override
+    void SetViewportPosition(const til::point /*position*/) override
     {
-        Log::Comment(L"SetRenderMode MOCK called...");
+        Log::Comment(L"SetViewportPosition MOCK called...");
     }
 
     void SetAutoWrapMode(const bool /*wrapAtEOL*/) override
@@ -142,63 +89,18 @@ class TestGetSet final : public ConGetSet
         Log::Comment(L"SetAutoWrapMode MOCK called...");
     }
 
-    void SetCursorVisibility(const bool visible) override
-    {
-        Log::Comment(L"SetCursorVisibility MOCK called...");
-
-        THROW_HR_IF(E_FAIL, !_setCursorVisibilityResult);
-        VERIFY_ARE_EQUAL(_expectedCursorVisibility, visible);
-    }
-
-    bool EnableCursorBlinking(const bool enable) override
-    {
-        Log::Comment(L"EnableCursorBlinking MOCK called...");
-
-        if (_enableCursorBlinkingResult)
-        {
-            VERIFY_ARE_EQUAL(_enable, enable);
-        }
-
-        return _enableCursorBlinkingResult;
-    }
-
     bool IsVtInputEnabled() const override
     {
         return false;
     }
 
-    TextAttribute GetTextAttributes() const
-    {
-        Log::Comment(L"GetTextAttributes MOCK called...");
-
-        THROW_HR_IF(E_FAIL, !_getTextAttributesResult);
-        return _attribute;
-    }
-
     void SetTextAttributes(const TextAttribute& attrs)
     {
         Log::Comment(L"SetTextAttributes MOCK called...");
 
         THROW_HR_IF(E_FAIL, !_setTextAttributesResult);
         VERIFY_ARE_EQUAL(_expectedAttribute, attrs);
-        _attribute = attrs;
-    }
-
-    void SetCurrentLineRendition(const LineRendition /*lineRendition*/)
-    {
-        Log::Comment(L"SetCurrentLineRendition MOCK called...");
-    }
-
-    void ResetLineRenditionRange(const size_t /*startRow*/, const size_t /*endRow*/)
-    {
-        Log::Comment(L"ResetLineRenditionRange MOCK called...");
-    }
-
-    SHORT GetLineWidth(const size_t /*row*/) const
-    {
-        Log::Comment(L"GetLineWidth MOCK called...");
-
-        return _bufferSize.X;
+        _textBuffer->SetCurrentAttributes(attrs);
     }
 
     void WriteInput(std::deque<std::unique_ptr<IInputEvent>>& events, size_t& eventsWritten) override
@@ -222,16 +124,6 @@ class TestGetSet final : public ConGetSet
         eventsWritten = _events.size();
     }
 
-    void WriteControlInput(_In_ KeyEvent key) override
-    {
-        Log::Comment(L"WriteControlInput MOCK called...");
-
-        THROW_HR_IF(E_FAIL, !_writeControlInputResult);
-        VERIFY_ARE_EQUAL('C', key.GetVirtualKeyCode());
-        VERIFY_ARE_EQUAL(0x3, key.GetCharData());
-        VERIFY_ARE_EQUAL(true, key.IsCtrlPressed());
-    }
-
     void SetScrollingRegion(const SMALL_RECT& scrollMargins) override
     {
         Log::Comment(L"SetScrollingRegion MOCK called...");
@@ -262,11 +154,6 @@ class TestGetSet final : public ConGetSet
         VERIFY_ARE_EQUAL(_expectedLineFeedWithReturn, withReturn);
     }
 
-    void ReverseLineFeed() override
-    {
-        Log::Comment(L"ReverseLineFeed MOCK called...");
-    }
-
     void SetWindowTitle(const std::wstring_view title)
     {
         Log::Comment(L"SetWindowTitle MOCK called...");
@@ -289,46 +176,17 @@ class TestGetSet final : public ConGetSet
         Log::Comment(L"UseMainScreenBuffer MOCK called...");
     }
 
-    void EraseAll() override
-    {
-        Log::Comment(L"EraseAll MOCK called...");
-    }
-
-    void ClearBuffer() override
-    {
-        Log::Comment(L"ClearBuffer MOCK called...");
-    }
-
     CursorType GetUserDefaultCursorStyle() const override
     {
         return CursorType::Legacy;
     }
 
-    void SetCursorStyle(const CursorType cursorType) override
-    {
-        Log::Comment(L"SetCursorStyle MOCK called...");
-
-        THROW_HR_IF(E_FAIL, !_setCursorStyleResult);
-        VERIFY_ARE_EQUAL(_expectedCursorStyle, cursorType);
-    }
-
-    void RefreshWindow() override
-    {
-        Log::Comment(L"RefreshWindow MOCK called...");
-    }
-
     bool ResizeWindow(const size_t /*width*/, const size_t /*height*/) override
     {
         Log::Comment(L"ResizeWindow MOCK called...");
         return true;
     }
 
-    void SuppressResizeRepaint() override
-    {
-        Log::Comment(L"SuppressResizeRepaint MOCK called...");
-        VERIFY_IS_TRUE(false, L"AdaptDispatch should never be calling this function.");
-    }
-
     void SetConsoleOutputCP(const unsigned int codepage) override
     {
         Log::Comment(L"SetConsoleOutputCP MOCK called...");
@@ -348,78 +206,9 @@ class TestGetSet final : public ConGetSet
         return _isPty;
     }
 
-    void DeleteLines(const size_t /*count*/) override
+    void NotifyAccessibilityChange(const til::rect& /*changedRect*/) override
     {
-        Log::Comment(L"DeleteLines MOCK called...");
-    }
-
-    void InsertLines(const size_t /*count*/) override
-    {
-        Log::Comment(L"InsertLines MOCK called...");
-    }
-
-    void MoveToBottom() override
-    {
-        Log::Comment(L"MoveToBottom MOCK called...");
-    }
-
-    COLORREF GetColorTableEntry(const size_t tableIndex) const noexcept override
-    {
-        Log::Comment(L"GetColorTableEntry MOCK called...");
-
-        if (_getColorTableEntryResult)
-        {
-            VERIFY_ARE_EQUAL(_expectedColorTableIndex, tableIndex);
-            // Simply returning the index as the color value makes it easy for
-            // tests to confirm that they've received the color they expected.
-            return gsl::narrow_cast<COLORREF>(tableIndex);
-        }
-
-        return INVALID_COLOR;
-    }
-
-    bool SetColorTableEntry(const size_t tableIndex, const COLORREF color) noexcept override
-    {
-        Log::Comment(L"SetColorTableEntry MOCK called...");
-        if (_setColorTableEntryResult)
-        {
-            VERIFY_ARE_EQUAL(_expectedColorTableIndex, tableIndex);
-            VERIFY_ARE_EQUAL(_expectedColorValue, color);
-        }
-
-        return _setColorTableEntryResult;
-    }
-
-    void SetColorAliasIndex(const ColorAlias /*alias*/, const size_t /*tableIndex*/) noexcept override
-    {
-        Log::Comment(L"SetColorAliasIndex MOCK called...");
-    }
-
-    void FillRegion(const COORD /*startPosition*/,
-                    const size_t /*fillLength*/,
-                    const wchar_t /*fillChar*/,
-                    const bool /*standardFillAttrs*/) noexcept override
-    {
-        Log::Comment(L"FillRegion MOCK called...");
-    }
-
-    void ScrollRegion(const SMALL_RECT /*scrollRect*/,
-                      const std::optional<SMALL_RECT> /*clipRect*/,
-                      const COORD /*destinationOrigin*/,
-                      const bool /*standardFillAttrs*/) noexcept override
-    {
-        Log::Comment(L"ScrollRegion MOCK called...");
-    }
-
-    void UpdateSoftFont(const gsl::span<const uint16_t> /*bitPattern*/,
-                        const SIZE cellSize,
-                        const size_t /*centeringHint*/) noexcept override
-    {
-        Log::Comment(L"UpdateSoftFont MOCK called...");
-
-        Log::Comment(NoThrowString().Format(L"Cell size: %dx%d", cellSize.cx, cellSize.cy));
-        VERIFY_ARE_EQUAL(_expectedCellSize.cx, cellSize.cx);
-        VERIFY_ARE_EQUAL(_expectedCellSize.cy, cellSize.cy);
+        Log::Comment(L"NotifyAccessibilityChange MOCK called...");
     }
 
     void ReparentWindow(const uint64_t /*handle*/)
@@ -456,17 +245,10 @@ class TestGetSet final : public ConGetSet
         Log::Comment(L"Resetting mock data state.");
 
         // APIs succeed by default
-        _setCursorPositionResult = TRUE;
-        _getConsoleScreenBufferInfoExResult = TRUE;
-        _getTextAttributesResult = TRUE;
         _setTextAttributesResult = TRUE;
         _writeInputResult = TRUE;
-        _writeControlInputResult = TRUE;
-        _setWindowInfoResult = TRUE;
-        _moveToBottomResult = true;
 
-        _bufferSize.X = 100;
-        _bufferSize.Y = 600;
+        _textBuffer = std::make_unique<TextBuffer>(COORD{ 100, 600 }, TextAttribute{}, 0, false, _renderer);
 
         // Viewport sitting in the "middle" of the buffer somewhere (so all sides have excess buffer around them)
         _viewport.Top = 20;
@@ -477,11 +259,9 @@ class TestGetSet final : public ConGetSet
         // Call cursor positions separately
         PrepCursor(xact, yact);
 
-        _cursorVisible = TRUE;
-
         // Attribute default is gray on black.
-        _attribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
-        _expectedAttribute = _attribute;
+        _textBuffer->SetCurrentAttributes(TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED });
+        _expectedAttribute = _textBuffer->GetCurrentAttributes();
 
         _events.clear();
         _retainInput = false;
@@ -491,19 +271,22 @@ class TestGetSet final : public ConGetSet
     {
         Log::Comment(L"Adjusting cursor within viewport... Expected will match actual when done.");
 
+        COORD cursorPos = {};
+        const COORD bufferSize = _textBuffer->GetSize().Dimensions();
+
         switch (xact)
         {
         case CursorX::LEFT:
             Log::Comment(L"Cursor set to left edge of buffer.");
-            _cursorPos.X = 0;
+            cursorPos.X = 0;
             break;
         case CursorX::RIGHT:
             Log::Comment(L"Cursor set to right edge of buffer.");
-            _cursorPos.X = _bufferSize.X - 1;
+            cursorPos.X = bufferSize.X - 1;
             break;
         case CursorX::XCENTER:
             Log::Comment(L"Cursor set to centered X of buffer.");
-            _cursorPos.X = _bufferSize.X / 2;
+            cursorPos.X = bufferSize.X / 2;
             break;
         }
 
@@ -511,19 +294,25 @@ class TestGetSet final : public ConGetSet
         {
         case CursorY::TOP:
             Log::Comment(L"Cursor set to top edge of viewport.");
-            _cursorPos.Y = _viewport.Top;
+            cursorPos.Y = _viewport.Top;
             break;
         case CursorY::BOTTOM:
             Log::Comment(L"Cursor set to bottom edge of viewport.");
-            _cursorPos.Y = _viewport.Bottom - 1;
+            cursorPos.Y = _viewport.Bottom - 1;
             break;
         case CursorY::YCENTER:
             Log::Comment(L"Cursor set to centered Y of viewport.");
-            _cursorPos.Y = _viewport.Top + ((_viewport.Bottom - _viewport.Top) / 2);
+            cursorPos.Y = _viewport.Top + ((_viewport.Bottom - _viewport.Top) / 2);
             break;
         }
 
-        _expectedCursorPos = _cursorPos;
+        _textBuffer->GetCursor().SetPosition(cursorPos);
+        _expectedCursorPos = cursorPos;
+    }
+
+    void ValidateExpectedCursorPos()
+    {
+        VERIFY_ARE_EQUAL(_expectedCursorPos, _textBuffer->GetCursor().GetPosition());
     }
 
     void ValidateInputEvent(_In_ PCWSTR pwszExpectedResponse)
@@ -552,16 +341,6 @@ class TestGetSet final : public ConGetSet
         }
     }
 
-    void AddHyperlink(const std::wstring_view /*uri*/, const std::wstring_view /*params*/) const
-    {
-        Log::Comment(L"AddHyperlink MOCK called...");
-    }
-
-    void EndHyperlink() const
-    {
-        Log::Comment(L"EndHyperlink MOCK called...");
-    }
-
     void _SetMarginsHelper(SMALL_RECT* rect, SHORT top, SHORT bottom)
     {
         rect->Top = top;
@@ -593,66 +372,31 @@ class TestGetSet final : public ConGetSet
         });
     }
 
-    COORD _bufferSize = { 0, 0 };
+    StateMachine* _stateMachine;
+    DummyRenderer _renderer;
+    std::unique_ptr<TextBuffer> _textBuffer;
     SMALL_RECT _viewport = { 0, 0, 0, 0 };
-    SMALL_RECT _expectedConsoleWindow = { 0, 0, 0, 0 };
-    COORD _cursorPos = { 0, 0 };
     SMALL_RECT _expectedScrollRegion = { 0, 0, 0, 0 };
     SMALL_RECT _activeScrollRegion = { 0, 0, 0, 0 };
 
-    bool _cursorVisible = false;
-
     COORD _expectedCursorPos = { 0, 0 };
 
-    TextAttribute _attribute = {};
     TextAttribute _expectedAttribute = {};
     unsigned int _expectedOutputCP = 0;
     bool _isPty = false;
 
-    bool _setCursorVisibilityResult = false;
-    bool _expectedCursorVisibility = false;
-
-    bool _getConsoleScreenBufferInfoExResult = false;
-    bool _setCursorPositionResult = false;
-    bool _getTextAttributesResult = false;
     bool _setTextAttributesResult = false;
     bool _writeInputResult = false;
-    bool _writeControlInputResult = false;
-
-    bool _setWindowInfoResult = false;
-    bool _expectedWindowAbsolute = false;
-    bool _setConsoleScreenBufferInfoExResult = false;
-
-    COORD _expectedScreenBufferSize = { 0, 0 };
-    SMALL_RECT _expectedScreenBufferViewport{ 0, 0, 0, 0 };
-    bool _setInputModeResult = false;
-    TerminalInput::Mode _expectedInputMode;
-    bool _expectedInputModeEnabled = false;
-    bool _setParserModeResult = false;
-    StateMachine::Mode _expectedParserMode;
-    bool _expectedParserModeEnabled = false;
-    bool _enableCursorBlinkingResult = false;
-    bool _enable = false; // for cursor blinking
+
     bool _setScrollingRegionResult = false;
     bool _getLineFeedModeResult = false;
     bool _lineFeedResult = false;
     bool _expectedLineFeedWithReturn = false;
-    bool _reverseLineFeedResult = false;
 
     bool _setWindowTitleResult = false;
     std::wstring_view _expectedWindowTitle{};
-    bool _setCursorStyleResult = false;
-    CursorType _expectedCursorStyle;
     bool _setConsoleOutputCPResult = false;
     bool _getConsoleOutputCPResult = false;
-    bool _moveToBottomResult = false;
-
-    bool _getColorTableEntryResult = false;
-    bool _setColorTableEntryResult = false;
-    size_t _expectedColorTableIndex = SIZE_MAX;
-    COLORREF _expectedColorValue = INVALID_COLOR;
-
-    SIZE _expectedCellSize = {};
 
 private:
     HANDLE _hCon;
@@ -673,15 +417,27 @@ class AdapterTest
         {
             // give AdaptDispatch ownership of _testGetSet
             _testGetSet = api.get(); // keep a copy for us but don't manage its lifetime anymore.
-            _pDispatch = std::make_unique<AdaptDispatch>(std::move(api));
-            fSuccess = _pDispatch != nullptr;
+            _terminalInput = TerminalInput{ nullptr };
+            auto& renderer = _testGetSet->_renderer;
+            auto& renderSettings = renderer._renderSettings;
+            auto adapter = std::make_unique<AdaptDispatch>(std::move(api), renderer, renderSettings, _terminalInput);
+
+            fSuccess = adapter.get() != nullptr;
+            if (fSuccess)
+            {
+                _pDispatch = adapter.get();
+                auto engine = std::make_unique<OutputStateMachineEngine>(std::move(adapter));
+                _stateMachine = std::make_unique<StateMachine>(std::move(engine));
+                _testGetSet->_stateMachine = _stateMachine.get();
+            }
         }
         return fSuccess;
     }
 
     TEST_METHOD_CLEANUP(CleanupMethods)
     {
-        _pDispatch.reset();
+        _stateMachine.reset();
+        _pDispatch = nullptr;
         _testGetSet = nullptr;
         return true;
     }
@@ -743,7 +499,8 @@ class AdapterTest
         Log::Comment(L"Test 1: Cursor doesn't move when placed in corner of viewport.");
         _testGetSet->PrepData(direction);
 
-        VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
+        VERIFY_IS_TRUE((_pDispatch->*(moveFunc))(1));
+        _testGetSet->ValidateExpectedCursorPos();
 
         Log::Comment(L"Test 1b: Cursor moves to left of line with next/prev line command when cursor can't move higher/lower.");
 
@@ -764,7 +521,8 @@ class AdapterTest
         if (fDoTest1b)
         {
             _testGetSet->_expectedCursorPos.X = 0;
-            VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
+            VERIFY_IS_TRUE((_pDispatch->*(moveFunc))(1));
+            _testGetSet->ValidateExpectedCursorPos();
         }
         else
         {
@@ -799,7 +557,8 @@ class AdapterTest
             break;
         }
 
-        VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
+        VERIFY_IS_TRUE((_pDispatch->*(moveFunc))(1));
+        _testGetSet->ValidateExpectedCursorPos();
 
         // place cursor and move it up too far. It should get bounded by the viewport.
         Log::Comment(L"Test 3: Cursor moves and gets stuck at viewport when started away from edges and moved beyond edges.");
@@ -816,7 +575,7 @@ class AdapterTest
             _testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
             break;
         case CursorDirection::RIGHT:
-            _testGetSet->_expectedCursorPos.X = _testGetSet->_bufferSize.X - 1;
+            _testGetSet->_expectedCursorPos.X = _testGetSet->_textBuffer->GetSize().Dimensions().X - 1;
             break;
         case CursorDirection::LEFT:
             _testGetSet->_expectedCursorPos.X = 0;
@@ -831,23 +590,8 @@ class AdapterTest
             break;
         }
 
-        VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(100));
-
-        // error cases
-        // SetCursorPosition throws failure. Parameters are otherwise normal.
-        Log::Comment(L"Test 4: When SetCursorPosition throws a failure, call fails and cursor doesn't move.");
-        _testGetSet->PrepData(direction);
-        _testGetSet->_setCursorPositionResult = FALSE;
-
-        VERIFY_THROWS((_pDispatch.get()->*(moveFunc))(0), std::exception);
-        VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
-
-        // GetConsoleScreenBufferInfo throws failure. Parameters are otherwise normal.
-        Log::Comment(L"Test 5: When GetConsoleScreenBufferInfo throws a failure, call fails and cursor doesn't move.");
-        _testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
-        _testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
-        VERIFY_THROWS((_pDispatch.get()->*(moveFunc))(0), std::exception);
-        VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
+        VERIFY_IS_TRUE((_pDispatch->*(moveFunc))(100));
+        _testGetSet->ValidateExpectedCursorPos();
     }
 
     TEST_METHOD(CursorPositionTest)
@@ -864,7 +608,8 @@ class AdapterTest
         _testGetSet->_expectedCursorPos.X = sCol - 1;
         _testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top + (sRow - 1);
 
-        VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
+        VERIFY_IS_TRUE(_pDispatch->CursorPosition(sRow, sCol));
+        _testGetSet->ValidateExpectedCursorPos();
 
         Log::Comment(L"Test 2: Move to 0, 0 (which is 1,1 in VT speak)");
         _testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
@@ -873,32 +618,20 @@ class AdapterTest
         _testGetSet->_expectedCursorPos.X = 0;
         _testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
 
-        VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(1, 1));
+        VERIFY_IS_TRUE(_pDispatch->CursorPosition(1, 1));
+        _testGetSet->ValidateExpectedCursorPos();
 
         Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
         _testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
 
-        sCol = (_testGetSet->_bufferSize.X) * 2;
+        sCol = (_testGetSet->_textBuffer->GetSize().Dimensions().X) * 2;
         sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) * 2;
 
-        _testGetSet->_expectedCursorPos.X = _testGetSet->_bufferSize.X - 1;
+        _testGetSet->_expectedCursorPos.X = _testGetSet->_textBuffer->GetSize().Dimensions().X - 1;
         _testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
 
-        VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
-
-        Log::Comment(L"Test 4: GetConsoleInfo API returns false. No move, return false.");
-        _testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
-
-        _testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
-
-        VERIFY_THROWS(_pDispatch.get()->CursorPosition(1, 1), std::exception);
-
-        Log::Comment(L"Test 5: SetCursor API returns false. No move, return false.");
-        _testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
-
-        _testGetSet->_setCursorPositionResult = FALSE;
-
-        VERIFY_THROWS(_pDispatch.get()->CursorPosition(1, 1), std::exception);
+        VERIFY_IS_TRUE(_pDispatch->CursorPosition(sRow, sCol));
+        _testGetSet->ValidateExpectedCursorPos();
     }
 
     TEST_METHOD(CursorSingleDimensionMoveTest)
@@ -927,7 +660,7 @@ class AdapterTest
         {
         case AbsolutePosition::CursorHorizontal:
             Log::Comment(L"Testing cursor horizontal movement.");
-            sRangeEnd = _testGetSet->_bufferSize.X;
+            sRangeEnd = _testGetSet->_textBuffer->GetSize().Dimensions().X;
             sRangeStart = 0;
             psCursorExpected = &_testGetSet->_expectedCursorPos.X;
             moveFunc = &AdaptDispatch::CursorHorizontalPositionAbsolute;
@@ -954,7 +687,8 @@ class AdapterTest
 
         *psCursorExpected = sRangeStart + (sVal - 1);
 
-        VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
+        VERIFY_IS_TRUE((_pDispatch->*(moveFunc))(sVal));
+        _testGetSet->ValidateExpectedCursorPos();
 
         Log::Comment(L"Test 2: Move to 0 (which is 1 in VT speak)");
         _testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
@@ -962,7 +696,8 @@ class AdapterTest
         *psCursorExpected = sRangeStart;
         sVal = 1;
 
-        VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
+        VERIFY_IS_TRUE((_pDispatch->*(moveFunc))(sVal));
+        _testGetSet->ValidateExpectedCursorPos();
 
         Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
         _testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
@@ -971,25 +706,8 @@ class AdapterTest
 
         *psCursorExpected = sRangeEnd - 1;
 
-        VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
-
-        Log::Comment(L"Test 4: GetConsoleInfo API returns false. No move, return false.");
-        _testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
-
-        _testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
-
-        sVal = 1;
-
-        VERIFY_THROWS((_pDispatch.get()->*(moveFunc))(sVal), std::exception);
-
-        Log::Comment(L"Test 5: SetCursor API returns false. No move, return false.");
-        _testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
-
-        _testGetSet->_setCursorPositionResult = FALSE;
-
-        sVal = 1;
-
-        VERIFY_THROWS((_pDispatch.get()->*(moveFunc))(sVal), std::exception);
+        VERIFY_IS_TRUE((_pDispatch->*(moveFunc))(sVal));
+        _testGetSet->ValidateExpectedCursorPos();
     }
 
     TEST_METHOD(CursorSaveRestoreTest)
@@ -1011,11 +729,13 @@ class AdapterTest
         // Attributes are restored to defaults.
         _testGetSet->_expectedAttribute = {};
 
-        VERIFY_IS_TRUE(_pDispatch.get()->CursorRestoreState(), L"By default, restore to top left corner (0,0 offset from viewport).");
+        VERIFY_IS_TRUE(_pDispatch->CursorRestoreState(), L"By default, restore to top left corner (0,0 offset from viewport).");
+        _testGetSet->ValidateExpectedCursorPos();
 
         Log::Comment(L"Test 2: Place cursor in center. Save. Move cursor to corner. Restore. Should come back to center.");
         _testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
-        VERIFY_IS_TRUE(_pDispatch.get()->CursorSaveState(), L"Succeed at saving position.");
+        VERIFY_IS_TRUE(_pDispatch->CursorSaveState(), L"Succeed at saving position.");
+        _testGetSet->ValidateExpectedCursorPos();
 
         Log::Comment(L"Backup expected cursor (in the middle). Move cursor to corner. Then re-set expected cursor to middle.");
         // save expected cursor position
@@ -1027,7 +747,8 @@ class AdapterTest
         // restore expected cursor position to center.
         _testGetSet->_expectedCursorPos = coordExpected;
 
-        VERIFY_IS_TRUE(_pDispatch.get()->CursorRestoreState(), L"Restoring to corner should succeed. API call inside will test that cursor matched expected position.");
+        VERIFY_IS_TRUE(_pDispatch->CursorRestoreState(), L"Restoring to corner should succeed. API call inside will test that cursor matched expected position.");
+        _testGetSet->ValidateExpectedCursorPos();
     }
 
     TEST_METHOD(CursorHideShowTest)
@@ -1045,17 +766,11 @@ class AdapterTest
         VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"fStartingVis", fStart));
         VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"fEndingVis", fEnd));
 
-        Log::Comment(L"Test 1: Verify successful API call modifies visibility state.");
-        _testGetSet->PrepData();
-        _testGetSet->_cursorVisible = fStart;
-        _testGetSet->_setCursorVisibilityResult = true;
-        _testGetSet->_expectedCursorVisibility = fEnd;
-        VERIFY_IS_TRUE(_pDispatch.get()->CursorVisibility(fEnd));
-
-        Log::Comment(L"Test 3: When we fail to set updated cursor information, the dispatch should fail.");
+        Log::Comment(L"Verify successful API call modifies visibility state.");
         _testGetSet->PrepData();
-        _testGetSet->_setCursorVisibilityResult = false;
-        VERIFY_THROWS(_pDispatch.get()->CursorVisibility(fEnd), std::exception);
+        _testGetSet->_textBuffer->GetCursor().SetIsVisible(fStart);
+        VERIFY_IS_TRUE(_pDispatch->CursorVisibility(fEnd));
+        VERIFY_ARE_EQUAL(fEnd, _testGetSet->_textBuffer->GetCursor().IsVisible());
     }
 
     TEST_METHOD(GraphicsBaseTests)
@@ -1069,23 +784,16 @@ class AdapterTest
         VTParameter rgOptions[16];
         size_t cOptions = 0;
 
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-
-        Log::Comment(L"Test 2: Gracefully fail when getting attribute data fails.");
-
-        _testGetSet->PrepData();
-        _testGetSet->_getTextAttributesResult = FALSE;
-
-        VERIFY_THROWS(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }), std::exception);
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
 
-        Log::Comment(L"Test 3: Gracefully fail when setting attribute data fails.");
+        Log::Comment(L"Test 2: Gracefully fail when setting attribute data fails.");
 
         _testGetSet->PrepData();
         _testGetSet->_setTextAttributesResult = FALSE;
         // Need at least one option in order for the call to be able to fail.
         rgOptions[0] = (DispatchTypes::GraphicsOptions)0;
         cOptions = 1;
-        VERIFY_THROWS(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }), std::exception);
+        VERIFY_THROWS(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }), std::exception);
     }
 
     TEST_METHOD(GraphicsSingleTests)
@@ -1107,299 +815,300 @@ class AdapterTest
         size_t cOptions = 1;
         rgOptions[0] = graphicsOption;
 
+        TextAttribute startingAttribute;
         switch (graphicsOption)
         {
         case DispatchTypes::GraphicsOptions::Off:
             Log::Comment(L"Testing graphics 'Off/Reset'");
-            _testGetSet->_attribute = TextAttribute{ (WORD)~_testGetSet->s_defaultFill };
+            startingAttribute = TextAttribute{ (WORD)~_testGetSet->s_defaultFill };
             _testGetSet->_expectedAttribute = TextAttribute{};
             break;
         case DispatchTypes::GraphicsOptions::Intense:
             Log::Comment(L"Testing graphics 'Intense'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
+            startingAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute.SetIntense(true);
             break;
         case DispatchTypes::GraphicsOptions::RGBColorOrFaint:
             Log::Comment(L"Testing graphics 'Faint'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
+            startingAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute.SetFaint(true);
             break;
         case DispatchTypes::GraphicsOptions::Underline:
             Log::Comment(L"Testing graphics 'Underline'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
+            startingAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute.SetUnderlined(true);
             break;
         case DispatchTypes::GraphicsOptions::DoublyUnderlined:
             Log::Comment(L"Testing graphics 'Doubly Underlined'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
+            startingAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute.SetDoublyUnderlined(true);
             break;
         case DispatchTypes::GraphicsOptions::Overline:
             Log::Comment(L"Testing graphics 'Overline'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
+            startingAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute = TextAttribute{ COMMON_LVB_GRID_HORIZONTAL };
             break;
         case DispatchTypes::GraphicsOptions::Negative:
             Log::Comment(L"Testing graphics 'Negative'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
+            startingAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute = TextAttribute{ COMMON_LVB_REVERSE_VIDEO };
             break;
         case DispatchTypes::GraphicsOptions::Invisible:
             Log::Comment(L"Testing graphics 'Invisible'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
+            startingAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute.SetInvisible(true);
             break;
         case DispatchTypes::GraphicsOptions::CrossedOut:
             Log::Comment(L"Testing graphics 'Crossed Out'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
+            startingAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             _testGetSet->_expectedAttribute.SetCrossedOut(true);
             break;
         case DispatchTypes::GraphicsOptions::NotIntenseOrFaint:
             Log::Comment(L"Testing graphics 'No Intense or Faint'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
-            _testGetSet->_attribute.SetIntense(true);
-            _testGetSet->_attribute.SetFaint(true);
+            startingAttribute = TextAttribute{ 0 };
+            startingAttribute.SetIntense(true);
+            startingAttribute.SetFaint(true);
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             break;
         case DispatchTypes::GraphicsOptions::NoUnderline:
             Log::Comment(L"Testing graphics 'No Underline'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
-            _testGetSet->_attribute.SetUnderlined(true);
-            _testGetSet->_attribute.SetDoublyUnderlined(true);
+            startingAttribute = TextAttribute{ 0 };
+            startingAttribute.SetUnderlined(true);
+            startingAttribute.SetDoublyUnderlined(true);
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             break;
         case DispatchTypes::GraphicsOptions::NoOverline:
             Log::Comment(L"Testing graphics 'No Overline'");
-            _testGetSet->_attribute = TextAttribute{ COMMON_LVB_GRID_HORIZONTAL };
+            startingAttribute = TextAttribute{ COMMON_LVB_GRID_HORIZONTAL };
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             break;
         case DispatchTypes::GraphicsOptions::Positive:
             Log::Comment(L"Testing graphics 'Positive'");
-            _testGetSet->_attribute = TextAttribute{ COMMON_LVB_REVERSE_VIDEO };
+            startingAttribute = TextAttribute{ COMMON_LVB_REVERSE_VIDEO };
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             break;
         case DispatchTypes::GraphicsOptions::Visible:
             Log::Comment(L"Testing graphics 'Visible'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
-            _testGetSet->_attribute.SetInvisible(true);
+            startingAttribute = TextAttribute{ 0 };
+            startingAttribute.SetInvisible(true);
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             break;
         case DispatchTypes::GraphicsOptions::NotCrossedOut:
             Log::Comment(L"Testing graphics 'Not Crossed Out'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
-            _testGetSet->_attribute.SetCrossedOut(true);
+            startingAttribute = TextAttribute{ 0 };
+            startingAttribute.SetCrossedOut(true);
             _testGetSet->_expectedAttribute = TextAttribute{ 0 };
             break;
         case DispatchTypes::GraphicsOptions::ForegroundBlack:
             Log::Comment(L"Testing graphics 'Foreground Color Black'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLACK);
             break;
         case DispatchTypes::GraphicsOptions::ForegroundBlue:
             Log::Comment(L"Testing graphics 'Foreground Color Blue'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
             break;
         case DispatchTypes::GraphicsOptions::ForegroundGreen:
             Log::Comment(L"Testing graphics 'Foreground Color Green'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
             break;
         case DispatchTypes::GraphicsOptions::ForegroundCyan:
             Log::Comment(L"Testing graphics 'Foreground Color Cyan'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_CYAN);
             break;
         case DispatchTypes::GraphicsOptions::ForegroundRed:
             Log::Comment(L"Testing graphics 'Foreground Color Red'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_RED);
             break;
         case DispatchTypes::GraphicsOptions::ForegroundMagenta:
             Log::Comment(L"Testing graphics 'Foreground Color Magenta'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_GREEN | FOREGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_GREEN | FOREGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_MAGENTA);
             break;
         case DispatchTypes::GraphicsOptions::ForegroundYellow:
             Log::Comment(L"Testing graphics 'Foreground Color Yellow'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_YELLOW);
             break;
         case DispatchTypes::GraphicsOptions::ForegroundWhite:
             Log::Comment(L"Testing graphics 'Foreground Color White'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_WHITE);
             break;
         case DispatchTypes::GraphicsOptions::ForegroundDefault:
             Log::Comment(L"Testing graphics 'Foreground Color Default'");
-            _testGetSet->_attribute = TextAttribute{ (WORD)~_testGetSet->s_wDefaultAttribute }; // set the current attribute to the opposite of default so we can ensure all relevant bits flip.
+            startingAttribute = TextAttribute{ (WORD)~_testGetSet->s_wDefaultAttribute }; // set the current attribute to the opposite of default so we can ensure all relevant bits flip.
             // To get expected value, take what we started with and change ONLY the background series of bits to what the Default says.
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute; // expect = starting
+            _testGetSet->_expectedAttribute = startingAttribute; // expect = starting
             _testGetSet->_expectedAttribute.SetDefaultForeground(); // set the foreground as default
             break;
         case DispatchTypes::GraphicsOptions::BackgroundBlack:
             Log::Comment(L"Testing graphics 'Background Color Black'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_BLACK);
             break;
         case DispatchTypes::GraphicsOptions::BackgroundBlue:
             Log::Comment(L"Testing graphics 'Background Color Blue'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_BLUE);
             break;
         case DispatchTypes::GraphicsOptions::BackgroundGreen:
             Log::Comment(L"Testing graphics 'Background Color Green'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_GREEN);
             break;
         case DispatchTypes::GraphicsOptions::BackgroundCyan:
             Log::Comment(L"Testing graphics 'Background Color Cyan'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_CYAN);
             break;
         case DispatchTypes::GraphicsOptions::BackgroundRed:
             Log::Comment(L"Testing graphics 'Background Color Red'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_RED);
             break;
         case DispatchTypes::GraphicsOptions::BackgroundMagenta:
             Log::Comment(L"Testing graphics 'Background Color Magenta'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_GREEN | BACKGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_GREEN | BACKGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_MAGENTA);
             break;
         case DispatchTypes::GraphicsOptions::BackgroundYellow:
             Log::Comment(L"Testing graphics 'Background Color Yellow'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_YELLOW);
             break;
         case DispatchTypes::GraphicsOptions::BackgroundWhite:
             Log::Comment(L"Testing graphics 'Background Color White'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_INTENSITY };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_INTENSITY };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_WHITE);
             break;
         case DispatchTypes::GraphicsOptions::BackgroundDefault:
             Log::Comment(L"Testing graphics 'Background Color Default'");
-            _testGetSet->_attribute = TextAttribute{ (WORD)~_testGetSet->s_wDefaultAttribute }; // set the current attribute to the opposite of default so we can ensure all relevant bits flip.
+            startingAttribute = TextAttribute{ (WORD)~_testGetSet->s_wDefaultAttribute }; // set the current attribute to the opposite of default so we can ensure all relevant bits flip.
             // To get expected value, take what we started with and change ONLY the background series of bits to what the Default says.
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute; // expect = starting
+            _testGetSet->_expectedAttribute = startingAttribute; // expect = starting
             _testGetSet->_expectedAttribute.SetDefaultBackground(); // set the background as default
             break;
         case DispatchTypes::GraphicsOptions::BrightForegroundBlack:
             Log::Comment(L"Testing graphics 'Bright Foreground Color Black'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_BLACK);
             break;
         case DispatchTypes::GraphicsOptions::BrightForegroundBlue:
             Log::Comment(L"Testing graphics 'Bright Foreground Color Blue'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_BLUE);
             break;
         case DispatchTypes::GraphicsOptions::BrightForegroundGreen:
             Log::Comment(L"Testing graphics 'Bright Foreground Color Green'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_BLUE };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_BLUE };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_GREEN);
             break;
         case DispatchTypes::GraphicsOptions::BrightForegroundCyan:
             Log::Comment(L"Testing graphics 'Bright Foreground Color Cyan'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_RED };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_RED };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_CYAN);
             break;
         case DispatchTypes::GraphicsOptions::BrightForegroundRed:
             Log::Comment(L"Testing graphics 'Bright Foreground Color Red'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_RED);
             break;
         case DispatchTypes::GraphicsOptions::BrightForegroundMagenta:
             Log::Comment(L"Testing graphics 'Bright Foreground Color Magenta'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_GREEN };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_GREEN };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_MAGENTA);
             break;
         case DispatchTypes::GraphicsOptions::BrightForegroundYellow:
             Log::Comment(L"Testing graphics 'Bright Foreground Color Yellow'");
-            _testGetSet->_attribute = TextAttribute{ FOREGROUND_BLUE };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ FOREGROUND_BLUE };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_YELLOW);
             break;
         case DispatchTypes::GraphicsOptions::BrightForegroundWhite:
             Log::Comment(L"Testing graphics 'Bright Foreground Color White'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ 0 };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_WHITE);
             break;
         case DispatchTypes::GraphicsOptions::BrightBackgroundBlack:
             Log::Comment(L"Testing graphics 'Bright Background Color Black'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_BLACK);
             break;
         case DispatchTypes::GraphicsOptions::BrightBackgroundBlue:
             Log::Comment(L"Testing graphics 'Bright Background Color Blue'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_BLUE);
             break;
         case DispatchTypes::GraphicsOptions::BrightBackgroundGreen:
             Log::Comment(L"Testing graphics 'Bright Background Color Green'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_BLUE };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_BLUE };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_GREEN);
             break;
         case DispatchTypes::GraphicsOptions::BrightBackgroundCyan:
             Log::Comment(L"Testing graphics 'Bright Background Color Cyan'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_RED };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_RED };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_CYAN);
             break;
         case DispatchTypes::GraphicsOptions::BrightBackgroundRed:
             Log::Comment(L"Testing graphics 'Bright Background Color Red'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_GREEN };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_GREEN };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_RED);
             break;
         case DispatchTypes::GraphicsOptions::BrightBackgroundMagenta:
             Log::Comment(L"Testing graphics 'Bright Background Color Magenta'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_GREEN };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_GREEN };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_MAGENTA);
             break;
         case DispatchTypes::GraphicsOptions::BrightBackgroundYellow:
             Log::Comment(L"Testing graphics 'Bright Background Color Yellow'");
-            _testGetSet->_attribute = TextAttribute{ BACKGROUND_BLUE };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ BACKGROUND_BLUE };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_YELLOW);
             break;
         case DispatchTypes::GraphicsOptions::BrightBackgroundWhite:
             Log::Comment(L"Testing graphics 'Bright Background Color White'");
-            _testGetSet->_attribute = TextAttribute{ 0 };
-            _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+            startingAttribute = TextAttribute{ 0 };
+            _testGetSet->_expectedAttribute = startingAttribute;
             _testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_WHITE);
             break;
         default:
@@ -1407,7 +1116,8 @@ class AdapterTest
             break;
         }
 
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
+        _testGetSet->_textBuffer->SetCurrentAttributes(startingAttribute);
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
     }
 
     TEST_METHOD(GraphicsPushPopTests)
@@ -1561,82 +1271,82 @@ class AdapterTest
         Log::Comment(L"Resetting graphics options");
         rgOptions[0] = DispatchTypes::GraphicsOptions::Off;
         _testGetSet->_expectedAttribute = {};
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
 
         Log::Comment(L"Testing graphics 'Foreground Color Blue'");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundBlue;
         _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
 
         Log::Comment(L"Enabling brightness");
         rgOptions[0] = DispatchTypes::GraphicsOptions::Intense;
         _testGetSet->_expectedAttribute.SetIntense(true);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_TRUE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Testing graphics 'Foreground Color Green, with brightness'");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundGreen;
         _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_TRUE(WI_IsFlagSet(_testGetSet->_attribute.GetLegacyAttributes(), FOREGROUND_GREEN));
-        VERIFY_IS_TRUE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(WI_IsFlagSet(_testGetSet->_textBuffer->GetCurrentAttributes().GetLegacyAttributes(), FOREGROUND_GREEN));
+        VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Test 2: Disable brightness, use a bright color, next normal call remains not bright");
         Log::Comment(L"Resetting graphics options");
         rgOptions[0] = DispatchTypes::GraphicsOptions::Off;
         _testGetSet->_expectedAttribute = {};
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_TRUE(WI_IsFlagClear(_testGetSet->_attribute.GetLegacyAttributes(), FOREGROUND_INTENSITY));
-        VERIFY_IS_FALSE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(WI_IsFlagClear(_testGetSet->_textBuffer->GetCurrentAttributes().GetLegacyAttributes(), FOREGROUND_INTENSITY));
+        VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Testing graphics 'Foreground Color Bright Blue'");
         rgOptions[0] = DispatchTypes::GraphicsOptions::BrightForegroundBlue;
         _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_BLUE);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_FALSE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Testing graphics 'Foreground Color Blue', brightness of 9x series doesn't persist");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundBlue;
         _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_FALSE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Test 3: Enable brightness, use a bright color, brightness persists to next normal call");
         Log::Comment(L"Resetting graphics options");
         rgOptions[0] = DispatchTypes::GraphicsOptions::Off;
         _testGetSet->_expectedAttribute = {};
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_FALSE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Testing graphics 'Foreground Color Blue'");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundBlue;
         _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_FALSE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Enabling brightness");
         rgOptions[0] = DispatchTypes::GraphicsOptions::Intense;
         _testGetSet->_expectedAttribute.SetIntense(true);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_TRUE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Testing graphics 'Foreground Color Bright Blue'");
         rgOptions[0] = DispatchTypes::GraphicsOptions::BrightForegroundBlue;
         _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_BLUE);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_TRUE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Testing graphics 'Foreground Color Blue, with brightness', brightness of 9x series doesn't affect brightness");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundBlue;
         _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_TRUE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
 
         Log::Comment(L"Testing graphics 'Foreground Color Green, with brightness'");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundGreen;
         _testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
-        VERIFY_IS_TRUE(_testGetSet->_attribute.IsIntense());
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCurrentAttributes().IsIntense());
     }
 
     TEST_METHOD(DeviceStatusReportTests)
@@ -1645,7 +1355,7 @@ class AdapterTest
 
         Log::Comment(L"Test 1: Verify failure when using bad status.");
         _testGetSet->PrepData();
-        VERIFY_IS_FALSE(_pDispatch.get()->DeviceStatusReport((DispatchTypes::AnsiStatusType)-1));
+        VERIFY_IS_FALSE(_pDispatch->DeviceStatusReport((DispatchTypes::AnsiStatusType)-1));
     }
 
     TEST_METHOD(DeviceStatus_OperatingStatusTests)
@@ -1654,7 +1364,7 @@ class AdapterTest
 
         Log::Comment(L"Test 1: Verify good operating condition.");
         _testGetSet->PrepData();
-        VERIFY_IS_TRUE(_pDispatch.get()->DeviceStatusReport(DispatchTypes::AnsiStatusType::OS_OperatingStatus));
+        VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::AnsiStatusType::OS_OperatingStatus));
 
         _testGetSet->ValidateInputEvent(L"\x1b[0n");
     }
@@ -1668,7 +1378,7 @@ class AdapterTest
             _testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
 
             // start with the cursor position in the buffer.
-            COORD coordCursorExpected = _testGetSet->_cursorPos;
+            COORD coordCursorExpected = _testGetSet->_textBuffer->GetCursor().GetPosition();
 
             // to get to VT, we have to adjust it to its position relative to the viewport top.
             coordCursorExpected.Y -= _testGetSet->_viewport.Top;
@@ -1677,7 +1387,7 @@ class AdapterTest
             coordCursorExpected.X++;
             coordCursorExpected.Y++;
 
-            VERIFY_IS_TRUE(_pDispatch.get()->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
+            VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
 
             wchar_t pwszBuffer[50];
 
@@ -1693,7 +1403,7 @@ class AdapterTest
             auto retentionScope{ _testGetSet->EnableInputRetentionInScope() };
 
             // start with the cursor position in the buffer.
-            til::point coordCursorExpectedFirst{ _testGetSet->_cursorPos };
+            til::point coordCursorExpectedFirst{ _testGetSet->_textBuffer->GetCursor().GetPosition() };
 
             // to get to VT, we have to adjust it to its position relative to the viewport top.
             coordCursorExpectedFirst -= til::point{ 0, _testGetSet->_viewport.Top };
@@ -1701,15 +1411,17 @@ class AdapterTest
             // Then note that VT is 1,1 based for the top left, so add 1. (The rest of the console uses 0,0 for array index bases.)
             coordCursorExpectedFirst += til::point{ 1, 1 };
 
-            VERIFY_IS_TRUE(_pDispatch.get()->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
+            VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
 
-            _testGetSet->_cursorPos.X++;
-            _testGetSet->_cursorPos.Y++;
+            auto cursorPos = _testGetSet->_textBuffer->GetCursor().GetPosition();
+            cursorPos.X++;
+            cursorPos.Y++;
+            _testGetSet->_textBuffer->GetCursor().SetPosition(cursorPos);
 
             auto coordCursorExpectedSecond{ coordCursorExpectedFirst };
             coordCursorExpectedSecond += til::point{ 1, 1 };
 
-            VERIFY_IS_TRUE(_pDispatch.get()->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
+            VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
 
             wchar_t pwszBuffer[50];
 
@@ -1724,7 +1436,7 @@ class AdapterTest
 
         Log::Comment(L"Test 1: Verify normal response.");
         _testGetSet->PrepData();
-        VERIFY_IS_TRUE(_pDispatch.get()->DeviceAttributes());
+        VERIFY_IS_TRUE(_pDispatch->DeviceAttributes());
 
         PCWSTR pwszExpectedResponse = L"\x1b[?1;0c";
         _testGetSet->ValidateInputEvent(pwszExpectedResponse);
@@ -1733,7 +1445,7 @@ class AdapterTest
         _testGetSet->PrepData();
         _testGetSet->_writeInputResult = FALSE;
 
-        VERIFY_THROWS(_pDispatch.get()->DeviceAttributes(), std::exception);
+        VERIFY_THROWS(_pDispatch->DeviceAttributes(), std::exception);
     }
 
     TEST_METHOD(SecondaryDeviceAttributesTests)
@@ -1742,7 +1454,7 @@ class AdapterTest
 
         Log::Comment(L"Test 1: Verify normal response.");
         _testGetSet->PrepData();
-        VERIFY_IS_TRUE(_pDispatch.get()->SecondaryDeviceAttributes());
+        VERIFY_IS_TRUE(_pDispatch->SecondaryDeviceAttributes());
 
         PCWSTR pwszExpectedResponse = L"\x1b[>0;10;1c";
         _testGetSet->ValidateInputEvent(pwszExpectedResponse);
@@ -1751,7 +1463,7 @@ class AdapterTest
         _testGetSet->PrepData();
         _testGetSet->_writeInputResult = FALSE;
 
-        VERIFY_THROWS(_pDispatch.get()->SecondaryDeviceAttributes(), std::exception);
+        VERIFY_THROWS(_pDispatch->SecondaryDeviceAttributes(), std::exception);
     }
 
     TEST_METHOD(TertiaryDeviceAttributesTests)
@@ -1760,7 +1472,7 @@ class AdapterTest
 
         Log::Comment(L"Test 1: Verify normal response.");
         _testGetSet->PrepData();
-        VERIFY_IS_TRUE(_pDispatch.get()->TertiaryDeviceAttributes());
+        VERIFY_IS_TRUE(_pDispatch->TertiaryDeviceAttributes());
 
         PCWSTR pwszExpectedResponse = L"\x1bP!|00000000\x1b\\";
         _testGetSet->ValidateInputEvent(pwszExpectedResponse);
@@ -1769,7 +1481,7 @@ class AdapterTest
         _testGetSet->PrepData();
         _testGetSet->_writeInputResult = FALSE;
 
-        VERIFY_THROWS(_pDispatch.get()->TertiaryDeviceAttributes(), std::exception);
+        VERIFY_THROWS(_pDispatch->TertiaryDeviceAttributes(), std::exception);
     }
 
     TEST_METHOD(RequestTerminalParametersTests)
@@ -1778,28 +1490,28 @@ class AdapterTest
 
         Log::Comment(L"Test 1: Verify response for unsolicited permission.");
         _testGetSet->PrepData();
-        VERIFY_IS_TRUE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited));
+        VERIFY_IS_TRUE(_pDispatch->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited));
         _testGetSet->ValidateInputEvent(L"\x1b[2;1;1;128;128;1;0x");
 
         Log::Comment(L"Test 2: Verify response for solicited permission.");
         _testGetSet->PrepData();
-        VERIFY_IS_TRUE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Solicited));
+        VERIFY_IS_TRUE(_pDispatch->RequestTerminalParameters(DispatchTypes::ReportingPermission::Solicited));
         _testGetSet->ValidateInputEvent(L"\x1b[3;1;1;128;128;1;0x");
 
         Log::Comment(L"Test 3: Verify failure with invalid parameter.");
         _testGetSet->PrepData();
-        VERIFY_IS_FALSE(_pDispatch.get()->RequestTerminalParameters((DispatchTypes::ReportingPermission)2));
+        VERIFY_IS_FALSE(_pDispatch->RequestTerminalParameters((DispatchTypes::ReportingPermission)2));
 
         Log::Comment(L"Test 4: Verify failure when WriteInput doesn't work.");
         _testGetSet->PrepData();
         _testGetSet->_writeInputResult = FALSE;
-        VERIFY_THROWS(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited), std::exception);
+        VERIFY_THROWS(_pDispatch->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited), std::exception);
     }
 
     TEST_METHOD(RequestSettingsTests)
     {
         const auto requestSetting = [=](const std::wstring_view settingId = {}) {
-            const auto stringHandler = _pDispatch.get()->RequestSetting();
+            const auto stringHandler = _pDispatch->RequestSetting();
             for (auto ch : settingId)
             {
                 stringHandler(ch);
@@ -1809,7 +1521,7 @@ class AdapterTest
 
         Log::Comment(L"Requesting DECSTBM margins (5 to 10).");
         _testGetSet->PrepData();
-        _pDispatch.get()->SetTopBottomScrollingMargins(5, 10);
+        _pDispatch->SetTopBottomScrollingMargins(5, 10);
         requestSetting(L"r");
         _testGetSet->ValidateInputEvent(L"\033P1$r5;10r\033\\");
 
@@ -1817,79 +1529,88 @@ class AdapterTest
         _testGetSet->PrepData();
         // Set screen height to 25 - this will be the expected margin range.
         _testGetSet->_viewport.Bottom = _testGetSet->_viewport.Top + 25;
-        _pDispatch.get()->SetTopBottomScrollingMargins(0, 0);
+        _pDispatch->SetTopBottomScrollingMargins(0, 0);
         requestSetting(L"r");
         _testGetSet->ValidateInputEvent(L"\033P1$r1;25r\033\\");
 
         Log::Comment(L"Requesting SGR attributes (default).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
+        TextAttribute attribute = {};
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0m\033\\");
 
         Log::Comment(L"Requesting SGR attributes (intense, underlined, reversed).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
-        _testGetSet->_attribute.SetIntense(true);
-        _testGetSet->_attribute.SetUnderlined(true);
-        _testGetSet->_attribute.SetReverseVideo(true);
+        attribute = {};
+        attribute.SetIntense(true);
+        attribute.SetUnderlined(true);
+        attribute.SetReverseVideo(true);
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0;1;4;7m\033\\");
 
         Log::Comment(L"Requesting SGR attributes (faint, blinking, invisible).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
-        _testGetSet->_attribute.SetFaint(true);
-        _testGetSet->_attribute.SetBlinking(true);
-        _testGetSet->_attribute.SetInvisible(true);
+        attribute = {};
+        attribute.SetFaint(true);
+        attribute.SetBlinking(true);
+        attribute.SetInvisible(true);
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0;2;5;8m\033\\");
 
         Log::Comment(L"Requesting SGR attributes (italic, crossed-out).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
-        _testGetSet->_attribute.SetItalic(true);
-        _testGetSet->_attribute.SetCrossedOut(true);
+        attribute = {};
+        attribute.SetItalic(true);
+        attribute.SetCrossedOut(true);
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0;3;9m\033\\");
 
         Log::Comment(L"Requesting SGR attributes (doubly underlined, overlined).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
-        _testGetSet->_attribute.SetDoublyUnderlined(true);
-        _testGetSet->_attribute.SetOverlined(true);
+        attribute = {};
+        attribute.SetDoublyUnderlined(true);
+        attribute.SetOverlined(true);
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0;21;53m\033\\");
 
         Log::Comment(L"Requesting SGR attributes (standard colors).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
-        _testGetSet->_attribute.SetIndexedForeground(TextColor::DARK_YELLOW);
-        _testGetSet->_attribute.SetIndexedBackground(TextColor::DARK_CYAN);
+        attribute = {};
+        attribute.SetIndexedForeground(TextColor::DARK_YELLOW);
+        attribute.SetIndexedBackground(TextColor::DARK_CYAN);
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0;33;46m\033\\");
 
         Log::Comment(L"Requesting SGR attributes (AIX colors).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
-        _testGetSet->_attribute.SetIndexedForeground(TextColor::BRIGHT_CYAN);
-        _testGetSet->_attribute.SetIndexedBackground(TextColor::BRIGHT_YELLOW);
+        attribute = {};
+        attribute.SetIndexedForeground(TextColor::BRIGHT_CYAN);
+        attribute.SetIndexedBackground(TextColor::BRIGHT_YELLOW);
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0;96;103m\033\\");
 
         Log::Comment(L"Requesting SGR attributes (ITU indexed colors).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
-        _testGetSet->_attribute.SetIndexedForeground256(123);
-        _testGetSet->_attribute.SetIndexedBackground256(45);
+        attribute = {};
+        attribute.SetIndexedForeground256(123);
+        attribute.SetIndexedBackground256(45);
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0;38;5;123;48;5;45m\033\\");
 
         Log::Comment(L"Requesting SGR attributes (ITU RGB colors).");
         _testGetSet->PrepData();
-        _testGetSet->_attribute = {};
-        _testGetSet->_attribute.SetForeground(RGB(12, 34, 56));
-        _testGetSet->_attribute.SetBackground(RGB(65, 43, 21));
+        attribute = {};
+        attribute.SetForeground(RGB(12, 34, 56));
+        attribute.SetBackground(RGB(65, 43, 21));
+        _testGetSet->_textBuffer->SetCurrentAttributes(attribute);
         requestSetting(L"m");
         _testGetSet->ValidateInputEvent(L"\033P1$r0;38;2;12;34;56;48;2;65;43;21m\033\\");
 
@@ -1902,87 +1623,72 @@ class AdapterTest
     TEST_METHOD(CursorKeysModeTest)
     {
         Log::Comment(L"Starting test...");
+        _terminalInput.SetInputMode(TerminalInput::Mode::CursorKey, true);
 
         // success cases
         // set numeric mode = true
         Log::Comment(L"Test 1: application mode = false");
-        _testGetSet->_setInputModeResult = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::CursorKey;
-        _testGetSet->_expectedInputModeEnabled = false;
-
-        VERIFY_IS_TRUE(_pDispatch.get()->SetCursorKeysMode(false));
+        VERIFY_IS_TRUE(_pDispatch->SetCursorKeysMode(false));
+        VERIFY_IS_FALSE(_terminalInput.GetInputMode(TerminalInput::Mode::CursorKey));
 
         // set numeric mode = false
         Log::Comment(L"Test 2: application mode = true");
-        _testGetSet->_setInputModeResult = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::CursorKey;
-        _testGetSet->_expectedInputModeEnabled = true;
-
-        VERIFY_IS_TRUE(_pDispatch.get()->SetCursorKeysMode(true));
+        VERIFY_IS_TRUE(_pDispatch->SetCursorKeysMode(true));
+        VERIFY_IS_TRUE(_terminalInput.GetInputMode(TerminalInput::Mode::CursorKey));
     }
 
     TEST_METHOD(KeypadModeTest)
     {
         Log::Comment(L"Starting test...");
+        _terminalInput.SetInputMode(TerminalInput::Mode::Keypad, true);
 
         // success cases
         // set numeric mode = true
         Log::Comment(L"Test 1: application mode = false");
-        _testGetSet->_setInputModeResult = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::Keypad;
-        _testGetSet->_expectedInputModeEnabled = false;
-
-        VERIFY_IS_TRUE(_pDispatch.get()->SetKeypadMode(false));
+        VERIFY_IS_TRUE(_pDispatch->SetKeypadMode(false));
+        VERIFY_IS_FALSE(_terminalInput.GetInputMode(TerminalInput::Mode::Keypad));
 
         // set numeric mode = false
         Log::Comment(L"Test 2: application mode = true");
-        _testGetSet->_setInputModeResult = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::Keypad;
-        _testGetSet->_expectedInputModeEnabled = true;
-
-        VERIFY_IS_TRUE(_pDispatch.get()->SetKeypadMode(true));
+        VERIFY_IS_TRUE(_pDispatch->SetKeypadMode(true));
+        VERIFY_IS_TRUE(_terminalInput.GetInputMode(TerminalInput::Mode::Keypad));
     }
 
     TEST_METHOD(AnsiModeTest)
     {
         Log::Comment(L"Starting test...");
+        _stateMachine->SetParserMode(StateMachine::Mode::Ansi, false);
 
         // success cases
         // set ansi mode = true
         Log::Comment(L"Test 1: ansi mode = true");
-        _testGetSet->_setParserModeResult = true;
-        _testGetSet->_expectedParserMode = StateMachine::Mode::Ansi;
-        _testGetSet->_expectedParserModeEnabled = true;
-
-        VERIFY_IS_TRUE(_pDispatch.get()->SetAnsiMode(true));
+        VERIFY_IS_TRUE(_pDispatch->SetAnsiMode(true));
+        VERIFY_IS_TRUE(_stateMachine->GetParserMode(StateMachine::Mode::Ansi));
 
         // set ansi mode = false
         Log::Comment(L"Test 2: ansi mode = false.");
-        _testGetSet->_setParserModeResult = true;
-        _testGetSet->_expectedParserMode = StateMachine::Mode::Ansi;
-        _testGetSet->_expectedParserModeEnabled = false;
-
-        VERIFY_IS_TRUE(_pDispatch.get()->SetAnsiMode(false));
+        VERIFY_IS_TRUE(_pDispatch->SetAnsiMode(false));
+        VERIFY_IS_FALSE(_stateMachine->GetParserMode(StateMachine::Mode::Ansi));
     }
 
     TEST_METHOD(AllowBlinkingTest)
     {
         Log::Comment(L"Starting test...");
 
+        _testGetSet->PrepData();
+
         // success cases
-        // set numeric mode = true
+        // set blinking mode = true
         Log::Comment(L"Test 1: enable blinking = true");
-        _testGetSet->_enableCursorBlinkingResult = TRUE;
-        _testGetSet->_enable = true;
-
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableCursorBlinking(true));
+        _testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false);
+        VERIFY_IS_TRUE(_pDispatch->EnableCursorBlinking(true));
+        VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCursor().IsBlinkingAllowed());
 
-        // set numeric mode = false
+        // set blinking mode = false
         Log::Comment(L"Test 2: enable blinking = false");
-        _testGetSet->_enableCursorBlinkingResult = TRUE;
-        _testGetSet->_enable = false;
-
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableCursorBlinking(false));
+        _testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
+        VERIFY_IS_TRUE(_pDispatch->EnableCursorBlinking(false));
+        VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCursor().IsBlinkingAllowed());
     }
 
     TEST_METHOD(ScrollMarginsTest)
@@ -1990,85 +1696,82 @@ class AdapterTest
         Log::Comment(L"Starting test...");
 
         SMALL_RECT srTestMargins = { 0 };
-        _testGetSet->_bufferSize = { 100, 600 };
+        _testGetSet->_textBuffer = std::make_unique<TextBuffer>(COORD{ 100, 600 }, TextAttribute{}, 0, false, _testGetSet->_renderer);
         _testGetSet->_viewport.Right = 8;
         _testGetSet->_viewport.Bottom = 8;
-        _testGetSet->_getConsoleScreenBufferInfoExResult = TRUE;
         SHORT sScreenHeight = _testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top;
 
         Log::Comment(L"Test 1: Verify having both values is valid.");
         _testGetSet->_SetMarginsHelper(&srTestMargins, 2, 6);
         _testGetSet->_setScrollingRegionResult = TRUE;
-        _testGetSet->_setCursorPositionResult = true;
-        _testGetSet->_moveToBottomResult = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
 
         Log::Comment(L"Test 2: Verify having only top is valid.");
 
         _testGetSet->_SetMarginsHelper(&srTestMargins, 7, 0);
         _testGetSet->_expectedScrollRegion.Bottom = _testGetSet->_viewport.Bottom - 1; // We expect the bottom to be the bottom of the viewport, exclusive.
         _testGetSet->_setScrollingRegionResult = TRUE;
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
 
         Log::Comment(L"Test 3: Verify having only bottom is valid.");
 
         _testGetSet->_SetMarginsHelper(&srTestMargins, 0, 7);
         _testGetSet->_setScrollingRegionResult = TRUE;
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
 
         Log::Comment(L"Test 4: Verify having no values is valid.");
 
         _testGetSet->_SetMarginsHelper(&srTestMargins, 0, 0);
         _testGetSet->_setScrollingRegionResult = TRUE;
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
 
         Log::Comment(L"Test 5: Verify having both values, but bad bounds has no effect.");
 
         _testGetSet->_SetMarginsHelper(&srTestMargins, 7, 3);
         _testGetSet->_setScrollingRegionResult = TRUE;
         _testGetSet->_activeScrollRegion = {};
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
         VERIFY_ARE_EQUAL(SMALL_RECT{}, _testGetSet->_activeScrollRegion);
 
         Log::Comment(L"Test 6: Verify setting margins to (0, height) clears them");
         // First set,
         _testGetSet->_setScrollingRegionResult = TRUE;
         _testGetSet->_SetMarginsHelper(&srTestMargins, 2, 6);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
         // Then clear
         _testGetSet->_SetMarginsHelper(&srTestMargins, 0, sScreenHeight);
         _testGetSet->_expectedScrollRegion.Top = 0;
         _testGetSet->_expectedScrollRegion.Bottom = 0;
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
 
         Log::Comment(L"Test 7: Verify setting margins to (1, height) clears them");
         // First set,
         _testGetSet->_setScrollingRegionResult = TRUE;
         _testGetSet->_SetMarginsHelper(&srTestMargins, 2, 6);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
         // Then clear
         _testGetSet->_SetMarginsHelper(&srTestMargins, 1, sScreenHeight);
         _testGetSet->_expectedScrollRegion.Top = 0;
         _testGetSet->_expectedScrollRegion.Bottom = 0;
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
 
         Log::Comment(L"Test 8: Verify setting margins to (1, 0) clears them");
         // First set,
         _testGetSet->_setScrollingRegionResult = TRUE;
         _testGetSet->_SetMarginsHelper(&srTestMargins, 2, 6);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
         // Then clear
         _testGetSet->_SetMarginsHelper(&srTestMargins, 1, 0);
         _testGetSet->_expectedScrollRegion.Top = 0;
         _testGetSet->_expectedScrollRegion.Bottom = 0;
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
 
         Log::Comment(L"Test 9: Verify having top and bottom margin the same has no effect.");
 
         _testGetSet->_SetMarginsHelper(&srTestMargins, 4, 4);
         _testGetSet->_setScrollingRegionResult = TRUE;
         _testGetSet->_activeScrollRegion = {};
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
         VERIFY_ARE_EQUAL(SMALL_RECT{}, _testGetSet->_activeScrollRegion);
 
         Log::Comment(L"Test 10: Verify having top margin out of bounds has no effect.");
@@ -2076,7 +1779,7 @@ class AdapterTest
         _testGetSet->_SetMarginsHelper(&srTestMargins, sScreenHeight + 1, sScreenHeight + 10);
         _testGetSet->_setScrollingRegionResult = TRUE;
         _testGetSet->_activeScrollRegion = {};
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
         VERIFY_ARE_EQUAL(SMALL_RECT{}, _testGetSet->_activeScrollRegion);
 
         Log::Comment(L"Test 11: Verify having bottom margin out of bounds has no effect.");
@@ -2084,7 +1787,7 @@ class AdapterTest
         _testGetSet->_SetMarginsHelper(&srTestMargins, 1, sScreenHeight + 1);
         _testGetSet->_setScrollingRegionResult = TRUE;
         _testGetSet->_activeScrollRegion = {};
-        VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
+        VERIFY_IS_TRUE(_pDispatch->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
         VERIFY_ARE_EQUAL(SMALL_RECT{}, _testGetSet->_activeScrollRegion);
     }
 
@@ -2097,21 +1800,21 @@ class AdapterTest
 
         Log::Comment(L"Test 1: Line feed without carriage return.");
         _testGetSet->_expectedLineFeedWithReturn = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::WithoutReturn));
+        VERIFY_IS_TRUE(_pDispatch->LineFeed(DispatchTypes::LineFeedType::WithoutReturn));
 
         Log::Comment(L"Test 2: Line feed with carriage return.");
         _testGetSet->_expectedLineFeedWithReturn = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::WithReturn));
+        VERIFY_IS_TRUE(_pDispatch->LineFeed(DispatchTypes::LineFeedType::WithReturn));
 
         Log::Comment(L"Test 3: Line feed depends on mode, and mode reset.");
         _testGetSet->_getLineFeedModeResult = false;
         _testGetSet->_expectedLineFeedWithReturn = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
+        VERIFY_IS_TRUE(_pDispatch->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
 
         Log::Comment(L"Test 4: Line feed depends on mode, and mode set.");
         _testGetSet->_getLineFeedModeResult = true;
         _testGetSet->_expectedLineFeedWithReturn = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
+        VERIFY_IS_TRUE(_pDispatch->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
     }
 
     TEST_METHOD(SetConsoleTitleTest)
@@ -2122,13 +1825,13 @@ class AdapterTest
         _testGetSet->_setWindowTitleResult = TRUE;
         _testGetSet->_expectedWindowTitle = L"Foo bar";
 
-        VERIFY_IS_TRUE(_pDispatch.get()->SetWindowTitle(_testGetSet->_expectedWindowTitle));
+        VERIFY_IS_TRUE(_pDispatch->SetWindowTitle(_testGetSet->_expectedWindowTitle));
 
         Log::Comment(L"Test 2: set title to be null");
         _testGetSet->_setWindowTitleResult = FALSE;
         _testGetSet->_expectedWindowTitle = {};
 
-        VERIFY_IS_TRUE(_pDispatch.get()->SetWindowTitle({}));
+        VERIFY_IS_TRUE(_pDispatch->SetWindowTitle({}));
     }
 
     TEST_METHOD(TestMouseModes)
@@ -2136,52 +1839,46 @@ class AdapterTest
         Log::Comment(L"Starting test...");
 
         Log::Comment(L"Test 1: Test Default Mouse Mode");
-        _testGetSet->_expectedInputModeEnabled = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::DefaultMouseTracking;
-        _testGetSet->_setInputModeResult = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableVT200MouseMode(true));
-        _testGetSet->_expectedInputModeEnabled = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableVT200MouseMode(false));
+        _terminalInput.SetInputMode(TerminalInput::Mode::DefaultMouseTracking, false);
+        VERIFY_IS_TRUE(_pDispatch->EnableVT200MouseMode(true));
+        VERIFY_IS_TRUE(_terminalInput.GetInputMode(TerminalInput::Mode::DefaultMouseTracking));
+        VERIFY_IS_TRUE(_pDispatch->EnableVT200MouseMode(false));
+        VERIFY_IS_FALSE(_terminalInput.GetInputMode(TerminalInput::Mode::DefaultMouseTracking));
 
         Log::Comment(L"Test 2: Test UTF-8 Extended Mouse Mode");
-        _testGetSet->_expectedInputModeEnabled = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::Utf8MouseEncoding;
-        _testGetSet->_setInputModeResult = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableUTF8ExtendedMouseMode(true));
-        _testGetSet->_expectedInputModeEnabled = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableUTF8ExtendedMouseMode(false));
+        _terminalInput.SetInputMode(TerminalInput::Mode::Utf8MouseEncoding, false);
+        VERIFY_IS_TRUE(_pDispatch->EnableUTF8ExtendedMouseMode(true));
+        VERIFY_IS_TRUE(_terminalInput.GetInputMode(TerminalInput::Mode::Utf8MouseEncoding));
+        VERIFY_IS_TRUE(_pDispatch->EnableUTF8ExtendedMouseMode(false));
+        VERIFY_IS_FALSE(_terminalInput.GetInputMode(TerminalInput::Mode::Utf8MouseEncoding));
 
         Log::Comment(L"Test 3: Test SGR Extended Mouse Mode");
-        _testGetSet->_expectedInputModeEnabled = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::SgrMouseEncoding;
-        _testGetSet->_setInputModeResult = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableSGRExtendedMouseMode(true));
-        _testGetSet->_expectedInputModeEnabled = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableSGRExtendedMouseMode(false));
+        _terminalInput.SetInputMode(TerminalInput::Mode::SgrMouseEncoding, false);
+        VERIFY_IS_TRUE(_pDispatch->EnableSGRExtendedMouseMode(true));
+        VERIFY_IS_TRUE(_terminalInput.GetInputMode(TerminalInput::Mode::SgrMouseEncoding));
+        VERIFY_IS_TRUE(_pDispatch->EnableSGRExtendedMouseMode(false));
+        VERIFY_IS_FALSE(_terminalInput.GetInputMode(TerminalInput::Mode::SgrMouseEncoding));
 
         Log::Comment(L"Test 4: Test Button-Event Mouse Mode");
-        _testGetSet->_expectedInputModeEnabled = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::ButtonEventMouseTracking;
-        _testGetSet->_setInputModeResult = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableButtonEventMouseMode(true));
-        _testGetSet->_expectedInputModeEnabled = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableButtonEventMouseMode(false));
+        _terminalInput.SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, false);
+        VERIFY_IS_TRUE(_pDispatch->EnableButtonEventMouseMode(true));
+        VERIFY_IS_TRUE(_terminalInput.GetInputMode(TerminalInput::Mode::ButtonEventMouseTracking));
+        VERIFY_IS_TRUE(_pDispatch->EnableButtonEventMouseMode(false));
+        VERIFY_IS_FALSE(_terminalInput.GetInputMode(TerminalInput::Mode::ButtonEventMouseTracking));
 
         Log::Comment(L"Test 5: Test Any-Event Mouse Mode");
-        _testGetSet->_expectedInputModeEnabled = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::AnyEventMouseTracking;
-        _testGetSet->_setInputModeResult = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableAnyEventMouseMode(true));
-        _testGetSet->_expectedInputModeEnabled = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableAnyEventMouseMode(false));
+        _terminalInput.SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, false);
+        VERIFY_IS_TRUE(_pDispatch->EnableAnyEventMouseMode(true));
+        VERIFY_IS_TRUE(_terminalInput.GetInputMode(TerminalInput::Mode::AnyEventMouseTracking));
+        VERIFY_IS_TRUE(_pDispatch->EnableAnyEventMouseMode(false));
+        VERIFY_IS_FALSE(_terminalInput.GetInputMode(TerminalInput::Mode::AnyEventMouseTracking));
 
         Log::Comment(L"Test 6: Test Alt Scroll Mouse Mode");
-        _testGetSet->_expectedInputModeEnabled = true;
-        _testGetSet->_expectedInputMode = TerminalInput::Mode::AlternateScroll;
-        _testGetSet->_setInputModeResult = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableAlternateScroll(true));
-        _testGetSet->_expectedInputModeEnabled = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->EnableAlternateScroll(false));
+        _terminalInput.SetInputMode(TerminalInput::Mode::AlternateScroll, false);
+        VERIFY_IS_TRUE(_pDispatch->EnableAlternateScroll(true));
+        VERIFY_IS_TRUE(_terminalInput.GetInputMode(TerminalInput::Mode::AlternateScroll));
+        VERIFY_IS_TRUE(_pDispatch->EnableAlternateScroll(false));
+        VERIFY_IS_FALSE(_terminalInput.GetInputMode(TerminalInput::Mode::AlternateScroll));
     }
 
     TEST_METHOD(Xterm256ColorTest)
@@ -2193,36 +1890,35 @@ class AdapterTest
         VTParameter rgOptions[16];
         size_t cOptions = 3;
 
-        _testGetSet->_getColorTableEntryResult = true;
-        _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+        _testGetSet->_expectedAttribute = _testGetSet->_textBuffer->GetCurrentAttributes();
 
         Log::Comment(L"Test 1: Change Foreground");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
         rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
         rgOptions[2] = (DispatchTypes::GraphicsOptions)2; // Green
         _testGetSet->_expectedAttribute.SetIndexedForeground256(TextColor::DARK_GREEN);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
 
         Log::Comment(L"Test 2: Change Background");
         rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
         rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
         rgOptions[2] = (DispatchTypes::GraphicsOptions)9; // Bright Red
         _testGetSet->_expectedAttribute.SetIndexedBackground256(TextColor::BRIGHT_RED);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
 
         Log::Comment(L"Test 3: Change Foreground to RGB color");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
         rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
         rgOptions[2] = (DispatchTypes::GraphicsOptions)42; // Arbitrary Color
         _testGetSet->_expectedAttribute.SetIndexedForeground256(42);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
 
         Log::Comment(L"Test 4: Change Background to RGB color");
         rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
         rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
         rgOptions[2] = (DispatchTypes::GraphicsOptions)142; // Arbitrary Color
         _testGetSet->_expectedAttribute.SetIndexedBackground256(142);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
 
         Log::Comment(L"Test 5: Change Foreground to Legacy Attr while BG is RGB color");
         // Unfortunately this test isn't all that good, because the adapterTest adapter isn't smart enough
@@ -2232,7 +1928,7 @@ class AdapterTest
         rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
         rgOptions[2] = (DispatchTypes::GraphicsOptions)9; // Bright Red
         _testGetSet->_expectedAttribute.SetIndexedForeground256(TextColor::BRIGHT_RED);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
     }
 
     TEST_METHOD(XtermExtendedColorDefaultParameterTest)
@@ -2243,34 +1939,33 @@ class AdapterTest
 
         VTParameter rgOptions[16];
 
-        _testGetSet->_getColorTableEntryResult = true;
-        _testGetSet->_expectedAttribute = _testGetSet->_attribute;
+        _testGetSet->_expectedAttribute = _testGetSet->_textBuffer->GetCurrentAttributes();
 
         Log::Comment(L"Test 1: Change Indexed Foreground with missing index parameter");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
         rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
         _testGetSet->_expectedAttribute.SetIndexedForeground256(TextColor::DARK_BLACK);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 2 }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, 2 }));
 
         Log::Comment(L"Test 2: Change Indexed Background with default index parameter");
         rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
         rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
         rgOptions[2] = {};
         _testGetSet->_expectedAttribute.SetIndexedBackground256(TextColor::DARK_BLACK);
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 3 }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, 3 }));
 
         Log::Comment(L"Test 3: Change RGB Foreground with all RGB parameters missing");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
         rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
         _testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 0));
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 2 }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, 2 }));
 
         Log::Comment(L"Test 4: Change RGB Background with some missing RGB parameters");
         rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
         rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
         rgOptions[2] = 123;
         _testGetSet->_expectedAttribute.SetBackground(RGB(123, 0, 0));
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 3 }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, 3 }));
 
         Log::Comment(L"Test 5: Change RGB Foreground with some default RGB parameters");
         rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
@@ -2279,21 +1974,20 @@ class AdapterTest
         rgOptions[3] = {};
         rgOptions[4] = 123;
         _testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 123));
-        VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 5 }));
+        VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, 5 }));
     }
 
     TEST_METHOD(SetColorTableValue)
     {
         _testGetSet->PrepData();
 
-        _testGetSet->_setColorTableEntryResult = true;
         const auto testColor = RGB(1, 2, 3);
-        _testGetSet->_expectedColorValue = testColor;
+        const auto& renderSettings = _testGetSet->_renderer._renderSettings;
 
         for (size_t i = 0; i < 256; i++)
         {
-            _testGetSet->_expectedColorTableIndex = i;
-            VERIFY_IS_TRUE(_pDispatch.get()->SetColorTableEntry(i, testColor));
+            VERIFY_IS_TRUE(_pDispatch->SetColorTableEntry(i, testColor));
+            VERIFY_ARE_EQUAL(testColor, renderSettings.GetColorTableEntry(i));
         }
     }
 
@@ -2303,43 +1997,49 @@ class AdapterTest
         using FontSet = DispatchTypes::DrcsFontSet;
         using FontUsage = DispatchTypes::DrcsFontUsage;
 
-        const auto decdld = [=](const auto cmw, const auto cmh, const auto ss, const auto u, const std::wstring_view data = {}) {
-            const auto ec = DispatchTypes::DrcsEraseControl::AllChars;
-            const auto css = DispatchTypes::DrcsCharsetSize::Size94;
+        FontBuffer fontBuffer;
+        SIZE expectedCellSize;
+
+        const auto decdld = [&](const auto cmw, const auto cmh, const auto ss, const auto u, const std::wstring_view data = {}) {
             const auto cellMatrix = static_cast<DispatchTypes::DrcsCellMatrix>(cmw);
-            const auto stringHandler = _pDispatch.get()->DownloadDRCS(0, 0, ec, cellMatrix, ss, u, cmh, css);
-            if (stringHandler)
+            RETURN_BOOL_IF_FALSE(fontBuffer.SetEraseControl(DispatchTypes::DrcsEraseControl::AllChars));
+            RETURN_BOOL_IF_FALSE(fontBuffer.SetAttributes(cellMatrix, cmh, ss, u));
+            RETURN_BOOL_IF_FALSE(fontBuffer.SetStartChar(0, DispatchTypes::DrcsCharsetSize::Size94));
+
+            fontBuffer.AddSixelData(L'B'); // Charset identifier
+            for (auto ch : data)
             {
-                stringHandler(L'B'); // Charset identifier
-                for (auto ch : data)
-                {
-                    stringHandler(ch);
-                }
-                stringHandler(L'\033'); // String terminator
+                fontBuffer.AddSixelData(ch);
             }
-            return stringHandler != nullptr;
+            RETURN_BOOL_IF_FALSE(fontBuffer.FinalizeSixelData());
+
+            const auto cellSize = fontBuffer.GetCellSize().to_win32_size();
+            Log::Comment(NoThrowString().Format(L"Cell size: %dx%d", cellSize.cx, cellSize.cy));
+            VERIFY_ARE_EQUAL(expectedCellSize.cx, cellSize.cx);
+            VERIFY_ARE_EQUAL(expectedCellSize.cy, cellSize.cy);
+            return true;
         };
 
         // Matrix sizes at 80x24 should always use a 10x10 cell size (VT2xx).
         Log::Comment(L"Matrix 5x10 for 80x24 font set with text usage");
-        _testGetSet->_expectedCellSize = { 10, 10 };
+        expectedCellSize = { 10, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Size5x10, 0, FontSet::Size80x24, FontUsage::Text));
         Log::Comment(L"Matrix 6x10 for 80x24 font set with text usage");
-        _testGetSet->_expectedCellSize = { 10, 10 };
+        expectedCellSize = { 10, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Size6x10, 0, FontSet::Size80x24, FontUsage::Text));
         Log::Comment(L"Matrix 7x10 for 80x24 font set with text usage");
-        _testGetSet->_expectedCellSize = { 10, 10 };
+        expectedCellSize = { 10, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Size7x10, 0, FontSet::Size80x24, FontUsage::Text));
 
         // At 132x24 the cell size is typically 6x10 (VT240), but could be 10x10 (VT220)
         Log::Comment(L"Matrix 5x10 for 132x24 font set with text usage");
-        _testGetSet->_expectedCellSize = { 6, 10 };
+        expectedCellSize = { 6, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Size5x10, 0, FontSet::Size132x24, FontUsage::Text));
         Log::Comment(L"Matrix 6x10 for 132x24 font set with text usage");
-        _testGetSet->_expectedCellSize = { 6, 10 };
+        expectedCellSize = { 6, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Size6x10, 0, FontSet::Size132x24, FontUsage::Text));
         Log::Comment(L"Matrix 7x10 for 132x24 font set with text usage (VT220 only)");
-        _testGetSet->_expectedCellSize = { 10, 10 };
+        expectedCellSize = { 10, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Size7x10, 0, FontSet::Size132x24, FontUsage::Text));
 
         // Full cell usage is invalid for all matrix sizes except 6x10 at 132x24.
@@ -2352,7 +2052,7 @@ class AdapterTest
         Log::Comment(L"Matrix 5x10 for 132x24 font set with full cell usage (invalid)");
         VERIFY_IS_FALSE(decdld(CellMatrix::Size5x10, 0, FontSet::Size132x24, FontUsage::FullCell));
         Log::Comment(L"Matrix 6x10 for 132x24 font set with full cell usage");
-        _testGetSet->_expectedCellSize = { 6, 10 };
+        expectedCellSize = { 6, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Size6x10, 0, FontSet::Size132x24, FontUsage::FullCell));
         Log::Comment(L"Matrix 7x10 for 132x24 font set with full cell usage (invalid)");
         VERIFY_IS_FALSE(decdld(CellMatrix::Size7x10, 0, FontSet::Size132x24, FontUsage::FullCell));
@@ -2369,15 +2069,15 @@ class AdapterTest
 
         // The height parameter has no effect when a matrix size is used.
         Log::Comment(L"Matrix 7x10 with unused height parameter");
-        _testGetSet->_expectedCellSize = { 10, 10 };
+        expectedCellSize = { 10, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Size7x10, 20, FontSet::Size80x24, FontUsage::Text));
 
         // Full cell fonts with explicit dimensions are accepted as their given cell size.
         Log::Comment(L"Explicit 13x17 for 80x24 font set with full cell usage");
-        _testGetSet->_expectedCellSize = { 13, 17 };
+        expectedCellSize = { 13, 17 };
         VERIFY_IS_TRUE(decdld(13, 17, FontSet::Size80x24, FontUsage::FullCell));
         Log::Comment(L"Explicit 9x25 for 132x24 font set with full cell usage");
-        _testGetSet->_expectedCellSize = { 9, 25 };
+        expectedCellSize = { 9, 25 };
         VERIFY_IS_TRUE(decdld(9, 25, FontSet::Size132x24, FontUsage::FullCell));
 
         // Cell sizes outside the maximum supported range (16x32) are invalid.
@@ -2386,174 +2086,170 @@ class AdapterTest
 
         // Text fonts with explicit dimensions are interpreted as their closest matching device.
         Log::Comment(L"Explicit 12x12 for 80x24 font set with text usage (VT320)");
-        _testGetSet->_expectedCellSize = { 15, 12 };
+        expectedCellSize = { 15, 12 };
         VERIFY_IS_TRUE(decdld(12, 12, FontSet::Size80x24, FontUsage::Text));
         Log::Comment(L"Explicit 9x20 for 80x24 font set with text usage (VT340)");
-        _testGetSet->_expectedCellSize = { 10, 20 };
+        expectedCellSize = { 10, 20 };
         VERIFY_IS_TRUE(decdld(9, 20, FontSet::Size80x24, FontUsage::Text));
         Log::Comment(L"Explicit 10x30 for 80x24 font set with text usage (VT382)");
-        _testGetSet->_expectedCellSize = { 12, 30 };
+        expectedCellSize = { 12, 30 };
         VERIFY_IS_TRUE(decdld(10, 30, FontSet::Size80x24, FontUsage::Text));
         Log::Comment(L"Explicit 8x16 for 80x24 font set with text usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 10, 16 };
+        expectedCellSize = { 10, 16 };
         VERIFY_IS_TRUE(decdld(8, 16, FontSet::Size80x24, FontUsage::Text));
         Log::Comment(L"Explicit 7x12 for 132x24 font set with text usage (VT320)");
-        _testGetSet->_expectedCellSize = { 9, 12 };
+        expectedCellSize = { 9, 12 };
         VERIFY_IS_TRUE(decdld(7, 12, FontSet::Size132x24, FontUsage::Text));
         Log::Comment(L"Explicit 5x20 for 132x24 font set with text usage (VT340)");
-        _testGetSet->_expectedCellSize = { 6, 20 };
+        expectedCellSize = { 6, 20 };
         VERIFY_IS_TRUE(decdld(5, 20, FontSet::Size132x24, FontUsage::Text));
         Log::Comment(L"Explicit 6x30 for 132x24 font set with text usage (VT382)");
-        _testGetSet->_expectedCellSize = { 7, 30 };
+        expectedCellSize = { 7, 30 };
         VERIFY_IS_TRUE(decdld(6, 30, FontSet::Size132x24, FontUsage::Text));
         Log::Comment(L"Explicit 5x16 for 132x24 font set with text usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 6, 16 };
+        expectedCellSize = { 6, 16 };
         VERIFY_IS_TRUE(decdld(5, 16, FontSet::Size132x24, FontUsage::Text));
 
         // Font sets with more than 24 lines must be VT420/VT5xx.
         Log::Comment(L"80x36 font set with text usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 10, 10 };
+        expectedCellSize = { 10, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x36, FontUsage::Text));
         Log::Comment(L"80x48 font set with text usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 10, 8 };
+        expectedCellSize = { 10, 8 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x48, FontUsage::Text));
         Log::Comment(L"132x36 font set with text usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 6, 10 };
+        expectedCellSize = { 6, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x36, FontUsage::Text));
         Log::Comment(L"132x48 font set with text usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 6, 8 };
+        expectedCellSize = { 6, 8 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x48, FontUsage::Text));
         Log::Comment(L"80x36 font set with full cell usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 10, 10 };
+        expectedCellSize = { 10, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x36, FontUsage::FullCell));
         Log::Comment(L"80x48 font set with full cell usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 10, 8 };
+        expectedCellSize = { 10, 8 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x48, FontUsage::FullCell));
         Log::Comment(L"132x36 font set with full cell usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 6, 10 };
+        expectedCellSize = { 6, 10 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x36, FontUsage::FullCell));
         Log::Comment(L"132x48 font set with full cell usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 6, 8 };
+        expectedCellSize = { 6, 8 };
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x48, FontUsage::FullCell));
 
         // Without an explicit size, the cell size is estimated from the number of sixels
         // used in the character bitmaps. But note that sixel heights are always a multiple
         // of 6, so will often be larger than the cell size for which they were intended.
         Log::Comment(L"8x12 bitmap for 80x24 font set with text usage (VT2xx)");
-        _testGetSet->_expectedCellSize = { 10, 10 };
+        expectedCellSize = { 10, 10 };
         const auto bitmapOf8x12 = L"????????/????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf8x12));
         Log::Comment(L"12x12 bitmap for 80x24 font set with text usage (VT320)");
-        _testGetSet->_expectedCellSize = { 15, 12 };
+        expectedCellSize = { 15, 12 };
         const auto bitmapOf12x12 = L"????????????/????????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf12x12));
         Log::Comment(L"9x24 bitmap for 80x24 font set with text usage (VT340)");
-        _testGetSet->_expectedCellSize = { 10, 20 };
+        expectedCellSize = { 10, 20 };
         const auto bitmapOf9x24 = L"?????????/?????????/?????????/?????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf9x24));
         Log::Comment(L"10x30 bitmap for 80x24 font set with text usage (VT382)");
-        _testGetSet->_expectedCellSize = { 12, 30 };
+        expectedCellSize = { 12, 30 };
         const auto bitmapOf10x30 = L"??????????/??????????/??????????/??????????/??????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf10x30));
         Log::Comment(L"8x18 bitmap for 80x24 font set with text usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 10, 16 };
+        expectedCellSize = { 10, 16 };
         const auto bitmapOf8x18 = L"????????/????????/????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf8x18));
 
         Log::Comment(L"5x12 bitmap for 132x24 font set with text usage (VT240)");
-        _testGetSet->_expectedCellSize = { 6, 10 };
+        expectedCellSize = { 6, 10 };
         const auto bitmapOf5x12 = L"?????/?????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf5x12));
         Log::Comment(L"7x12 bitmap for 132x24 font set with text usage (VT320)");
-        _testGetSet->_expectedCellSize = { 9, 12 };
+        expectedCellSize = { 9, 12 };
         const auto bitmapOf7x12 = L"???????/???????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf7x12));
         Log::Comment(L"5x24 bitmap for 132x24 font set with text usage (VT340)");
-        _testGetSet->_expectedCellSize = { 6, 20 };
+        expectedCellSize = { 6, 20 };
         const auto bitmapOf5x24 = L"?????/?????/?????/?????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf5x24));
         Log::Comment(L"6x30 bitmap for 132x24 font set with text usage (VT382)");
-        _testGetSet->_expectedCellSize = { 7, 30 };
+        expectedCellSize = { 7, 30 };
         const auto bitmapOf6x30 = L"??????/??????/??????/??????/??????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf6x30));
         Log::Comment(L"5x18 bitmap for 132x24 font set with text usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 6, 16 };
+        expectedCellSize = { 6, 16 };
         const auto bitmapOf5x18 = L"?????/?????/?????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf5x18));
 
         Log::Comment(L"15x12 bitmap for 80x24 font set with full cell usage (VT320)");
-        _testGetSet->_expectedCellSize = { 15, 12 };
+        expectedCellSize = { 15, 12 };
         const auto bitmapOf15x12 = L"???????????????/???????????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::FullCell, bitmapOf15x12));
         Log::Comment(L"10x24 bitmap for 80x24 font set with full cell usage (VT340)");
-        _testGetSet->_expectedCellSize = { 10, 20 };
+        expectedCellSize = { 10, 20 };
         const auto bitmapOf10x24 = L"??????????/??????????/??????????/??????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::FullCell, bitmapOf10x24));
         Log::Comment(L"12x30 bitmap for 80x24 font set with full cell usage (VT382)");
-        _testGetSet->_expectedCellSize = { 12, 30 };
+        expectedCellSize = { 12, 30 };
         const auto bitmapOf12x30 = L"????????????/????????????/????????????/????????????/????????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::FullCell, bitmapOf12x30));
         Log::Comment(L"10x18 bitmap for 80x24 font set with full cell usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 10, 16 };
+        expectedCellSize = { 10, 16 };
         const auto bitmapOf10x18 = L"??????????/??????????/??????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::FullCell, bitmapOf10x18));
 
         Log::Comment(L"6x12 bitmap for 132x24 font set with full cell usage (VT240)");
-        _testGetSet->_expectedCellSize = { 6, 10 };
+        expectedCellSize = { 6, 10 };
         const auto bitmapOf6x12 = L"??????/??????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf6x12));
         Log::Comment(L"9x12 bitmap for 132x24 font set with full cell usage (VT320)");
-        _testGetSet->_expectedCellSize = { 9, 12 };
+        expectedCellSize = { 9, 12 };
         const auto bitmapOf9x12 = L"?????????/?????????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf9x12));
         Log::Comment(L"6x24 bitmap for 132x24 font set with full cell usage (VT340)");
-        _testGetSet->_expectedCellSize = { 6, 20 };
+        expectedCellSize = { 6, 20 };
         const auto bitmapOf6x24 = L"??????/??????/??????/??????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf6x24));
         Log::Comment(L"7x30 bitmap for 132x24 font set with full cell usage (VT382)");
-        _testGetSet->_expectedCellSize = { 7, 30 };
+        expectedCellSize = { 7, 30 };
         const auto bitmapOf7x30 = L"???????/???????/???????/???????/???????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf7x30));
         Log::Comment(L"6x18 bitmap for 132x24 font set with full cell usage (VT420/VT5xx)");
-        _testGetSet->_expectedCellSize = { 6, 16 };
+        expectedCellSize = { 6, 16 };
         const auto bitmapOf6x18 = L"??????/??????/??????";
         VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf6x18));
     }
 
     TEST_METHOD(TogglingC1ParserMode)
     {
+        _stateMachine->SetParserMode(StateMachine::Mode::AcceptC1, false);
+
         Log::Comment(L"1. Accept C1 controls");
-        _testGetSet->_setParserModeResult = true;
-        _testGetSet->_expectedParserMode = StateMachine::Mode::AcceptC1;
-        _testGetSet->_expectedParserModeEnabled = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->AcceptC1Controls(true));
+        VERIFY_IS_TRUE(_pDispatch->AcceptC1Controls(true));
+        VERIFY_IS_TRUE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1));
 
         Log::Comment(L"2. Don't accept C1 controls");
-        _testGetSet->_setParserModeResult = true;
-        _testGetSet->_expectedParserMode = StateMachine::Mode::AcceptC1;
-        _testGetSet->_expectedParserModeEnabled = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->AcceptC1Controls(false));
+        VERIFY_IS_TRUE(_pDispatch->AcceptC1Controls(false));
+        VERIFY_IS_FALSE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1));
 
         Log::Comment(L"3. Designate ISO-2022 coding system");
         // Code page should be set to ISO-8859-1 and C1 parsing enabled
         _testGetSet->_setConsoleOutputCPResult = true;
         _testGetSet->_expectedOutputCP = 28591;
-        _testGetSet->_setParserModeResult = true;
-        _testGetSet->_expectedParserMode = StateMachine::Mode::AcceptC1;
-        _testGetSet->_expectedParserModeEnabled = true;
-        VERIFY_IS_TRUE(_pDispatch.get()->DesignateCodingSystem(DispatchTypes::CodingSystem::ISO2022));
+        VERIFY_IS_TRUE(_pDispatch->DesignateCodingSystem(DispatchTypes::CodingSystem::ISO2022));
+        VERIFY_IS_TRUE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1));
 
         Log::Comment(L"4. Designate UTF-8 coding system");
         // Code page should be set to UTF-8 and C1 parsing disabled
         _testGetSet->_setConsoleOutputCPResult = true;
         _testGetSet->_expectedOutputCP = CP_UTF8;
-        _testGetSet->_setParserModeResult = true;
-        _testGetSet->_expectedParserMode = StateMachine::Mode::AcceptC1;
-        _testGetSet->_expectedParserModeEnabled = false;
-        VERIFY_IS_TRUE(_pDispatch.get()->DesignateCodingSystem(DispatchTypes::CodingSystem::UTF8));
+        VERIFY_IS_TRUE(_pDispatch->DesignateCodingSystem(DispatchTypes::CodingSystem::UTF8));
+        VERIFY_IS_FALSE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1));
     }
 
 private:
+    TerminalInput _terminalInput{ nullptr };
     TestGetSet* _testGetSet; // non-ownership pointer
-    std::unique_ptr<AdaptDispatch> _pDispatch;
+    AdaptDispatch* _pDispatch; // non-ownership pointer
+    std::unique_ptr<StateMachine> _stateMachine;
 };


Test patch: