Repository: microsoft/terminal (language: cpp)

Issue:
Terminal doesen't show a clause cursor of Microsoft IME
### Windows Terminal version

1.17.11461.0

### Windows build number

10.0.19045.3570

### Other Software

Microsoft IME

### Steps to reproduce

1. invoke Microsoft IME
2. enter ro-maji that will be translated to Kanji.
3. IME show some clauses



### Expected Behavior

## Expected Behavior

After step three 

- We can change the taget clause to the left or the right.
- We can enlarge or shorten the target clause

with shortcut keys.

## Bare Command Prompt

In the attached movie, the target clause's background color is blue.
You can see its move and the change of its size.

https://github.com/microsoft/terminal/assets/12756480/cc9b131f-6e21-4ada-8307-3ee7e1dd294f





### Actual Behavior

## Terminal

There is no visual feed back, so we cannot see which part is the target clause.
It's very hard to use.

https://github.com/microsoft/terminal/assets/12756480/06ef369b-0b2c-45d8-9bca-68029ba60b92


Discussion / hints:
I expect this to be fixed if we replace the Windows Terminal IME with the TSF implementation from conhost.

Fix patch:
diff --git a/.github/actions/spelling/expect/alphabet.txt b/.github/actions/spelling/expect/alphabet.txt
index 97180b72115..6a2ee3bb519 100644
--- a/.github/actions/spelling/expect/alphabet.txt
+++ b/.github/actions/spelling/expect/alphabet.txt
@@ -1,4 +1,3 @@
-AAAa
 AAAAA
 AAAAAAAAAAAAA
 AAAAAABBBBBBCCC
diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt
index e458086ce89..36e345cfbbd 100644
--- a/.github/actions/spelling/expect/expect.txt
+++ b/.github/actions/spelling/expect/expect.txt
@@ -10,6 +10,7 @@ ACCESSTOKEN
 acidev
 ACIOSS
 ACover
+acp
 actctx
 ACTCTXW
 ADDALIAS
@@ -67,10 +68,10 @@ ASBSET
 asdfghjkl
 ASetting
 ASingle
+ASYNCDONTCARE
 ASYNCWINDOWPOS
 atch
 ATest
-ATTRCOLOR
 aumid
 Authenticode
 AUTOBUDDY
@@ -124,8 +125,6 @@ Blt
 BLUESCROLL
 BODGY
 BOLDFONT
-BOOLIFY
-bools
 Borland
 boutput
 boxheader
@@ -156,7 +155,6 @@ cazamor
 CBash
 cbiex
 CBN
-CBoolean
 cbt
 CCCBB
 cch
@@ -164,12 +162,9 @@ CCHAR
 CCmd
 ccolor
 CCom
-CComp
 CConsole
-CConversion
 CCRT
 cdd
-CEdit
 CELLSIZE
 cfae
 cfie
@@ -179,18 +174,17 @@ CFuzz
 cgscrn
 chafa
 changelists
-chaof
 charinfo
 CHARSETINFO
 chh
 chshdng
 CHT
-Cic
 CLASSSTRING
 CLE
 cleartype
 CLICKACTIVE
 clickdown
+CLIENTID
 clipbrd
 CLIPCHILDREN
 CLIPSIBLINGS
@@ -229,7 +223,6 @@ commdlg
 COMMITID
 componentization
 conapi
-conareainfo
 conattrs
 conbufferout
 concfg
@@ -240,8 +233,7 @@ condrv
 conechokey
 conemu
 conhost
-conime
-conimeinfo
+CONIME
 conintegrity
 conintegrityuwp
 coninteractivitybase
@@ -279,7 +271,6 @@ contentfiles
 conterm
 contsf
 contypes
-convarea
 conwinuserrefs
 coordnew
 COPYCOLOR
@@ -314,7 +305,6 @@ csrutil
 CSTYLE
 CSwitch
 CTerminal
-CText
 ctl
 ctlseqs
 CTRLEVENT
@@ -322,7 +312,7 @@ CTRLFREQUENCY
 CTRLKEYSHORTCUTS
 Ctrls
 CTRLVOLUME
-Ctxt
+CUAS
 CUF
 cupxy
 CURRENTFONT
@@ -486,7 +476,6 @@ DISABLEDELAYEDEXPANSION
 DISABLENOSCROLL
 DISPATCHNOTIFY
 DISPLAYATTRIBUTE
-DISPLAYATTRIBUTEPROPERTY
 DISPLAYCHANGE
 distros
 dlg
@@ -562,7 +551,6 @@ entrypoints
 ENU
 ENUMLOGFONT
 ENUMLOGFONTEX
-enumranges
 EOK
 EPres
 EQU
@@ -621,7 +609,6 @@ FINDDOWN
 FINDSTRINGEXACT
 FINDUP
 FIter
-FIXEDCONVERTED
 FIXEDFILEINFO
 Flg
 flyouts
@@ -736,9 +723,8 @@ Greyscale
 gridline
 gset
 gsl
-GTP
 guc
-guidatom
+GUIDATOM
 GValue
 GWL
 GWLP
@@ -837,7 +823,6 @@ IEnd
 IEnum
 IFACEMETHODIMP
 ification
-IGNOREEND
 IGNORELANGUAGE
 IHosted
 iid
@@ -861,7 +846,6 @@ inkscape
 INLINEPREFIX
 inproc
 Inputkeyinfo
-INPUTPROCESSORPROFILE
 Inputreadhandledata
 INSERTMODE
 INTERACTIVITYBASE
@@ -873,9 +857,7 @@ INVALIDARG
 INVALIDATERECT
 Ioctl
 ipch
-ipp
 IProperty
-IPSINK
 ipsp
 IShell
 ISwap
@@ -895,7 +877,6 @@ JOBOBJECT
 JOBOBJECTINFOCLASS
 JPN
 jsoncpp
-Jsons
 jsprovider
 jumplist
 KAttrs
@@ -925,6 +906,7 @@ KLMNOPQRSTY
 KOK
 KPRIORITY
 KVM
+kyouhaishaheiku
 langid
 LANGUAGELIST
 lasterror
@@ -987,7 +969,6 @@ lpdw
 lpelfe
 lpfn
 LPFNADDPROPSHEETPAGE
-lpl
 LPMEASUREITEMSTRUCT
 LPMINMAXINFO
 lpmsg
@@ -1063,6 +1044,7 @@ MENUITEMINFO
 MENUSELECT
 messageext
 metaproj
+Mgrs
 microsoftpublicsymbols
 midl
 mii
@@ -1117,7 +1099,6 @@ muxes
 myapplet
 mybranch
 mydir
-MYMAX
 Mypair
 Myval
 NAMELENGTH
@@ -1161,6 +1142,7 @@ NOCOPYBITS
 NODUP
 noexcepts
 NOFONT
+NOHIDDENTEXT
 NOINTEGRALHEIGHT
 NOINTERFACE
 NOLINKINFO
@@ -1184,6 +1166,7 @@ NORMALDISPLAY
 NOSCRATCH
 NOSEARCH
 noselect
+NOSELECTION
 NOSENDCHANGING
 NOSIZE
 NOSNAPSHOT
@@ -1274,6 +1257,7 @@ packageuwp
 PACKAGEVERSIONNUMBER
 PACKCOORD
 PACKVERSION
+pacp
 pagedown
 pageup
 PAINTPARAMS
@@ -1285,7 +1269,6 @@ parms
 PATCOPY
 pathcch
 PATTERNID
-pcat
 pcb
 pcch
 PCCHAR
@@ -1309,9 +1292,9 @@ PCSTR
 PCWCH
 PCWCHAR
 PCWSTR
-pda
 pdbs
 pdbstr
+pdcs
 PDPs
 pdtobj
 pdw
@@ -1341,6 +1324,7 @@ PIDLIST
 pids
 pii
 piml
+pimpl
 pinvoke
 pipename
 pipestr
@@ -1372,7 +1356,6 @@ POSXSCROLL
 POSYSCROLL
 PPEB
 ppf
-ppguid
 ppidl
 PPROC
 ppropvar
@@ -1402,7 +1385,7 @@ PROCESSINFOCLASS
 PRODEXT
 PROPERTYID
 PROPERTYKEY
-PROPERTYVAL
+propertyval
 propsheet
 PROPSHEETHEADER
 PROPSHEETPAGE
@@ -1434,6 +1417,7 @@ ptch
 ptsz
 PTYIn
 PUCHAR
+pvar
 pwch
 PWDDMCONSOLECONTEXT
 pws
@@ -1519,6 +1503,7 @@ rgn
 rgp
 rgpwsz
 rgrc
+rguid
 rgw
 RIGHTALIGN
 RIGHTBUTTON
@@ -1526,6 +1511,7 @@ riid
 RIS
 roadmap
 robomac
+rodata
 rosetta
 RRF
 rrr
@@ -1648,7 +1634,6 @@ sidebyside
 SIF
 SIGDN
 Signtool
-SINGLEFLAG
 SINGLETHREADED
 siup
 sixel
@@ -1721,6 +1706,7 @@ SYMED
 SYNCPAINT
 syscalls
 SYSCHAR
+SYSCOLOR
 SYSCOMMAND
 SYSDEADCHAR
 SYSKEYDOWN
@@ -1786,7 +1772,6 @@ TEXTMETRIC
 TEXTMETRICW
 textmode
 texttests
-TFCAT
 TFunction
 THUMBPOSITION
 THUMBTRACK
@@ -1817,7 +1802,6 @@ Tpqrst
 tracelogging
 traceviewpp
 trackbar
-TRACKCOMPOSITION
 trackpad
 transitioning
 Trd
@@ -1841,8 +1825,6 @@ TTM
 TTo
 tvpp
 tvtseq
-Txtev
-typechecked
 TYUI
 UAC
 uap
@@ -1860,10 +1842,8 @@ UIACCESS
 uiacore
 uiautomationcore
 uielem
-UIELEMENTENABLEDONLY
 UINTs
 ul
-ulcch
 uld
 uldash
 uldb
@@ -1957,7 +1937,6 @@ vstest
 VSTS
 VSTT
 vswhere
-vtapi
 vtapp
 VTE
 VTID
@@ -2020,7 +1999,6 @@ WINDOWALPHA
 windowdpiapi
 WINDOWEDGE
 windowext
-windowime
 WINDOWINFO
 windowio
 windowmetrics
@@ -2071,7 +2049,6 @@ WNDCLASSW
 Wndproc
 WNegative
 WNull
-wnwb
 workarea
 WOutside
 WOWARM
@@ -2110,7 +2087,6 @@ WTs
 WTSOFTFONT
 wtw
 wtypes
-Wubi
 WUX
 WVerify
 WWith
@@ -2137,9 +2113,7 @@ xes
 XFG
 XFile
 XFORM
-xin
-xinchaof
-xinxinchaof
+XIn
 XManifest
 XMath
 xorg
diff --git a/OpenConsole.sln b/OpenConsole.sln
index 809dd25a346..411f5ca0ed6 100644
--- a/OpenConsole.sln
+++ b/OpenConsole.sln
@@ -171,6 +171,7 @@ EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Control.Lib", "src\cascadia\TerminalControl\TerminalControlLib.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}"
 	ProjectSection(ProjectDependencies) = postProject
 		{1CF55140-EF6A-4736-A403-957E4F7430BB} = {1CF55140-EF6A-4736-A403-957E4F7430BB}
+		{2FD12FBB-1DDB-46D8-B818-1023C624CACA} = {2FD12FBB-1DDB-46D8-B818-1023C624CACA}
 		{48D21369-3D7B-4431-9967-24E81292CF63} = {48D21369-3D7B-4431-9967-24E81292CF63}
 		{8222900C-8B6C-452A-91AC-BE95DB04B95F} = {8222900C-8B6C-452A-91AC-BE95DB04B95F}
 		{AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} = {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}
@@ -412,7 +413,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TerminalStress", "src\tools
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RenderingTests", "src\tools\RenderingTests\RenderingTests.vcxproj", "{37C995E0-2349-4154-8E77-4A52C0C7F46D}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UIHelpers", "src\cascadia\UIHelpers\UIHelpers.vcxproj", "{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.UI", "src\cascadia\UIHelpers\UIHelpers.vcxproj", "{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}"
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "benchcat", "src\tools\benchcat\benchcat.vcxproj", "{2C836962-9543-4CE5-B834-D28E1F124B66}"
 EndProject
@@ -477,6 +478,7 @@ Global
 		{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.Build.0 = Debug|ARM64
 		{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x64.ActiveCfg = Debug|x64
 		{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x64.Build.0 = Debug|x64
+		{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x64.Deploy.0 = Debug|x64
 		{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x86.ActiveCfg = Debug|Win32
 		{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x86.Build.0 = Debug|Win32
 		{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
@@ -2443,7 +2445,7 @@ Global
 		{345FD5A4-B32B-4F29-BD1C-B033BD2C35CC} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
 		{4C8E6BB0-4713-4ADB-BD04-81628ECEAF20} = {81C352DB-1818-45B7-A284-18E259F1CC87}
 		{D57841D1-8294-4F2B-BB8B-D2A35738DECD} = {81C352DB-1818-45B7-A284-18E259F1CC87}
-		{2FD12FBB-1DDB-46D8-B818-1023C624CACA} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
+		{2FD12FBB-1DDB-46D8-B818-1023C624CACA} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
 		{3AE13314-1939-4DFA-9C14-38CA0834050C} = {F1995847-4AE5-479A-BBAF-382E51A63532}
 		{DCF55140-EF6A-4736-A403-957E4F7430BB} = {F1995847-4AE5-479A-BBAF-382E51A63532}
 		{1CF55140-EF6A-4736-A403-957E4F7430BB} = {F1995847-4AE5-479A-BBAF-382E51A63532}
diff --git a/doc/ORGANIZATION.md b/doc/ORGANIZATION.md
index 5f93639d099..18edbdabe58 100644
--- a/doc/ORGANIZATION.md
+++ b/doc/ORGANIZATION.md
@@ -65,9 +65,6 @@
 	* `clipboard.cpp`
 * Handles the command prompt line as you see in CMD.exe (known as the processed input line… most other shells handle this themselves with raw input and don’t use ours. This is a legacy of bad architectural design, putting stuff in conhost not in CMD)
 	* `cmdline.cpp`
-* Handles shunting IME data back and forth to the TSF library and to and from the various buffers
-	* `Conimeinfo.cpp`
-	* `Convarea.cpp`
 * Contains the global state for the entire console application
 	* `consoleInformation.cpp`
 * Stuff related to the low-level server communication over our protocol with the driver
diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp
index 4793b86dac9..aaf2dcab20b 100644
--- a/src/buffer/out/Row.cpp
+++ b/src/buffer/out/Row.cpp
@@ -914,7 +914,7 @@ const til::small_rle<TextAttribute, uint16_t, 1>& ROW::Attributes() const noexce
 
 TextAttribute ROW::GetAttrByColumn(const til::CoordType column) const
 {
-    return _attr.at(_clampedUint16(column));
+    return _attr.at(_clampedColumn(column));
 }
 
 std::vector<uint16_t> ROW::GetHyperlinks() const
@@ -1097,12 +1097,6 @@ DelimiterClass ROW::DelimiterClassAt(til::CoordType column, const std::wstring_v
     }
 }
 
-template<typename T>
-constexpr uint16_t ROW::_clampedUint16(T v) noexcept
-{
-    return static_cast<uint16_t>(clamp(v, 0, 65535));
-}
-
 template<typename T>
 constexpr uint16_t ROW::_clampedColumn(T v) const noexcept
 {
diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp
index 317c935edce..a13e6e7996a 100644
--- a/src/buffer/out/Row.hpp
+++ b/src/buffer/out/Row.hpp
@@ -230,8 +230,6 @@ class ROW final
     static constexpr uint16_t CharOffsetsTrailer = 0x8000;
     static constexpr uint16_t CharOffsetsMask = 0x7fff;
 
-    template<typename T>
-    static constexpr uint16_t _clampedUint16(T v) noexcept;
     template<typename T>
     constexpr uint16_t _clampedColumn(T v) const noexcept;
     template<typename T>
diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp
index c7b68926ef8..f250749c577 100644
--- a/src/buffer/out/textBuffer.hpp
+++ b/src/buffer/out/textBuffer.hpp
@@ -49,8 +49,6 @@ filling in the last row, and updating the screen.
 
 #pragma once
 
-#include <vector>
-
 #include "cursor.h"
 #include "Row.hpp"
 #include "TextAttribute.hpp"
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index c704b412310..a6b808b835b 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -111,9 +111,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         auto pfnScrollPositionChanged = std::bind(&ControlCore::_terminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
         _terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
 
-        auto pfnTerminalCursorPositionChanged = std::bind(&ControlCore::_terminalCursorPositionChanged, this);
-        _terminal->SetCursorPositionChangedCallback(pfnTerminalCursorPositionChanged);
-
         auto pfnTerminalTaskbarProgressChanged = std::bind(&ControlCore::_terminalTaskbarProgressChanged, this);
         _terminal->TaskbarProgressChangedCallback(pfnTerminalTaskbarProgressChanged);
 
@@ -168,10 +165,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
 
         // A few different events should be throttled, so they don't fire absolutely all the time:
-        // * _tsfTryRedrawCanvas: When the cursor position moves, we need to
-        //   inform TSF, so it can move the canvas for the composition. We
-        //   throttle this so that we're not hopping across the process boundary
-        //   every time that the cursor moves.
         // * _updatePatternLocations: When there's new output, or we scroll the
         //   viewport, we should re-check if there are any visible hyperlinks.
         //   But we don't really need to do this every single time text is
@@ -181,16 +174,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         //   We can throttle this to once every 8ms, which will get us out of
         //   the way of the main output & rendering threads.
         const auto shared = _shared.lock();
-        shared->tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
-            _dispatcher,
-            TsfRedrawInterval,
-            [weakThis = get_weak()]() {
-                if (auto core{ weakThis.get() }; !core->_IsClosing())
-                {
-                    core->CursorPositionChanged.raise(*core, nullptr);
-                }
-            });
-
         // NOTE: Calling UpdatePatternLocations from a background
         // thread is a workaround for us to hit GH#12607 less often.
         shared->updatePatternLocations = std::make_unique<til::throttled_func_trailing<>>(
@@ -232,7 +215,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         // thread. These will be recreated in _setupDispatcherAndCallbacks, when
         // we're re-attached to a new control (on a possibly new UI thread).
         const auto shared = _shared.lock();
-        shared->tsfTryRedrawCanvas.reset();
         shared->updatePatternLocations.reset();
         shared->updateScrollBar.reset();
     }
@@ -453,7 +435,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
     // - wstr: the string of characters to write to the terminal connection.
     // Return Value:
     // - <none>
-    void ControlCore::SendInput(const winrt::hstring& wstr)
+    void ControlCore::SendInput(const std::wstring_view wstr)
     {
         _sendInputToConnection(wstr);
     }
@@ -1037,7 +1019,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         const auto fontWeight = _settings->FontWeight();
         _desiredFont = { fontFace, 0, fontWeight.Weight, newSize, CP_UTF8 };
         _actualFont = { fontFace, 0, fontWeight.Weight, _desiredFont.GetEngineSize(), CP_UTF8, false };
-        _actualFontFaceName = { fontFace };
 
         _desiredFont.SetEnableBuiltinGlyphs(_builtinGlyphs);
         _desiredFont.SetEnableColorGlyphs(_colorGlyphs);
@@ -1439,13 +1420,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
             ::base::saturated_cast<float>(fontSize.height)
         };
     }
-    winrt::hstring ControlCore::FontFaceName() const noexcept
-    {
-        // This getter used to return _actualFont.GetFaceName(), however GetFaceName() returns a STL
-        // string and we need to return a WinRT string. This would require an additional allocation.
-        // This method is called 10/s by TSFInputControl at the time of writing.
-        return _actualFontFaceName;
-    }
 
     uint16_t ControlCore::FontWeight() const noexcept
     {
@@ -1616,17 +1590,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
     }
 
-    void ControlCore::_terminalCursorPositionChanged()
-    {
-        // When the buffer's cursor moves, start the throttled func to
-        // eventually dispatch a CursorPositionChanged event.
-        const auto shared = _shared.lock_shared();
-        if (shared->tsfTryRedrawCanvas)
-        {
-            shared->tsfTryRedrawCanvas->Run();
-        }
-    }
-
     void ControlCore::_terminalTaskbarProgressChanged()
     {
         TaskbarProgressChanged.raise(*this, nullptr);
@@ -2141,6 +2104,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
     }
 
+    ::Microsoft::Console::Render::Renderer* ControlCore::GetRenderer() const noexcept
+    {
+        return _renderer.get();
+    }
+
     uint64_t ControlCore::SwapChainHandle() const
     {
         // This is only ever called by TermControl::AttachContent, which occurs
diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h
index 0faffb6953e..7c73f29495f 100644
--- a/src/cascadia/TerminalControl/ControlCore.h
+++ b/src/cascadia/TerminalControl/ControlCore.h
@@ -100,6 +100,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept;
         void ColorScheme(const winrt::Microsoft::Terminal::Core::Scheme& scheme);
 
+        ::Microsoft::Console::Render::Renderer* GetRenderer() const noexcept;
         uint64_t SwapChainHandle() const;
         void AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings);
 
@@ -113,13 +114,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         winrt::Windows::Foundation::Size FontSizeInDips() const;
 
         winrt::Windows::Foundation::Size FontSize() const noexcept;
-        winrt::hstring FontFaceName() const noexcept;
         uint16_t FontWeight() const noexcept;
 
         til::color ForegroundColor() const;
         til::color BackgroundColor() const;
 
-        void SendInput(const winrt::hstring& wstr);
+        void SendInput(std::wstring_view wstr);
         void PasteText(const winrt::hstring& hstr);
         bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
         void SelectAll();
@@ -241,8 +241,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         hstring ReadEntireBuffer() const;
         Control::CommandHistoryContext CommandHistory() const;
 
-        static bool IsVintageOpacityAvailable() noexcept;
-
         void AdjustOpacity(const double opacity, const bool relative);
 
         void WindowVisibilityChanged(const bool showOrHide);
@@ -271,7 +269,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         til::typed_event<> TabColorChanged;
         til::typed_event<> BackgroundColorChanged;
         til::typed_event<IInspectable, Control::ScrollPositionChangedArgs> ScrollPositionChanged;
-        til::typed_event<> CursorPositionChanged;
         til::typed_event<> TaskbarProgressChanged;
         til::typed_event<> ConnectionStateChanged;
         til::typed_event<> HoveredHyperlinkChanged;
@@ -296,7 +293,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
     private:
         struct SharedState
         {
-            std::shared_ptr<ThrottledFuncTrailing<>> tsfTryRedrawCanvas;
             std::unique_ptr<til::throttled_func_trailing<>> updatePatternLocations;
             std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> updateScrollBar;
         };
@@ -326,7 +322,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
         FontInfoDesired _desiredFont;
         FontInfo _actualFont;
-        winrt::hstring _actualFontFaceName;
         bool _builtinGlyphs = true;
         bool _colorGlyphs = true;
         CSSLengthPercentage _cellWidth;
@@ -376,7 +371,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         void _terminalScrollPositionChanged(const int viewTop,
                                             const int viewHeight,
                                             const int bufferSize);
-        void _terminalCursorPositionChanged();
         void _terminalTaskbarProgressChanged();
         void _terminalShowWindowChanged(bool showOrHide);
         void _terminalPlayMidiNote(const int noteNumber,
diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl
index 632d2d73751..a651294f32a 100644
--- a/src/cascadia/TerminalControl/ControlCore.idl
+++ b/src/cascadia/TerminalControl/ControlCore.idl
@@ -87,7 +87,6 @@ namespace Microsoft.Terminal.Control
         UInt64 SwapChainHandle { get; };
 
         Windows.Foundation.Size FontSize { get; };
-        String FontFaceName { get; };
         UInt16 FontWeight { get; };
         Double Opacity { get; };
         Boolean UseAcrylic { get; };
@@ -171,7 +170,6 @@ namespace Microsoft.Terminal.Control
         // These events are always called from the UI thread (bugs aside)
         event Windows.Foundation.TypedEventHandler<Object, FontSizeChangedArgs> FontSizeChanged;
         event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
-        event Windows.Foundation.TypedEventHandler<Object, Object> CursorPositionChanged;
         event Windows.Foundation.TypedEventHandler<Object, Object> ConnectionStateChanged;
         event Windows.Foundation.TypedEventHandler<Object, Object> HoveredHyperlinkChanged;
         event Windows.Foundation.TypedEventHandler<Object, Object> SwapChainChanged;
diff --git a/src/cascadia/TerminalControl/TSFInputControl.cpp b/src/cascadia/TerminalControl/TSFInputControl.cpp
deleted file mode 100644
index 29ac69e4963..00000000000
--- a/src/cascadia/TerminalControl/TSFInputControl.cpp
+++ /dev/null
@@ -1,440 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "pch.h"
-#include "TSFInputControl.h"
-#include "TSFInputControl.g.cpp"
-
-using namespace winrt::Windows::Foundation;
-using namespace winrt::Windows::Graphics::Display;
-using namespace winrt::Windows::UI::Core;
-using namespace winrt::Windows::UI::Text;
-using namespace winrt::Windows::UI::Text::Core;
-using namespace winrt::Windows::UI::Xaml;
-
-namespace winrt::Microsoft::Terminal::Control::implementation
-{
-    TSFInputControl::TSFInputControl()
-    {
-        InitializeComponent();
-
-        // Create a CoreTextEditingContext for since we are acting like a custom edit control
-        auto manager = CoreTextServicesManager::GetForCurrentView();
-        _editContext = manager.CreateEditContext();
-
-        // InputPane is manually shown inside of TermControl.
-        _editContext.InputPaneDisplayPolicy(CoreTextInputPaneDisplayPolicy::Manual);
-
-        // Set the input scope to AlphanumericHalfWidth in order to facilitate those CJK input methods to open in English mode by default.
-        // AlphanumericHalfWidth scope doesn't prevent input method from switching to composition mode, it accepts any character too.
-        // Besides, Text scope turns on typing intelligence, but that doesn't work in this project.
-        _editContext.InputScope(CoreTextInputScope::AlphanumericHalfWidth);
-
-        _textRequestedRevoker = _editContext.TextRequested(winrt::auto_revoke, { this, &TSFInputControl::_textRequestedHandler });
-
-        _selectionRequestedRevoker = _editContext.SelectionRequested(winrt::auto_revoke, { this, &TSFInputControl::_selectionRequestedHandler });
-
-        _focusRemovedRevoker = _editContext.FocusRemoved(winrt::auto_revoke, { this, &TSFInputControl::_focusRemovedHandler });
-
-        _textUpdatingRevoker = _editContext.TextUpdating(winrt::auto_revoke, { this, &TSFInputControl::_textUpdatingHandler });
-
-        _selectionUpdatingRevoker = _editContext.SelectionUpdating(winrt::auto_revoke, { this, &TSFInputControl::_selectionUpdatingHandler });
-
-        _formatUpdatingRevoker = _editContext.FormatUpdating(winrt::auto_revoke, { this, &TSFInputControl::_formatUpdatingHandler });
-
-        _layoutRequestedRevoker = _editContext.LayoutRequested(winrt::auto_revoke, { this, &TSFInputControl::_layoutRequestedHandler });
-
-        _compositionStartedRevoker = _editContext.CompositionStarted(winrt::auto_revoke, { this, &TSFInputControl::_compositionStartedHandler });
-
-        _compositionCompletedRevoker = _editContext.CompositionCompleted(winrt::auto_revoke, { this, &TSFInputControl::_compositionCompletedHandler });
-    }
-
-    // Method Description:
-    // - Prepares this TSFInputControl to be removed from the UI hierarchy.
-    void TSFInputControl::Close()
-    {
-        // Explicitly disconnect the LayoutRequested handler -- it can cause problems during application teardown.
-        // See GH#4159 for more info.
-        // Also disconnect compositionCompleted and textUpdating explicitly. It seems to occasionally cause problems if
-        // a composition is active during application teardown.
-        _layoutRequestedRevoker.revoke();
-        _compositionCompletedRevoker.revoke();
-        _textUpdatingRevoker.revoke();
-    }
-
-    // Method Description:
-    // - NotifyFocusEnter handler for notifying CoreEditTextContext of focus enter
-    //   when TerminalControl receives focus.
-    // Arguments:
-    // - <none>
-    // Return Value:
-    // - <none>
-    void TSFInputControl::NotifyFocusEnter()
-    {
-        _editContext.NotifyFocusEnter();
-        _focused = true;
-    }
-
-    // Method Description:
-    // - NotifyFocusEnter handler for notifying CoreEditTextContext of focus leaving.
-    //   when TerminalControl no longer has focus.
-    // Arguments:
-    // - <none>
-    // Return Value:
-    // - <none>
-    void TSFInputControl::NotifyFocusLeave()
-    {
-        _editContext.NotifyFocusLeave();
-        _focused = false;
-    }
-
-    // Method Description:
-    // - Clears the input buffer and tells the text server to clear their buffer as well.
-    //   Also clears the TextBlock and sets the active text starting point to 0.
-    // Arguments:
-    // - <none>
-    // Return Value:
-    // - <none>
-    void TSFInputControl::ClearBuffer()
-    {
-        if (!_inputBuffer.empty())
-        {
-            _inputBuffer.clear();
-            _selection = {};
-            _activeTextStart = 0;
-            _editContext.NotifyTextChanged({ 0, INT32_MAX }, 0, _selection);
-            TextBlock().Text({});
-        }
-    }
-
-    // Method Description:
-    // - Redraw the canvas if certain dimensions have changed since the last
-    //   redraw. This includes the Terminal cursor position, the Canvas width, and the TextBlock height.
-    // Arguments:
-    // - <none>
-    // Return Value:
-    // - <none>
-    void TSFInputControl::TryRedrawCanvas()
-    try
-    {
-        if (!_focused || !Canvas())
-        {
-            return;
-        }
-
-        // Get the cursor position in text buffer position
-        auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
-        CurrentCursorPosition.raise(*this, *cursorArgs);
-        const til::point cursorPos{ til::math::flooring, cursorArgs->CurrentPosition() };
-
-        const auto actualCanvasWidth{ Canvas().ActualWidth() };
-        const auto actualTextBlockHeight{ TextBlock().ActualHeight() };
-        const auto actualWindowBounds{ CoreWindow::GetForCurrentThread().Bounds() };
-
-        if (_currentTerminalCursorPos == cursorPos &&
-            _currentCanvasWidth == actualCanvasWidth &&
-            _currentTextBlockHeight == actualTextBlockHeight &&
-            _currentWindowBounds == actualWindowBounds)
-        {
-            return;
-        }
-
-        _currentTerminalCursorPos = cursorPos;
-        _currentCanvasWidth = actualCanvasWidth;
-        _currentTextBlockHeight = actualTextBlockHeight;
-        _currentWindowBounds = actualWindowBounds;
-
-        _RedrawCanvas();
-    }
-    CATCH_LOG()
-
-    // Method Description:
-    // - Redraw the Canvas and update the current Text Bounds and Control Bounds for
-    //   the CoreTextEditContext.
-    // Arguments:
-    // - <none>
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_RedrawCanvas()
-    {
-        // Get Font Info as we use this is the pixel size for characters in the display
-        auto fontArgs = winrt::make_self<FontInfoEventArgs>();
-        CurrentFontInfo.raise(*this, *fontArgs);
-
-        const til::size fontSize{ til::math::flooring, fontArgs->FontSize() };
-
-        // Convert text buffer cursor position to client coordinate position
-        // within the window. This point is in _pixels_
-        const auto clientCursorPos{ _currentTerminalCursorPos * fontSize };
-
-        // Get scale factor for view
-        const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
-
-        const til::point clientCursorInDips{ til::math::flooring, clientCursorPos.x / scaleFactor, clientCursorPos.y / scaleFactor };
-
-        // Position our TextBlock at the cursor position
-        Canvas().SetLeft(TextBlock(), clientCursorInDips.x);
-        Canvas().SetTop(TextBlock(), clientCursorInDips.y);
-
-        // calculate FontSize in pixels from Points
-        const double fontSizePx = (fontSize.height * 72) / USER_DEFAULT_SCREEN_DPI;
-        const auto unscaledFontSizePx = fontSizePx / scaleFactor;
-
-        // Make sure to unscale the font size to correct for DPI! XAML needs
-        // things in DIPs, and the fontSize is in pixels.
-        TextBlock().FontSize(unscaledFontSizePx);
-        TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
-        TextBlock().FontWeight(fontArgs->FontWeight());
-
-        // TextBlock's actual dimensions right after initialization is 0w x 0h. So,
-        // if an IME is displayed before TextBlock has text (like showing the emoji picker
-        // using Win+.), it'll be placed higher than intended.
-        TextBlock().MinWidth(unscaledFontSizePx);
-        TextBlock().MinHeight(unscaledFontSizePx);
-        _currentTextBlockHeight = std::max(unscaledFontSizePx, _currentTextBlockHeight);
-
-        const auto widthToTerminalEnd = _currentCanvasWidth - clientCursorInDips.x;
-        // Make sure that we're setting the MaxWidth to a positive number - a
-        // negative number here will crash us in mysterious ways with a useless
-        // stack trace
-        const auto newMaxWidth = std::max<double>(0.0, widthToTerminalEnd);
-        TextBlock().MaxWidth(newMaxWidth);
-
-        // Get window in screen coordinates, this is the entire window including
-        // tabs. THIS IS IN DIPs
-        const til::point windowOrigin{ til::math::flooring, _currentWindowBounds.X, _currentWindowBounds.Y };
-
-        // Get the offset (margin + tabs, etc..) of the control within the window
-        const til::point controlOrigin{ til::math::flooring,
-                                        this->TransformToVisual(nullptr).TransformPoint(Point(0, 0)) };
-
-        // The controlAbsoluteOrigin is the origin of the control relative to
-        // the origin of the displays. THIS IS IN DIPs
-        const auto controlAbsoluteOrigin{ windowOrigin + controlOrigin };
-
-        // Convert the control origin to pixels
-        const auto scaledFrameOrigin = til::point{ til::math::flooring, controlAbsoluteOrigin.x * scaleFactor, controlAbsoluteOrigin.y * scaleFactor };
-
-        // Get the location of the cursor in the display, in pixels.
-        auto screenCursorPos{ scaledFrameOrigin + clientCursorPos };
-
-        // GH #5007 - make sure to account for wrapping the IME composition at
-        // the right side of the viewport.
-        const auto textBlockHeight = ::base::ClampMul(_currentTextBlockHeight, scaleFactor);
-
-        // Get the bounds of the composition text, in pixels.
-        const til::rect textBounds{ til::point{ screenCursorPos.x, screenCursorPos.y },
-                                    til::size{ 0, textBlockHeight } };
-
-        _currentTextBounds = textBounds.to_winrt_rect();
-
-        _currentControlBounds = Rect(static_cast<float>(screenCursorPos.x),
-                                     static_cast<float>(screenCursorPos.y),
-                                     0.0f,
-                                     static_cast<float>(fontSize.height));
-    }
-
-    // Method Description:
-    // - Handler for LayoutRequested event by CoreEditContext responsible
-    //   for returning the current position the IME should be placed
-    //   in screen coordinates on the screen.  TSFInputControls internal
-    //   XAML controls (TextBlock/Canvas) are also positioned and updated.
-    //   NOTE: documentation says application should handle this event
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request.
-    // - args: CoreTextLayoutRequestedEventArgs to be updated with position information.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_layoutRequestedHandler(CoreTextEditContext sender, const CoreTextLayoutRequestedEventArgs& args)
-    {
-        auto request = args.Request();
-
-        TryRedrawCanvas();
-
-        // Set the text block bounds
-        request.LayoutBounds().TextBounds(_currentTextBounds);
-
-        // Set the control bounds of the whole control
-        request.LayoutBounds().ControlBounds(_currentControlBounds);
-    }
-
-    // Method Description:
-    // - Handler for CompositionStarted event by CoreEditContext responsible
-    //   for making internal TSFInputControl controls visible.
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request. Not used in method.
-    // - args: CoreTextCompositionStartedEventArgs. Not used in method.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_compositionStartedHandler(CoreTextEditContext sender, const CoreTextCompositionStartedEventArgs& /*args*/)
-    {
-        _inComposition = true;
-    }
-
-    // Method Description:
-    // - Handler for CompositionCompleted event by CoreEditContext responsible
-    //   for making internal TSFInputControl controls visible.
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request. Not used in method.
-    // - args: CoreTextCompositionCompletedEventArgs. Not used in method.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_compositionCompletedHandler(CoreTextEditContext sender, const CoreTextCompositionCompletedEventArgs& /*args*/)
-    {
-        _inComposition = false;
-        _SendAndClearText();
-    }
-
-    // Method Description:
-    // - Handler for FocusRemoved event by CoreEditContext responsible
-    //   for removing focus for the TSFInputControl control accordingly
-    //   when focus was forcibly removed from text input control.
-    //   NOTE: Documentation says application should handle this event
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request. Not used in method.
-    // - object: CoreTextCompositionStartedEventArgs. Not used in method.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_focusRemovedHandler(CoreTextEditContext sender, const winrt::Windows::Foundation::IInspectable& /*object*/)
-    {
-    }
-
-    // Method Description:
-    // - Handler for TextRequested event by CoreEditContext responsible
-    //   for returning the range of text requested.
-    //   NOTE: Documentation says application should handle this event
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request. Not used in method.
-    // - args: CoreTextTextRequestedEventArgs to be updated with requested range text.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_textRequestedHandler(CoreTextEditContext sender, const CoreTextTextRequestedEventArgs& args)
-    {
-        try
-        {
-            const auto range = args.Request().Range();
-            const auto text = _inputBuffer.substr(
-                range.StartCaretPosition,
-                range.EndCaretPosition - range.StartCaretPosition);
-            args.Request().Text(text);
-        }
-        CATCH_LOG();
-    }
-
-    // Method Description:
-    // - Handler for SelectionRequested event by CoreEditContext responsible
-    //   for returning the currently selected text.
-    //   TSFInputControl currently doesn't allow selection, so nothing happens.
-    //   NOTE: Documentation says application should handle this event
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request. Not used in method.
-    // - args: CoreTextSelectionRequestedEventArgs for providing data for the SelectionRequested event. Not used in method.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_selectionRequestedHandler(CoreTextEditContext sender, const CoreTextSelectionRequestedEventArgs& args)
-    {
-        args.Request().Selection(_selection);
-    }
-
-    // Method Description:
-    // - Handler for SelectionUpdating event by CoreEditContext responsible
-    //   for handling modifications to the range of text currently selected.
-    //   TSFInputControl doesn't currently allow selection, so nothing happens.
-    //   NOTE: Documentation says application should set its selection range accordingly
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request. Not used in method.
-    // - args: CoreTextSelectionUpdatingEventArgs for providing data for the SelectionUpdating event. Not used in method.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_selectionUpdatingHandler(CoreTextEditContext sender, const CoreTextSelectionUpdatingEventArgs& args)
-    {
-        _selection = args.Selection();
-        args.Result(CoreTextSelectionUpdatingResult::Succeeded);
-    }
-
-    // Method Description:
-    // - Handler for TextUpdating event by CoreEditContext responsible
-    //   for handling text updates.
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request. Not used in method.
-    // - args: CoreTextTextUpdatingEventArgs contains new text to update buffer with.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_textUpdatingHandler(CoreTextEditContext sender, const CoreTextTextUpdatingEventArgs& args)
-    {
-        try
-        {
-            const auto incomingText = args.Text();
-            const auto range = args.Range();
-
-            _inputBuffer = _inputBuffer.replace(
-                range.StartCaretPosition,
-                range.EndCaretPosition - range.StartCaretPosition,
-                incomingText);
-            _selection = args.NewSelection();
-            // GH#5054: Pressing backspace might move the caret before the _activeTextStart.
-            _activeTextStart = std::min(_activeTextStart, _inputBuffer.size());
-
-            // Emojis/Kaomojis/Symbols chosen through the IME without starting composition
-            // will be sent straight through to the terminal.
-            if (!_inComposition)
-            {
-                _SendAndClearText();
-            }
-            else
-            {
-                Canvas().Visibility(Visibility::Visible);
-                const auto text = _inputBuffer.substr(_activeTextStart);
-                TextBlock().Text(text);
-            }
-
-            // Notify the TSF that the update succeeded
-            args.Result(CoreTextTextUpdatingResult::Succeeded);
-        }
-        catch (...)
-        {
-            LOG_CAUGHT_EXCEPTION();
-
-            // indicate updating failed.
-            args.Result(CoreTextTextUpdatingResult::Failed);
-        }
-    }
-
-    void TSFInputControl::_SendAndClearText()
-    {
-        const auto text = _inputBuffer.substr(_activeTextStart);
-        if (text.empty())
-        {
-            return;
-        }
-
-        CompositionCompleted.raise(text);
-
-        _activeTextStart = _inputBuffer.size();
-
-        TextBlock().Text({});
-
-        // After we reset the TextBlock to empty string, we want to make sure
-        // ActualHeight reflects the respective height. It seems that ActualHeight
-        // isn't updated until there's new text in the TextBlock, so the next time a user
-        // invokes "Win+." for the emoji picker IME, it would end up
-        // using the pre-reset TextBlock().ActualHeight().
-        TextBlock().UpdateLayout();
-
-        // hide the controls until text input starts again
-        Canvas().Visibility(Visibility::Collapsed);
-    }
-
-    // Method Description:
-    // - Handler for FormatUpdating event by CoreEditContext responsible
-    //   for handling different format updates for a particular range of text.
-    //   TSFInputControl doesn't do anything with this event.
-    // Arguments:
-    // - sender: CoreTextEditContext sending the request. Not used in method.
-    // - args: CoreTextFormatUpdatingEventArgs Provides data for the FormatUpdating event. Not used in method.
-    // Return Value:
-    // - <none>
-    void TSFInputControl::_formatUpdatingHandler(CoreTextEditContext sender, const CoreTextFormatUpdatingEventArgs& /*args*/)
-    {
-    }
-}
diff --git a/src/cascadia/TerminalControl/TSFInputControl.h b/src/cascadia/TerminalControl/TSFInputControl.h
deleted file mode 100644
index 95a79b96aa8..00000000000
--- a/src/cascadia/TerminalControl/TSFInputControl.h
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#pragma once
-#include "TSFInputControl.g.h"
-#include "CursorPositionEventArgs.g.h"
-#include "FontInfoEventArgs.g.h"
-
-namespace winrt::Microsoft::Terminal::Control::implementation
-{
-    struct CursorPositionEventArgs :
-        public CursorPositionEventArgsT<CursorPositionEventArgs>
-    {
-    public:
-        CursorPositionEventArgs() = default;
-
-        WINRT_PROPERTY(Windows::Foundation::Point, CurrentPosition);
-    };
-
-    struct FontInfoEventArgs :
-        public FontInfoEventArgsT<FontInfoEventArgs>
-    {
-    public:
-        FontInfoEventArgs() = default;
-
-        WINRT_PROPERTY(Windows::Foundation::Size, FontSize);
-
-        WINRT_PROPERTY(winrt::hstring, FontFace);
-
-        WINRT_PROPERTY(Windows::UI::Text::FontWeight, FontWeight);
-    };
-
-    struct TSFInputControl : TSFInputControlT<TSFInputControl>
-    {
-    public:
-        TSFInputControl();
-
-        void NotifyFocusEnter();
-        void NotifyFocusLeave();
-        void ClearBuffer();
-        void TryRedrawCanvas();
-
-        void Close();
-
-        // -------------------------------- WinRT Events ---------------------------------
-        til::typed_event<Control::TSFInputControl, Control::CursorPositionEventArgs> CurrentCursorPosition;
-        til::typed_event<Control::TSFInputControl, Control::FontInfoEventArgs> CurrentFontInfo;
-        til::event<Control::CompositionCompletedEventArgs> CompositionCompleted;
-
-    private:
-        void _layoutRequestedHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::UI::Text::Core::CoreTextLayoutRequestedEventArgs& args);
-        void _compositionStartedHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::UI::Text::Core::CoreTextCompositionStartedEventArgs& args);
-        void _compositionCompletedHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::UI::Text::Core::CoreTextCompositionCompletedEventArgs& args);
-        void _focusRemovedHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::Foundation::IInspectable& object);
-        void _selectionRequestedHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::UI::Text::Core::CoreTextSelectionRequestedEventArgs& args);
-        void _textRequestedHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::UI::Text::Core::CoreTextTextRequestedEventArgs& args);
-        void _selectionUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::UI::Text::Core::CoreTextSelectionUpdatingEventArgs& args);
-        void _textUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::UI::Text::Core::CoreTextTextUpdatingEventArgs& args);
-        void _formatUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, const winrt::Windows::UI::Text::Core::CoreTextFormatUpdatingEventArgs& args);
-
-        void _SendAndClearText();
-        void _RedrawCanvas();
-
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::TextRequested_revoker _textRequestedRevoker;
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionRequested_revoker _selectionRequestedRevoker;
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::FocusRemoved_revoker _focusRemovedRevoker;
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::TextUpdating_revoker _textUpdatingRevoker;
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionUpdating_revoker _selectionUpdatingRevoker;
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::FormatUpdating_revoker _formatUpdatingRevoker;
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::LayoutRequested_revoker _layoutRequestedRevoker;
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionStarted_revoker _compositionStartedRevoker;
-        winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionCompleted_revoker _compositionCompletedRevoker;
-
-        Windows::UI::Text::Core::CoreTextEditContext _editContext{ nullptr };
-        std::wstring _inputBuffer;
-        winrt::Windows::UI::Text::Core::CoreTextRange _selection{};
-        size_t _activeTextStart = 0;
-        bool _inComposition = false;
-        bool _focused = false;
-
-        til::point _currentTerminalCursorPos{};
-        double _currentCanvasWidth = 0.0;
-        double _currentTextBlockHeight = 0.0;
-        winrt::Windows::Foundation::Rect _currentControlBounds{};
-        winrt::Windows::Foundation::Rect _currentTextBounds{};
-        winrt::Windows::Foundation::Rect _currentWindowBounds{};
-    };
-}
-namespace winrt::Microsoft::Terminal::Control::factory_implementation
-{
-    BASIC_FACTORY(TSFInputControl);
-}
diff --git a/src/cascadia/TerminalControl/TSFInputControl.idl b/src/cascadia/TerminalControl/TSFInputControl.idl
deleted file mode 100644
index 6a6f6814f2a..00000000000
--- a/src/cascadia/TerminalControl/TSFInputControl.idl
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-namespace Microsoft.Terminal.Control
-{
-    delegate void CompositionCompletedEventArgs(String text);
-
-    runtimeclass CursorPositionEventArgs
-    {
-        Windows.Foundation.Point CurrentPosition { get; set; };
-    }
-
-    runtimeclass FontInfoEventArgs
-    {
-        String FontFace { get; set; };
-        Windows.Foundation.Size FontSize { get; set; };
-        Windows.UI.Text.FontWeight FontWeight { get; set; };
-    }
-
-    [default_interface]
-    runtimeclass TSFInputControl : Windows.UI.Xaml.Controls.UserControl
-    {
-        TSFInputControl();
-
-        event CompositionCompletedEventArgs CompositionCompleted;
-        event Windows.Foundation.TypedEventHandler<TSFInputControl, CursorPositionEventArgs> CurrentCursorPosition;
-        event Windows.Foundation.TypedEventHandler<TSFInputControl, FontInfoEventArgs> CurrentFontInfo;
-
-        void NotifyFocusEnter();
-        void NotifyFocusLeave();
-        void ClearBuffer();
-        void TryRedrawCanvas();
-
-        void Close();
-    }
-}
diff --git a/src/cascadia/TerminalControl/TSFInputControl.xaml b/src/cascadia/TerminalControl/TSFInputControl.xaml
deleted file mode 100644
index bc7fca22bfe..00000000000
--- a/src/cascadia/TerminalControl/TSFInputControl.xaml
+++ /dev/null
@@ -1,17 +0,0 @@
-<UserControl x:Class="Microsoft.Terminal.Control.TSFInputControl"
-             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             d:DesignHeight="768"
-             d:DesignWidth="1024"
-             mc:Ignorable="d">
-
-    <Canvas x:Name="Canvas"
-            Visibility="Collapsed">
-        <TextBlock x:Name="TextBlock"
-                   IsTextSelectionEnabled="false"
-                   TextDecorations="Underline"
-                   TextWrapping="Wrap" />
-    </Canvas>
-</UserControl>
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index 7c4a5b47630..c16153b6e48 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -5,10 +5,10 @@
 #include "TermControl.h"
 
 #include <LibraryResources.h>
-#include <utils.hpp>
 
 #include "TermControlAutomationPeer.h"
 #include "../../renderer/atlas/AtlasEngine.h"
+#include "../../tsf/Handle.h"
 
 #include "TermControl.g.cpp"
 
@@ -41,11 +41,130 @@ constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(
 constexpr const auto TerminalWarningBellInterval = std::chrono::milliseconds(1000);
 
 DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::Control::CopyFormat);
-
 DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::Control::MouseButtonState);
 
+static Microsoft::Console::TSF::Handle& GetTSFHandle()
+{
+    // https://en.cppreference.com/w/cpp/language/storage_duration
+    // > Variables declared at block scope with the specifier static or thread_local
+    // > [...] are initialized the first time control passes through their declaration
+    // --> Lazy, per-(window-)thread initialization of the TSF handle
+    thread_local auto s_tsf = ::Microsoft::Console::TSF::Handle::Create();
+    return s_tsf;
+}
+
 namespace winrt::Microsoft::Terminal::Control::implementation
 {
+    TsfDataProvider::TsfDataProvider(TermControl* termControl) noexcept :
+        _termControl{ termControl }
+    {
+    }
+
+    STDMETHODIMP TsfDataProvider::QueryInterface(REFIID, void**) noexcept
+    {
+        return E_NOTIMPL;
+    }
+
+    ULONG STDMETHODCALLTYPE TsfDataProvider::AddRef() noexcept
+    {
+        return 1;
+    }
+
+    ULONG STDMETHODCALLTYPE TsfDataProvider::Release() noexcept
+    {
+        return 1;
+    }
+
+    HWND TsfDataProvider::GetHwnd()
+    {
+        if (!_hwnd)
+        {
+            // WinUI's WinRT based TSF runs in its own window "Windows.UI.Input.InputSite.WindowClass" (..."great")
+            // and in order for us to snatch the focus away from that one we need to find its HWND.
+            // The way we do it here is by finding the existing, active TSF context and getting the HWND from it.
+            _hwnd = GetTSFHandle().FindWindowOfActiveTSF();
+            if (!_hwnd)
+            {
+                _hwnd = reinterpret_cast<HWND>(_termControl->OwningHwnd());
+            }
+        }
+        return _hwnd;
+    }
+
+    RECT TsfDataProvider::GetViewport()
+    {
+        const auto scaleFactor = static_cast<float>(DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel());
+        const auto globalOrigin = CoreWindow::GetForCurrentThread().Bounds();
+        const auto localOrigin = _termControl->TransformToVisual(nullptr).TransformPoint({});
+        const auto size = _termControl->ActualSize();
+
+        const auto left = globalOrigin.X + localOrigin.X;
+        const auto top = globalOrigin.Y + localOrigin.Y;
+        const auto right = left + size.x;
+        const auto bottom = top + size.y;
+
+        return {
+            lroundf(left * scaleFactor),
+            lroundf(top * scaleFactor),
+            lroundf(right * scaleFactor),
+            lroundf(bottom * scaleFactor),
+        };
+    }
+
+    RECT TsfDataProvider::GetCursorPosition()
+    {
+        const auto core = _getCore();
+        if (!core)
+        {
+            return {};
+        }
+
+        const auto scaleFactor = static_cast<float>(DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel());
+        const auto globalOrigin = CoreWindow::GetForCurrentThread().Bounds();
+        const auto localOrigin = _termControl->TransformToVisual(nullptr).TransformPoint({});
+        const auto padding = _termControl->GetPadding();
+        const auto cursorPosition = core->CursorPosition();
+        const auto fontSize = core->FontSize();
+
+        // fontSize is not in DIPs, so we need to first multiply by scaleFactor and then do the rest.
+        const auto left = (globalOrigin.X + localOrigin.X + static_cast<float>(padding.Left)) * scaleFactor + cursorPosition.X * fontSize.Width;
+        const auto top = (globalOrigin.Y + localOrigin.Y + static_cast<float>(padding.Top)) * scaleFactor + cursorPosition.Y * fontSize.Height;
+        const auto right = left + fontSize.Width;
+        const auto bottom = top + fontSize.Height;
+
+        return {
+            lroundf(left),
+            lroundf(top),
+            lroundf(right),
+            lroundf(bottom),
+        };
+    }
+
+    void TsfDataProvider::HandleOutput(std::wstring_view text)
+    {
+        const auto core = _getCore();
+        if (!core)
+        {
+            return;
+        }
+        core->SendInput(text);
+    }
+
+    ::Microsoft::Console::Render::Renderer* TsfDataProvider::GetRenderer()
+    {
+        const auto core = _getCore();
+        if (!core)
+        {
+            return nullptr;
+        }
+        return core->GetRenderer();
+    }
+
+    ControlCore* TsfDataProvider::_getCore() const noexcept
+    {
+        return get_self<ControlCore>(_termControl->_core);
+    }
+
     TermControl::TermControl(IControlSettings settings,
                              Control::IControlAppearance unfocusedAppearance,
                              TerminalConnection::ITerminalConnection connection) :
@@ -154,7 +273,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         // attached content before we set up the throttled func, and that'll A/V
         _revokers.coreScrollPositionChanged = _core.ScrollPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_ScrollPositionChanged });
         _revokers.WarningBell = _core.WarningBell(winrt::auto_revoke, { get_weak(), &TermControl::_coreWarningBell });
-        _revokers.CursorPositionChanged = _core.CursorPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_CursorPositionChanged });
 
         static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
         _autoScrollTimer.Interval(AutoScrollUpdateInterval);
@@ -590,18 +708,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         SelectionStartMarker().Fill(cursorColorBrush);
         SelectionEndMarker().Fill(cursorColorBrush);
 
-        // Set TSF Foreground
-        Media::SolidColorBrush foregroundBrush{};
-        if (_core.Settings().UseBackgroundImageForWindow())
-        {
-            foregroundBrush.Color(Windows::UI::Colors::Transparent());
-        }
-        else
-        {
-            foregroundBrush.Color(static_cast<til::color>(newAppearance.DefaultForeground()));
-        }
-        TSFInputControl().Foreground(foregroundBrush);
-
         _core.ApplyAppearance(_focused);
     }
 
@@ -654,8 +760,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         const auto newMargin = ParseThicknessFromPadding(settings.Padding());
         SwapChainPanel().Margin(newMargin);
 
-        TSFInputControl().Margin(newMargin);
-
         // Apply settings for scrollbar
         if (settings.ScrollState() == ScrollbarState::Hidden)
         {
@@ -1170,19 +1274,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
             return;
         }
 
-        // GH#11479: TSF wants to be notified of any character input via ICoreTextEditContext::NotifyTextChanged().
-        // TSF is built and tested around the idea that you inform it of any text changes that happen
-        // when it doesn't currently compose characters. For instance writing "xin chaof" with the
-        // Vietnamese IME should produce "xin chào". After writing "xin" it'll emit a composition
-        // completion event and we'll write "xin" to the shell. It now has no input focus and won't know
-        // about the whitespace. If you then write "chaof", it'll emit another composition completion
-        // event for "xinchaof" and the resulting output in the shell will finally read "xinxinchaof".
-        // A composition completion event technically doesn't mean that the completed text is now
-        // immutable after all. We could (and probably should) inform TSF of any input changes,
-        // but we technically aren't a text input field. The immediate solution was
-        // to simply force TSF to clear its text whenever we have input focus.
-        TSFInputControl().ClearBuffer();
-
         HidePointerCursor.raise(*this, nullptr);
 
         const auto ch = e.Character();
@@ -1970,12 +2061,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         {
             return;
         }
-
-        if (TSFInputControl() != nullptr)
-        {
-            TSFInputControl().NotifyFocusEnter();
-        }
-
         if (_cursorTimer)
         {
             // When the terminal focuses, show the cursor immediately
@@ -1996,6 +2081,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         {
             UpdateAppearance(_core.FocusedAppearance());
         }
+
+        GetTSFHandle().Focus(&_tsfDataProvider);
     }
 
     // Method Description:
@@ -2021,11 +2108,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
             _interactivity.LostFocus();
         }
 
-        if (TSFInputControl() != nullptr)
-        {
-            TSFInputControl().NotifyFocusLeave();
-        }
-
         if (_cursorTimer && !_displayCursorWhileBlurred())
         {
             _cursorTimer.Stop();
@@ -2043,6 +2125,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         {
             UpdateAppearance(_core.UnfocusedAppearance());
         }
+
+        GetTSFHandle().Unfocus(&_tsfDataProvider);
     }
 
     // Method Description:
@@ -2169,27 +2253,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
     }
 
-    // Method Description:
-    // - Tells TSFInputControl to redraw the Canvas/TextBlock so it'll update
-    //   to be where the current cursor position is.
-    // Arguments:
-    // - N/A
-    winrt::fire_and_forget TermControl::_CursorPositionChanged(const IInspectable& /*sender*/,
-                                                               const IInspectable& /*args*/)
-    {
-        // Prior to GH#10187, this fired a trailing throttled func to update the
-        // TSF canvas only every 100ms. Now, the throttling occurs on the
-        // ControlCore side. If we're told to update the cursor position, we can
-        // just go ahead and do it.
-        // This can come in off the COM thread - hop back to the UI thread.
-        auto weakThis{ get_weak() };
-        co_await wil::resume_foreground(Dispatcher());
-        if (auto control{ weakThis.get() }; !control->_IsClosing())
-        {
-            control->TSFInputControl().TryRedrawCanvas();
-        }
-    }
-
     hstring TermControl::Title()
     {
         return _core.Title();
@@ -2304,9 +2367,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
             _revokers = {};
 
-            // Disconnect the TSF input control so it doesn't receive EditContext events.
-            TSFInputControl().Close();
-
             // At the time of writing, closing the last tab of a window inexplicably
             // does not lead to the destruction of the remaining TermControl instance(s).
             // On Win10 we don't destroy window threads due to bugs in DesktopWindowXamlSource.
@@ -2317,6 +2377,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
             _cursorTimer.Stop();
             _blinkTimer.Stop();
 
+            // This is absolutely crucial, as the TSF code tries to hold a strong reference to _tsfDataProvider,
+            // but right now _tsfDataProvider implements IUnknown as a no-op. This ensures that TSF stops referencing us.
+            // ~TermControl() calls Close() so this should be safe.
+            GetTSFHandle().Unfocus(&_tsfDataProvider);
+
             if (!_detached)
             {
                 _interactivity.Close();
@@ -2742,62 +2807,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         return relativeToMarginInPixels;
     }
 
-    // Method Description:
-    // - Composition Completion handler for the TSFInputControl that
-    //   handles writing text out to TerminalConnection
-    // Arguments:
-    // - text: the text to write to TerminalConnection
-    // Return Value:
-    // - <none>
-    void TermControl::_CompositionCompleted(winrt::hstring text)
-    {
-        if (_IsClosing())
-        {
-            return;
-        }
-
-        // SendInput will take care of broadcasting for us.
-        SendInput(text);
-    }
-
-    // Method Description:
-    // - CurrentCursorPosition handler for the TSFInputControl that
-    //   handles returning current cursor position.
-    // Arguments:
-    // - eventArgs: event for storing the current cursor position
-    // Return Value:
-    // - <none>
-    void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/,
-                                                    const CursorPositionEventArgs& eventArgs)
-    {
-        if (!_initializedTerminal)
-        {
-            // fake it
-            eventArgs.CurrentPosition({ 0, 0 });
-            return;
-        }
-
-        const auto cursorPos = _core.CursorPosition();
-        eventArgs.CurrentPosition({ static_cast<float>(cursorPos.X), static_cast<float>(cursorPos.Y) });
-    }
-
-    // Method Description:
-    // - FontInfo handler for the TSFInputControl that
-    //   handles returning current font information
-    // Arguments:
-    // - eventArgs: event for storing the current font information
-    // Return Value:
-    // - <none>
-    void TermControl::_FontInfoHandler(const IInspectable& /*sender*/,
-                                       const FontInfoEventArgs& eventArgs)
-    {
-        eventArgs.FontSize(CharacterDimensions());
-        eventArgs.FontFace(_core.FontFaceName());
-        ::winrt::Windows::UI::Text::FontWeight weight;
-        weight.Weight = _core.FontWeight();
-        eventArgs.FontWeight(weight);
-    }
-
     // Method Description:
     // - Calculates speed of single axis of auto scrolling. It has to allow for both
     //      fast and precise selection.
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index 24142dc8a7b..55ec88c01db 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -3,14 +3,12 @@
 
 #pragma once
 
+#include "SearchBoxControl.h"
 #include "TermControl.g.h"
-#include "XamlLights.h"
-#include "EventArgs.h"
-#include "../../renderer/base/Renderer.hpp"
-#include "../../renderer/uia/UiaRenderer.hpp"
+#include "../../buffer/out/search.h"
 #include "../../cascadia/TerminalCore/Terminal.hpp"
-#include "../buffer/out/search.h"
-#include "SearchBoxControl.h"
+#include "../../renderer/uia/UiaRenderer.hpp"
+#include "../../tsf/Handle.h"
 
 #include "ControlInteractivity.h"
 #include "ControlSettings.h"
@@ -22,6 +20,30 @@ namespace Microsoft::Console::VirtualTerminal
 
 namespace winrt::Microsoft::Terminal::Control::implementation
 {
+    struct TermControl;
+
+    struct TsfDataProvider : ::Microsoft::Console::TSF::IDataProvider
+    {
+        explicit TsfDataProvider(TermControl* termControl) noexcept;
+        virtual ~TsfDataProvider() = default;
+
+        STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj) noexcept override;
+        ULONG STDMETHODCALLTYPE AddRef() noexcept override;
+        ULONG STDMETHODCALLTYPE Release() noexcept override;
+
+        HWND GetHwnd() override;
+        RECT GetViewport() override;
+        RECT GetCursorPosition() override;
+        void HandleOutput(std::wstring_view text) override;
+        ::Microsoft::Console::Render::Renderer* GetRenderer() override;
+
+    private:
+        ControlCore* _getCore() const noexcept;
+
+        TermControl* _termControl = nullptr;
+        HWND _hwnd = nullptr;
+    };
+
     struct TermControl : TermControlT<TermControl>
     {
         TermControl(Control::ControlInteractivity content);
@@ -201,6 +223,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
     private:
         friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
+        friend struct TsfDataProvider;
 
         // NOTE: _uiaEngine must be ordered before _core.
         //
@@ -213,7 +236,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         Control::TermControlAutomationPeer _automationPeer{ nullptr };
         Control::ControlInteractivity _interactivity{ nullptr };
         Control::ControlCore _core{ nullptr };
-
+        TsfDataProvider _tsfDataProvider{ this };
         winrt::com_ptr<SearchBoxControl> _searchBox;
 
         bool _closing{ false };
@@ -328,7 +351,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         void _TerminalTabColorChanged(const std::optional<til::color> color);
 
         void _ScrollPositionChanged(const IInspectable& sender, const Control::ScrollPositionChangedArgs& args);
-        winrt::fire_and_forget _CursorPositionChanged(const IInspectable& sender, const IInspectable& args);
 
         bool _CapturePointer(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
         bool _ReleasePointerCapture(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
@@ -353,11 +375,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         void _SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive);
         void _CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
 
-        // TSFInputControl Handlers
-        void _CompositionCompleted(winrt::hstring text);
-        void _CurrentCursorPositionHandler(const IInspectable& sender, const CursorPositionEventArgs& eventArgs);
-        void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs);
-
         void _hoveredHyperlinkChanged(const IInspectable& sender, const IInspectable& args);
         winrt::fire_and_forget _updateSelectionMarkers(IInspectable sender, Control::UpdateSelectionMarkersEventArgs args);
 
@@ -387,7 +404,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         {
             Control::ControlCore::ScrollPositionChanged_revoker coreScrollPositionChanged;
             Control::ControlCore::WarningBell_revoker WarningBell;
-            Control::ControlCore::CursorPositionChanged_revoker CursorPositionChanged;
             Control::ControlCore::RendererEnteredErrorState_revoker RendererEnteredErrorState;
             Control::ControlCore::BackgroundColorChanged_revoker BackgroundColorChanged;
             Control::ControlCore::FontSizeChanged_revoker FontSizeChanged;
diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml
index 043a95d3b2e..feb8508440d 100644
--- a/src/cascadia/TerminalControl/TermControl.xaml
+++ b/src/cascadia/TerminalControl/TermControl.xaml
@@ -1332,11 +1332,6 @@
 
         </Grid>
 
-        <local:TSFInputControl x:Name="TSFInputControl"
-                               CompositionCompleted="_CompositionCompleted"
-                               CurrentCursorPosition="_CurrentCursorPositionHandler"
-                               CurrentFontInfo="_FontInfoHandler" />
-
         <Grid x:Name="RendererFailedNotice"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
diff --git a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj
index 59b6a01ea7d..f585755f209 100644
--- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj
+++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj
@@ -65,9 +65,6 @@
     <ClInclude Include="InteractivityAutomationPeer.h">
       <DependentUpon>InteractivityAutomationPeer.idl</DependentUpon>
     </ClInclude>
-    <ClInclude Include="TSFInputControl.h">
-      <DependentUpon>TSFInputControl.xaml</DependentUpon>
-    </ClInclude>
     <ClInclude Include="XamlUiaTextRange.h" />
     <ClInclude Include="HwndTerminal.hpp" />
     <ClInclude Include="HwndTerminalAutomationPeer.hpp" />
@@ -102,9 +99,6 @@
     <ClCompile Include="TermControl.cpp">
       <DependentUpon>TermControl.xaml</DependentUpon>
     </ClCompile>
-    <ClCompile Include="TSFInputControl.cpp">
-      <DependentUpon>TSFInputControl.xaml</DependentUpon>
-    </ClCompile>
     <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
     <ClCompile Include="TermControlAutomationPeer.cpp">
       <DependentUpon>TermControlAutomationPeer.idl</DependentUpon>
@@ -137,9 +131,6 @@
     <Midl Include="TermControlAutomationPeer.idl" />
     <Midl Include="InteractivityAutomationPeer.idl" />
     <Midl Include="IMouseWheelListener.idl" />
-    <Midl Include="TSFInputControl.idl">
-      <DependentUpon>TSFInputControl.xaml</DependentUpon>
-    </Midl>
   </ItemGroup>
   <!-- ========================= XAML Files ======================== -->
   <ItemGroup>
@@ -149,9 +140,6 @@
     <Page Include="TermControl.xaml">
       <SubType>Designer</SubType>
     </Page>
-    <Page Include="TSFInputControl.xaml">
-      <SubType>Designer</SubType>
-    </Page>
   </ItemGroup>
   <!-- ========================= Misc Files ======================== -->
   <ItemGroup>
@@ -160,15 +148,16 @@
   </ItemGroup>
   <!-- ========================= Project References ======================== -->
   <ItemGroup>
-    <ProjectReference Include="..\..\types\lib\types.vcxproj" />
-    <ProjectReference Include="..\..\audio\midi\lib\midi.vcxproj" />
-    <ProjectReference Include="..\..\buffer\out\lib\bufferout.vcxproj" />
+    <ProjectReference Include="$(OpenConsoleDir)src\audio\midi\lib\midi.vcxproj" />
+    <ProjectReference Include="$(OpenConsoleDir)src\buffer\out\lib\bufferout.vcxproj" />
+    <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalCore\lib\TerminalCore-lib.vcxproj" />
+    <ProjectReference Include="$(OpenConsoleDir)src\renderer\atlas\atlas.vcxproj" />
     <ProjectReference Include="$(OpenConsoleDir)src\renderer\base\lib\base.vcxproj" />
-    <ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj" />
-    <ProjectReference Include="..\..\renderer\uia\lib\uia.vcxproj" />
-    <ProjectReference Include="..\..\terminal\parser\lib\parser.vcxproj" />
+    <ProjectReference Include="$(OpenConsoleDir)src\renderer\uia\lib\uia.vcxproj" />
     <ProjectReference Include="$(OpenConsoleDir)src\terminal\input\lib\terminalinput.vcxproj" />
-    <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalCore\lib\TerminalCore-lib.vcxproj" />
+    <ProjectReference Include="$(OpenConsoleDir)src\terminal\parser\lib\parser.vcxproj" />
+    <ProjectReference Include="$(OpenConsoleDir)src\tsf\tsf.vcxproj" />
+    <ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
     <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj">
       <Private>false</Private>
       <CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp
index 0d2c6dbf43b..e0b2c236cd5 100644
--- a/src/cascadia/TerminalCore/Terminal.cpp
+++ b/src/cascadia/TerminalCore/Terminal.cpp
@@ -422,19 +422,7 @@ CATCH_RETURN()
 
 void Terminal::Write(std::wstring_view stringView)
 {
-    const auto& cursor = _activeBuffer().GetCursor();
-    const til::point cursorPosBefore{ cursor.GetPosition() };
-
     _stateMachine->ProcessString(stringView);
-
-    const til::point cursorPosAfter{ cursor.GetPosition() };
-
-    // Firing the CursorPositionChanged event is very expensive so we try not to
-    // do that when the cursor does not need to be redrawn.
-    if (cursorPosBefore != cursorPosAfter)
-    {
-        _NotifyTerminalCursorPositionChanged();
-    }
 }
 
 // Method Description:
@@ -1099,18 +1087,6 @@ void Terminal::_NotifyScrollEvent()
     }
 }
 
-void Terminal::_NotifyTerminalCursorPositionChanged() noexcept
-{
-    if (_pfnCursorPositionChanged)
-    {
-        try
-        {
-            _pfnCursorPositionChanged();
-        }
-        CATCH_LOG();
-    }
-}
-
 void Terminal::SetWriteInputCallback(std::function<void(std::wstring_view)> pfn) noexcept
 {
     _pfnWriteInput.swap(pfn);
@@ -1136,11 +1112,6 @@ void Terminal::SetScrollPositionChangedCallback(std::function<void(const int, co
     _pfnScrollPositionChanged.swap(pfn);
 }
 
-void Terminal::SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept
-{
-    _pfnCursorPositionChanged.swap(pfn);
-}
-
 // Method Description:
 // - Allows settings a callback for settings the taskbar progress indicator
 // Arguments:
diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp
index fc57907ad19..b94959e3047 100644
--- a/src/cascadia/TerminalCore/Terminal.hpp
+++ b/src/cascadia/TerminalCore/Terminal.hpp
@@ -188,7 +188,7 @@ class Microsoft::Terminal::Core::Terminal final :
 #pragma region IRenderData
     Microsoft::Console::Types::Viewport GetViewport() noexcept override;
     til::point GetTextBufferEndPosition() const noexcept override;
-    const TextBuffer& GetTextBuffer() const noexcept override;
+    TextBuffer& GetTextBuffer() const noexcept override;
     const FontInfo& GetFontInfo() const noexcept override;
 
     void LockConsole() noexcept override;
@@ -202,7 +202,6 @@ class Microsoft::Terminal::Core::Terminal final :
     ULONG GetCursorPixelWidth() const noexcept override;
     CursorType GetCursorStyle() const noexcept override;
     bool IsCursorDoubleWidth() const override;
-    const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
     const bool IsGridLineDrawingAllowed() noexcept override;
     const std::wstring GetHyperlinkUri(uint16_t id) const override;
     const std::wstring GetHyperlinkCustomId(uint16_t id) const override;
@@ -227,7 +226,6 @@ class Microsoft::Terminal::Core::Terminal final :
     void SetTitleChangedCallback(std::function<void(std::wstring_view)> pfn) noexcept;
     void SetCopyToClipboardCallback(std::function<void(wil::zwstring_view)> pfn) noexcept;
     void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
-    void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
     void TaskbarProgressChangedCallback(std::function<void()> pfn) noexcept;
     void SetShowWindowCallback(std::function<void(bool)> pfn) noexcept;
     void SetPlayMidiNoteCallback(std::function<void(const int, const int, const std::chrono::microseconds)> pfn) noexcept;
@@ -335,7 +333,6 @@ class Microsoft::Terminal::Core::Terminal final :
     til::recursive_ticket_lock _readWriteLock;
 
     std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
-    std::function<void()> _pfnCursorPositionChanged;
     std::function<void()> _pfnTaskbarProgressChanged;
     std::function<void(bool)> _pfnShowWindowChanged;
     std::function<void(const int, const int, const std::chrono::microseconds)> _pfnPlayMidiNote;
@@ -449,9 +446,6 @@ class Microsoft::Terminal::Core::Terminal final :
     void _PreserveUserScrollOffset(const int viewportDelta) noexcept;
 
     void _NotifyScrollEvent();
-
-    void _NotifyTerminalCursorPositionChanged() noexcept;
-
     bool _inAltBuffer() const noexcept;
     TextBuffer& _activeBuffer() const noexcept;
     void _updateUrlDetection();
diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp
index 8dd9b4dae9a..ba9fdbaea9a 100644
--- a/src/cascadia/TerminalCore/terminalrenderdata.cpp
+++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp
@@ -22,7 +22,7 @@ til::point Terminal::GetTextBufferEndPosition() const noexcept
     return { _GetMutableViewport().Width() - 1, ViewEndIndex() };
 }
 
-const TextBuffer& Terminal::GetTextBuffer() const noexcept
+TextBuffer& Terminal::GetTextBuffer() const noexcept
 {
     return _activeBuffer();
 }
@@ -79,11 +79,6 @@ bool Terminal::IsCursorDoubleWidth() const
     return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single;
 }
 
-const std::vector<RenderOverlay> Terminal::GetOverlays() const noexcept
-{
-    return {};
-}
-
 const bool Terminal::IsGridLineDrawingAllowed() noexcept
 {
     return true;
diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
index d789bfc5583..051732333cc 100644
--- a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
@@ -51,8 +51,6 @@ class TerminalCoreUnitTests::TerminalBufferTests final
 
     TEST_METHOD(TestGetReverseTab);
 
-    TEST_METHOD(TestCursorNotifications);
-
     TEST_METHOD_SETUP(MethodSetup)
     {
         // STEP 1: Set up the Terminal
@@ -596,43 +594,3 @@ void TerminalBufferTests::TestGetReverseTab()
                          L"Cursor adjusted to last item in the sample list from position beyond end.");
     }
 }
-
-void TerminalBufferTests::TestCursorNotifications()
-{
-    // Test for GH#11170
-
-    // Suppress test exceptions. If they occur in the lambda, they'll just crash
-    // TAEF, which is annoying.
-    const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
-
-    auto callbackWasCalled = false;
-    auto expectedCallbacks = 0;
-    auto cb = [&expectedCallbacks, &callbackWasCalled]() mutable {
-        Log::Comment(L"Callback triggered");
-        callbackWasCalled = true;
-        expectedCallbacks--;
-        VERIFY_IS_GREATER_THAN_OR_EQUAL(expectedCallbacks, 0);
-    };
-    term->_pfnCursorPositionChanged = cb;
-
-    // The exact number of callbacks here is fungible, if need be.
-
-    expectedCallbacks = 1;
-    callbackWasCalled = false;
-    term->Write(L"Foo");
-    VERIFY_ARE_EQUAL(0, expectedCallbacks);
-    VERIFY_IS_TRUE(callbackWasCalled);
-
-    expectedCallbacks = 1;
-    callbackWasCalled = false;
-    term->Write(L"Foo\r\nBar");
-    VERIFY_ARE_EQUAL(0, expectedCallbacks);
-    VERIFY_IS_TRUE(callbackWasCalled);
-
-    expectedCallbacks = 2; // One for each Write
-    callbackWasCalled = false;
-    term->Write(L"Foo\r\nBar");
-    term->Write(L"Foo\r\nBar");
-    VERIFY_ARE_EQUAL(0, expectedCallbacks);
-    VERIFY_IS_TRUE(callbackWasCalled);
-}
diff --git a/src/host/_output.cpp b/src/host/_output.cpp
index 8ad7117ea0b..0804a7ffab9 100644
--- a/src/host/_output.cpp
+++ b/src/host/_output.cpp
@@ -52,8 +52,6 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
             ServiceLocator::LocateGlobals().pRender->TriggerRedraw(region);
         }
     }
-
-    WriteConvRegionToScreen(screenInfo, region);
 }
 
 // Routine Description:
diff --git a/src/host/conareainfo.cpp b/src/host/conareainfo.cpp
deleted file mode 100644
index 0699c7bf15c..00000000000
--- a/src/host/conareainfo.cpp
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-
-#include "conareainfo.h"
-
-#include "_output.h"
-
-#include "../interactivity/inc/ServiceLocator.hpp"
-#include "../types/inc/viewport.hpp"
-
-#pragma hdrstop
-
-using namespace Microsoft::Console::Types;
-using Microsoft::Console::Interactivity::ServiceLocator;
-
-ConversionAreaBufferInfo::ConversionAreaBufferInfo(const til::size coordBufferSize) :
-    coordCaBuffer(coordBufferSize)
-{
-}
-
-ConversionAreaInfo::ConversionAreaInfo(const til::size bufferSize,
-                                       const til::size windowSize,
-                                       const TextAttribute& fill,
-                                       const TextAttribute& popupFill,
-                                       const FontInfo fontInfo) :
-    _caInfo{ bufferSize },
-    _isHidden{ true },
-    _screenBuffer{ nullptr }
-{
-    SCREEN_INFORMATION* pNewScreen = nullptr;
-
-    // cursor has no height because it won't be rendered for conversion area
-    THROW_IF_NTSTATUS_FAILED(SCREEN_INFORMATION::CreateInstance(windowSize,
-                                                                fontInfo,
-                                                                bufferSize,
-                                                                fill,
-                                                                popupFill,
-                                                                0,
-                                                                &pNewScreen));
-
-    // Suppress painting notifications for modifying a conversion area cursor as they're not actually rendered.
-    pNewScreen->GetTextBuffer().GetCursor().SetIsConversionArea(true);
-    pNewScreen->ConvScreenInfo = this;
-
-    _screenBuffer.reset(pNewScreen);
-}
-
-ConversionAreaInfo::ConversionAreaInfo(ConversionAreaInfo&& other) :
-    _caInfo(other._caInfo),
-    _isHidden(other._isHidden),
-    _screenBuffer(nullptr)
-{
-    std::swap(_screenBuffer, other._screenBuffer);
-}
-
-// Routine Description:
-// - Describes whether the conversion area should be drawn or should be hidden.
-// Arguments:
-// - <none>
-// Return Value:
-// - True if it should not be drawn. False if it should be drawn.
-bool ConversionAreaInfo::IsHidden() const noexcept
-{
-    return _isHidden;
-}
-
-// Routine Description:
-// - Sets a value describing whether the conversion area should be drawn or should be hidden.
-// Arguments:
-// - fIsHidden - True if it should not be drawn. False if it should be drawn.
-// Return Value:
-// - <none>
-void ConversionAreaInfo::SetHidden(const bool fIsHidden) noexcept
-{
-    _isHidden = fIsHidden;
-}
-
-// Routine Description:
-// - Retrieves the underlying text buffer for use in rendering data
-const TextBuffer& ConversionAreaInfo::GetTextBuffer() const noexcept
-{
-    return _screenBuffer->GetTextBuffer();
-}
-
-// Routine Description:
-// - Retrieves the layout/overlay information about where to place this conversion area relative to the
-//   existing screen buffers and viewports.
-const ConversionAreaBufferInfo& ConversionAreaInfo::GetAreaBufferInfo() const noexcept
-{
-    return _caInfo;
-}
-
-// Routine Description:
-// - Forwards a color attribute setting request to the internal screen information
-// Arguments:
-// - attr - Color to apply to internal screen buffer
-void ConversionAreaInfo::SetAttributes(const TextAttribute& attr)
-{
-    _screenBuffer->SetAttributes(attr);
-}
-
-// Routine Description:
-// - Writes text into the conversion area. Since conversion areas are only
-//   one line, you can only specify the column to write.
-// Arguments:
-// - text - Text to insert into the conversion area buffer
-// - column - Column to start at (X position)
-void ConversionAreaInfo::WriteText(const std::vector<OutputCell>& text,
-                                   const til::CoordType column)
-{
-    std::span<const OutputCell> view(text.data(), text.size());
-    _screenBuffer->Write(view, { column, 0 });
-}
-
-// Routine Description:
-// - Clears out a conversion area
-void ConversionAreaInfo::ClearArea() noexcept
-{
-    SetHidden(true);
-
-    try
-    {
-        _screenBuffer->ClearTextData();
-    }
-    CATCH_LOG();
-
-    Paint();
-}
-
-[[nodiscard]] HRESULT ConversionAreaInfo::Resize(const til::size newSize) noexcept
-{
-    // attempt to resize underlying buffers
-    RETURN_IF_NTSTATUS_FAILED(_screenBuffer->ResizeScreenBuffer(newSize, FALSE));
-
-    // store new size
-    _caInfo.coordCaBuffer = newSize;
-
-    // restrict viewport to buffer size.
-    const til::point restriction = { newSize.width - 1, newSize.height - 1 };
-    _caInfo.rcViewCaWindow.left = std::min(_caInfo.rcViewCaWindow.left, restriction.x);
-    _caInfo.rcViewCaWindow.right = std::min(_caInfo.rcViewCaWindow.right, restriction.x);
-    _caInfo.rcViewCaWindow.top = std::min(_caInfo.rcViewCaWindow.top, restriction.y);
-    _caInfo.rcViewCaWindow.bottom = std::min(_caInfo.rcViewCaWindow.bottom, restriction.y);
-
-    return S_OK;
-}
-
-void ConversionAreaInfo::SetWindowInfo(const til::inclusive_rect& view) noexcept
-{
-    if (view.left != _caInfo.rcViewCaWindow.left ||
-        view.top != _caInfo.rcViewCaWindow.top ||
-        view.right != _caInfo.rcViewCaWindow.right ||
-        view.bottom != _caInfo.rcViewCaWindow.bottom)
-    {
-        if (!IsHidden())
-        {
-            SetHidden(true);
-            Paint();
-
-            _caInfo.rcViewCaWindow = view;
-            SetHidden(false);
-            Paint();
-        }
-        else
-        {
-            _caInfo.rcViewCaWindow = view;
-        }
-    }
-}
-
-void ConversionAreaInfo::SetViewPos(const til::point pos) noexcept
-{
-    if (IsHidden())
-    {
-        _caInfo.coordConView = pos;
-    }
-    else
-    {
-        auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-
-        auto OldRegion = _caInfo.rcViewCaWindow;
-        OldRegion.left += _caInfo.coordConView.x;
-        OldRegion.right += _caInfo.coordConView.x;
-        OldRegion.top += _caInfo.coordConView.y;
-        OldRegion.bottom += _caInfo.coordConView.y;
-        WriteToScreen(gci.GetActiveOutputBuffer(), Viewport::FromInclusive(OldRegion));
-
-        _caInfo.coordConView = pos;
-
-        auto NewRegion = _caInfo.rcViewCaWindow;
-        NewRegion.left += _caInfo.coordConView.x;
-        NewRegion.right += _caInfo.coordConView.x;
-        NewRegion.top += _caInfo.coordConView.y;
-        NewRegion.bottom += _caInfo.coordConView.y;
-        WriteToScreen(gci.GetActiveOutputBuffer(), Viewport::FromInclusive(NewRegion));
-    }
-}
-
-void ConversionAreaInfo::Paint() const noexcept
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    auto& ScreenInfo = gci.GetActiveOutputBuffer();
-    const auto viewport = ScreenInfo.GetViewport();
-
-    til::inclusive_rect WriteRegion;
-    WriteRegion.left = viewport.Left() + _caInfo.coordConView.x + _caInfo.rcViewCaWindow.left;
-    WriteRegion.right = WriteRegion.left + (_caInfo.rcViewCaWindow.right - _caInfo.rcViewCaWindow.left);
-    WriteRegion.top = viewport.Top() + _caInfo.coordConView.y + _caInfo.rcViewCaWindow.top;
-    WriteRegion.bottom = WriteRegion.top + (_caInfo.rcViewCaWindow.bottom - _caInfo.rcViewCaWindow.top);
-
-    if (!IsHidden())
-    {
-        WriteConvRegionToScreen(ScreenInfo, Viewport::FromInclusive(WriteRegion));
-    }
-    else
-    {
-        WriteToScreen(ScreenInfo, Viewport::FromInclusive(WriteRegion));
-    }
-}
diff --git a/src/host/conareainfo.h b/src/host/conareainfo.h
deleted file mode 100644
index f334cbac813..00000000000
--- a/src/host/conareainfo.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*++
-Copyright (c) Microsoft Corporation
-Licensed under the MIT license.
-
-Module Name:
-- conareainfo.h
-
-Abstract:
-- This module contains the structures for the console IME conversion area
-- The conversion area is the overlay on the screen where a user attempts to form
-  a string that they would like to insert into the buffer.
-
-Author:
-- Michael Niksa (MiNiksa) 10-May-2018
-
-Revision History:
-- From pieces of convarea.cpp originally authored by KazuM
---*/
-
-#pragma once
-
-#include "../buffer/out/OutputCell.hpp"
-#include "../buffer/out/TextAttribute.hpp"
-#include "../renderer/inc/FontInfo.hpp"
-
-class SCREEN_INFORMATION;
-class TextBuffer;
-
-// Internal structures and definitions used by the conversion area.
-class ConversionAreaBufferInfo final
-{
-public:
-    til::size coordCaBuffer;
-    til::inclusive_rect rcViewCaWindow;
-    til::point coordConView;
-
-    explicit ConversionAreaBufferInfo(const til::size coordBufferSize);
-};
-
-class ConversionAreaInfo final
-{
-public:
-    ConversionAreaInfo(const til::size bufferSize,
-                       const til::size windowSize,
-                       const TextAttribute& fill,
-                       const TextAttribute& popupFill,
-                       const FontInfo fontInfo);
-    ~ConversionAreaInfo() = default;
-    ConversionAreaInfo(const ConversionAreaInfo&) = delete;
-    ConversionAreaInfo(ConversionAreaInfo&& other);
-    ConversionAreaInfo& operator=(const ConversionAreaInfo&) & = delete;
-    ConversionAreaInfo& operator=(ConversionAreaInfo&&) & = delete;
-
-    bool IsHidden() const noexcept;
-    void SetHidden(const bool fIsHidden) noexcept;
-    void ClearArea() noexcept;
-
-    [[nodiscard]] HRESULT Resize(const til::size newSize) noexcept;
-
-    void SetViewPos(const til::point pos) noexcept;
-    void SetWindowInfo(const til::inclusive_rect& view) noexcept;
-    void Paint() const noexcept;
-
-    void WriteText(const std::vector<OutputCell>& text, const til::CoordType column);
-    void SetAttributes(const TextAttribute& attr);
-
-    const TextBuffer& GetTextBuffer() const noexcept;
-    const ConversionAreaBufferInfo& GetAreaBufferInfo() const noexcept;
-
-private:
-    ConversionAreaBufferInfo _caInfo;
-    std::unique_ptr<SCREEN_INFORMATION> _screenBuffer;
-    bool _isHidden;
-};
diff --git a/src/host/conimeinfo.cpp b/src/host/conimeinfo.cpp
deleted file mode 100644
index fc2ce5ae74c..00000000000
--- a/src/host/conimeinfo.cpp
+++ /dev/null
@@ -1,507 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-
-#include "conimeinfo.h"
-
-#include <til/unicode.h>
-
-#include "conareainfo.h"
-#include "_output.h"
-#include "dbcs.h"
-#include "../interactivity/inc/ServiceLocator.hpp"
-#include "../types/inc/GlyphWidth.hpp"
-
-// Attributes flags:
-#define COMMON_LVB_GRID_SINGLEFLAG 0x2000 // DBCS: Grid attribute: use for ime cursor.
-
-using Microsoft::Console::Interactivity::ServiceLocator;
-
-ConsoleImeInfo::ConsoleImeInfo() :
-    _isSavedCursorVisible(false)
-{
-}
-
-// Routine Description:
-// - Copies default attribute (color) data from the active screen buffer into the conversion area buffers
-void ConsoleImeInfo::RefreshAreaAttributes()
-{
-    const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    const auto attributes = gci.GetActiveOutputBuffer().GetAttributes();
-
-    for (auto& area : ConvAreaCompStr)
-    {
-        area.SetAttributes(attributes);
-    }
-}
-
-// Routine Description:
-// - Takes the internally held composition message data from the last WriteCompMessage call
-//   and attempts to redraw it on the screen which will account for changes in viewport dimensions
-void ConsoleImeInfo::RedrawCompMessage()
-{
-    if (!_text.empty())
-    {
-        ClearAllAreas();
-        _WriteUndeterminedChars(_text, _attributes, _colorArray);
-    }
-}
-
-// Routine Description:
-// - Writes an undetermined composition message to the screen including the text
-//   and color and cursor positioning attribute data so the user can walk through
-//   what they're proposing to insert into the buffer.
-// Arguments:
-// - text - The actual text of what the user would like to insert (UTF-16)
-// - attributes - Encoded attributes including the cursor position and the color index (to the array)
-// - colorArray - An array of colors to use for the text
-void ConsoleImeInfo::WriteCompMessage(const std::wstring_view text,
-                                      const std::span<const BYTE> attributes,
-                                      const std::span<const WORD> colorArray)
-{
-    ClearAllAreas();
-
-    // MSFT:29219348 only hide the cursor after the IME produces a string.
-    // See notes in convarea.cpp ImeStartComposition().
-    SaveCursorVisibility();
-
-    // Save copies of the composition message in case we need to redraw it as things scroll/resize
-    _text = text;
-    _attributes.assign(attributes.begin(), attributes.end());
-    _colorArray.assign(colorArray.begin(), colorArray.end());
-
-    _WriteUndeterminedChars(text, attributes, colorArray);
-}
-
-// Routine Description:
-// - Writes the final result into the screen buffer through the input queue
-//   as if the user had inputted it (if their keyboard was able to)
-// Arguments:
-// - text - The actual text of what the user would like to insert (UTF-16)
-void ConsoleImeInfo::WriteResultMessage(const std::wstring_view text)
-{
-    ClearAllAreas();
-
-    _InsertConvertedString(text);
-
-    _ClearComposition();
-}
-
-// Routine Description:
-// - Clears internally cached composition data from the last WriteCompMessage call.
-void ConsoleImeInfo::_ClearComposition()
-{
-    _text.clear();
-    _attributes.clear();
-    _colorArray.clear();
-}
-
-// Routine Description:
-// - Clears out all conversion areas
-void ConsoleImeInfo::ClearAllAreas()
-{
-    for (auto& area : ConvAreaCompStr)
-    {
-        if (!area.IsHidden())
-        {
-            area.ClearArea();
-        }
-    }
-
-    // Also clear internal buffer of string data.
-    _ClearComposition();
-}
-
-// Routine Description:
-// - Resizes all conversion areas to the new dimensions
-// Arguments:
-// - newSize - New size for conversion areas
-// Return Value:
-// - S_OK or appropriate failure HRESULT.
-[[nodiscard]] HRESULT ConsoleImeInfo::ResizeAllAreas(const til::size newSize)
-{
-    for (auto& area : ConvAreaCompStr)
-    {
-        if (!area.IsHidden())
-        {
-            area.SetHidden(true);
-            area.Paint();
-        }
-
-        RETURN_IF_FAILED(area.Resize(newSize));
-    }
-
-    return S_OK;
-}
-
-// Routine Description:
-// - Adds another conversion area to the current list of conversion areas (lines) available for IME candidate text
-// Arguments:
-// - <none>
-// Return Value:
-// - Status successful or appropriate HRESULT response.
-[[nodiscard]] HRESULT ConsoleImeInfo::_AddConversionArea()
-{
-    const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-
-    auto bufferSize = gci.GetActiveOutputBuffer().GetBufferSize().Dimensions();
-    bufferSize.height = 1;
-
-    const auto windowSize = gci.GetActiveOutputBuffer().GetViewport().Dimensions();
-
-    const auto fill = gci.GetActiveOutputBuffer().GetAttributes();
-
-    const auto popupFill = gci.GetActiveOutputBuffer().GetPopupAttributes();
-
-    const auto& fontInfo = gci.GetActiveOutputBuffer().GetCurrentFont();
-
-    try
-    {
-        ConvAreaCompStr.emplace_back(bufferSize,
-                                     windowSize,
-                                     fill,
-                                     popupFill,
-                                     fontInfo);
-    }
-    CATCH_RETURN();
-
-    RefreshAreaAttributes();
-
-    return S_OK;
-}
-
-// Routine Description:
-// - Helper method to decode the cursor and color position out of the encoded attributes
-//   and color array and return it in the TextAttribute structure format
-// Arguments:
-// - pos - Character position in the string (and matching encoded attributes array)
-// - attributes - Encoded attributes holding cursor and color array position
-// - colorArray - Colors to choose from
-// Return Value:
-// - TextAttribute object with color and cursor and line drawing data.
-TextAttribute ConsoleImeInfo::s_RetrieveAttributeAt(const size_t pos,
-                                                    const std::span<const BYTE> attributes,
-                                                    const std::span<const WORD> colorArray)
-{
-    // Encoded attribute is the shorthand information passed from the IME
-    // that contains a cursor position packed in along with which color in the
-    // given array should apply to the text.
-    auto encodedAttribute = attributes[pos];
-
-    // Legacy attribute is in the color/line format that is understood for drawing
-    // We use the lower 3 bits (0-7) from the encoded attribute as the array index to start
-    // creating our legacy attribute.
-    auto legacyAttribute = colorArray[encodedAttribute & (CONIME_ATTRCOLOR_SIZE - 1)];
-
-    if (WI_IsFlagSet(encodedAttribute, CONIME_CURSOR_RIGHT))
-    {
-        WI_SetFlag(legacyAttribute, COMMON_LVB_GRID_SINGLEFLAG);
-        WI_SetFlag(legacyAttribute, COMMON_LVB_GRID_RVERTICAL);
-    }
-    else if (WI_IsFlagSet(encodedAttribute, CONIME_CURSOR_LEFT))
-    {
-        WI_SetFlag(legacyAttribute, COMMON_LVB_GRID_SINGLEFLAG);
-        WI_SetFlag(legacyAttribute, COMMON_LVB_GRID_LVERTICAL);
-    }
-
-    return TextAttribute(legacyAttribute);
-}
-
-// Routine Description:
-// - Converts IME-formatted information into OutputCells to determine what can fit into each
-//   displayable cell inside the console output buffer.
-// Arguments:
-// - text - Text data provided by the IME
-// - attributes - Encoded color and cursor position data provided by the IME
-// - colorArray - Array of color values provided by the IME.
-// Return Value:
-// - Vector of OutputCells where each one represents one cell of the output buffer.
-std::vector<OutputCell> ConsoleImeInfo::s_ConvertToCells(const std::wstring_view text,
-                                                         const std::span<const BYTE> attributes,
-                                                         const std::span<const WORD> colorArray)
-{
-    std::vector<OutputCell> cells;
-
-    // - Walk through all of the grouped up text, match up the correct attribute to it, and make a new cell.
-    size_t attributesUsed = 0;
-    for (const auto& parsedGlyph : til::utf16_iterator{ text })
-    {
-        const std::wstring_view glyph{ parsedGlyph.data(), parsedGlyph.size() };
-        // Collect up attributes that apply to this glyph range.
-        auto drawingAttr = s_RetrieveAttributeAt(attributesUsed, attributes, colorArray);
-        attributesUsed++;
-
-        // The IME gave us an attribute for every glyph position in a surrogate pair.
-        // But the only important information will be the cursor position.
-        // Check all additional attributes to see if the cursor resides on top of them.
-        for (size_t i = 1; i < glyph.size(); i++)
-        {
-            auto additionalAttr = s_RetrieveAttributeAt(attributesUsed, attributes, colorArray);
-            attributesUsed++;
-            if (additionalAttr.IsLeftVerticalDisplayed())
-            {
-                drawingAttr.SetLeftVerticalDisplayed(true);
-            }
-            if (additionalAttr.IsRightVerticalDisplayed())
-            {
-                drawingAttr.SetRightVerticalDisplayed(true);
-            }
-        }
-
-        // We have to determine if the glyph range is 1 column or two.
-        // If it's full width, it's two, and we need to make sure we don't draw the cursor
-        // right down the middle of the character.
-        // Otherwise it's one column and we'll push it in with the default empty DbcsAttribute.
-        DbcsAttribute dbcsAttr = DbcsAttribute::Single;
-        if (IsGlyphFullWidth(glyph))
-        {
-            auto leftHalfAttr = drawingAttr;
-            auto rightHalfAttr = drawingAttr;
-
-            // Don't draw lines in the middle of full width glyphs.
-            // If we need a right vertical, don't apply it to the left side of the character
-            if (leftHalfAttr.IsRightVerticalDisplayed())
-            {
-                leftHalfAttr.SetRightVerticalDisplayed(false);
-            }
-
-            dbcsAttr = DbcsAttribute::Leading;
-            cells.emplace_back(glyph, dbcsAttr, leftHalfAttr);
-            dbcsAttr = DbcsAttribute::Trailing;
-
-            // If we need a left vertical, don't apply it to the right side of the character
-            if (rightHalfAttr.IsLeftVerticalDisplayed())
-            {
-                rightHalfAttr.SetLeftVerticalDisplayed(false);
-            }
-            cells.emplace_back(glyph, dbcsAttr, rightHalfAttr);
-        }
-        else
-        {
-            cells.emplace_back(glyph, dbcsAttr, drawingAttr);
-        }
-    }
-
-    return cells;
-}
-
-// Routine Description:
-// - Walks through the cells given and attempts to fill a conversion area line with as much data as can fit.
-// - Each conversion area represents one line of the display starting at the cursor position filling to the right edge
-//   of the display.
-// - The first conversion area should be placed from the screen buffer's current cursor position to the right
-//   edge of the viewport.
-// - All subsequent areas should use one entire line of the viewport.
-// Arguments:
-// - begin - Beginning position in OutputCells for iteration
-// - end - Ending position in OutputCells for iteration
-// - pos - Reference to the coordinate position in the viewport that this conversion area will occupy.
-//       - Updated to set up the next conversion area down a line (and to the left viewport edge)
-// - view - The rectangle representing the viewable area of the screen right now to let us know how many cells can fit.
-// - screenInfo - A reference to the screen information we will use for accessibility notifications
-// Return Value:
-// - Updated begin position for the next call. It will normally be >begin and <= end.
-//   However, if text couldn't fit in our line (full-width character starting at the very last cell)
-//   then we will give back the same begin and update the position for the next call to try again.
-//   If the viewport is deemed too small, we'll skip past it and advance begin past the entire full-width character.
-std::vector<OutputCell>::const_iterator ConsoleImeInfo::_WriteConversionArea(const std::vector<OutputCell>::const_iterator begin,
-                                                                             const std::vector<OutputCell>::const_iterator end,
-                                                                             til::point& pos,
-                                                                             const Microsoft::Console::Types::Viewport view,
-                                                                             SCREEN_INFORMATION& screenInfo)
-{
-    // The position in the viewport where we will start inserting cells for this conversion area
-    // NOTE: We might exit early if there's not enough space to fit here, so we take a copy of
-    //       the original and increment it up front.
-    const auto insertionPos = pos;
-
-    // Advance the cursor position to set up the next call for success (insert the next conversion area
-    // at the beginning of the following line)
-    pos.x = view.Left();
-    pos.y++;
-
-    // The index of the last column in the viewport. (view is inclusive)
-    const auto finalViewColumn = view.RightInclusive();
-
-    // The maximum number of cells we can insert into a line.
-    const auto lineWidth = finalViewColumn - insertionPos.x + 1; // +1 because view was inclusive
-
-    // The iterator to the beginning position to form our line
-    const auto lineBegin = begin;
-
-    // The total number of cells we could insert.
-    const auto size = end - begin;
-    FAIL_FAST_IF(size <= 0); // It's a programming error to have <= 0 cells to insert.
-
-    // The end is the smaller of the remaining number of cells or the amount of line cells we can write before
-    // hitting the right edge of the viewport
-    auto lineEnd = lineBegin + std::min(size, (ptrdiff_t)lineWidth);
-
-    // We must attempt to compensate for ending on a leading byte. We can't split a full-width character across lines.
-    // As such, if the last item is a leading byte, back the end up by one.
-    // Get the last cell in the run and if it's a leading byte, move the end position back one so we don't
-    // try to insert it.
-    const auto lastCell = lineEnd - 1;
-    if (lastCell->DbcsAttr() == DbcsAttribute::Leading)
-    {
-        lineEnd--;
-    }
-
-    // GH#12730 - if the lineVec would now be empty, just return early. Failing
-    // to do so will later cause a crash trying to construct an empty view.
-    if (lineEnd <= lineBegin)
-    {
-        return lineEnd;
-    }
-
-    // Copy out the substring into a vector.
-    const std::vector<OutputCell> lineVec(lineBegin, lineEnd);
-
-    // Add a conversion area to the internal state to hold this line.
-    THROW_IF_FAILED(_AddConversionArea());
-
-    // Get the added conversion area.
-    auto& area = ConvAreaCompStr.back();
-
-    // Write our text into the conversion area.
-    area.WriteText(lineVec, insertionPos.x);
-
-    // Set the viewport and positioning parameters for the conversion area to describe to the renderer
-    // the appropriate location to overlay this conversion area on top of the main screen buffer inside the viewport.
-    const til::inclusive_rect region{ insertionPos.x, 0, gsl::narrow<til::CoordType>(insertionPos.x + lineVec.size() - 1), 0 };
-    area.SetWindowInfo(region);
-    area.SetViewPos({ 0 - view.Left(), insertionPos.y - view.Top() });
-
-    // Make it visible and paint it.
-    area.SetHidden(false);
-    area.Paint();
-
-    // Notify accessibility that we have updated the text in this display region within the viewport.
-    if (screenInfo.HasAccessibilityEventing())
-    {
-        screenInfo.NotifyAccessibilityEventing(region.left, insertionPos.y, region.right, insertionPos.y);
-    }
-
-    // Hand back the iterator representing the end of what we used to be fed into the beginning of the next call.
-    return lineEnd;
-}
-
-// Routine Description:
-// - Takes information from the IME message to write the "undetermined" text to the
-//   conversion area overlays on the screen.
-// - The "undetermined" text represents the word or phrase that the user is currently building
-//   using the IME. They haven't "determined" what they want yet, so it's "undetermined" right now.
-// Arguments:
-// - text - View into the text characters provided by the IME.
-// - attributes - Attributes specifying which color and cursor positioning information should apply to
-//                each text character. This view must be the same size as the text view.
-// - colorArray - 8 colors to be used to format the text for display
-void ConsoleImeInfo::_WriteUndeterminedChars(const std::wstring_view text,
-                                             const std::span<const BYTE> attributes,
-                                             const std::span<const WORD> colorArray)
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    auto& screenInfo = gci.GetActiveOutputBuffer();
-
-    // Ensure cursor is visible for prompt line
-    screenInfo.MakeCurrentCursorVisible();
-
-    // Clear out existing conversion areas.
-    ConvAreaCompStr.clear();
-
-    // If the text length and attribute length don't match,
-    // it's a programming error on our part. We control the sizes here.
-    FAIL_FAST_IF(text.size() != attributes.size());
-
-    // If we have no text, return. We've already cleared above.
-    if (text.empty())
-    {
-        return;
-    }
-
-    // Convert data-to-be-stored into OutputCells.
-    const auto cells = s_ConvertToCells(text, attributes, colorArray);
-
-    // Get some starting position information of where to place the conversion areas on top of the existing
-    // screen buffer and viewport positioning.
-    // Each conversion area write will adjust these to set up any subsequent calls to go onto the next line.
-    auto pos = screenInfo.GetTextBuffer().GetCursor().GetPosition();
-    // Convert the cursor buffer position to the equivalent screen
-    // coordinates, taking line rendition into account.
-    pos = screenInfo.GetTextBuffer().BufferToScreenPosition(pos);
-
-    const auto view = screenInfo.GetViewport();
-    // Set cursor position relative to viewport
-
-    // Set up our iterators. We will walk through the entire set of cells from beginning to end.
-    // The first time, we will give the iterators as the whole span and the begin
-    // will be moved forward by the conversion area write to set up the next call.
-    auto begin = cells.cbegin();
-    const auto end = cells.cend();
-
-    // Write over and over updating the beginning iterator until we reach the end.
-    do
-    {
-        begin = _WriteConversionArea(begin, end, pos, view, screenInfo);
-    } while (begin < end);
-}
-
-// Routine Description:
-// - Takes the final text string and injects it into the input buffer
-// Arguments:
-// - text - The text to inject into the input buffer
-void ConsoleImeInfo::_InsertConvertedString(const std::wstring_view text)
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-
-    auto& screenInfo = gci.GetActiveOutputBuffer();
-    if (screenInfo.GetTextBuffer().GetCursor().IsOn())
-    {
-        gci.GetCursorBlinker().TimerRoutine(screenInfo);
-    }
-
-    const auto dwControlKeyState = GetControlKeyState(0);
-    InputEventQueue inEvents;
-    auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, 0, dwControlKeyState);
-
-    for (const auto& ch : text)
-    {
-        keyEvent.Event.KeyEvent.uChar.UnicodeChar = ch;
-        inEvents.push_back(keyEvent);
-    }
-
-    gci.pInputBuffer->Write(inEvents);
-}
-
-// Routine Description:
-// - Backs up the global cursor visibility state if it is shown and disables
-//   it while we work on the conversion areas.
-void ConsoleImeInfo::SaveCursorVisibility()
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    auto& cursor = gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor();
-
-    // Cursor turn OFF.
-    if (cursor.IsVisible())
-    {
-        _isSavedCursorVisible = true;
-
-        cursor.SetIsVisible(false);
-    }
-}
-
-// Routine Description:
-// - Restores the global cursor visibility state if it was on when it was backed up.
-void ConsoleImeInfo::RestoreCursorVisibility()
-{
-    if (_isSavedCursorVisible)
-    {
-        _isSavedCursorVisible = false;
-
-        auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-        auto& cursor = gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor();
-
-        cursor.SetIsVisible(true);
-    }
-}
diff --git a/src/host/conimeinfo.h b/src/host/conimeinfo.h
deleted file mode 100644
index 81ea359d8a5..00000000000
--- a/src/host/conimeinfo.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*++
-Copyright (c) Microsoft Corporation
-Licensed under the MIT license.
-
-Module Name:
-- conimeinfo.h
-
-Abstract:
-- This module contains the structures for the console IME entrypoints
-  for overall control
-
-Author:
-- Michael Niksa (MiNiksa) 10-May-2018
-
-Revision History:
-- From pieces of convarea.cpp originally authored by KazuM
---*/
-
-#pragma once
-
-#include "../inc/conime.h"
-#include "../buffer/out/OutputCell.hpp"
-#include "../buffer/out/TextAttribute.hpp"
-#include "../renderer/inc/FontInfo.hpp"
-#include "../types/inc/viewport.hpp"
-
-#include "conareainfo.h"
-
-class SCREEN_INFORMATION;
-
-class ConsoleImeInfo final
-{
-public:
-    // IME composition string information
-    // There is one "composition string" per line that must be rendered on the screen
-    std::vector<ConversionAreaInfo> ConvAreaCompStr;
-
-    ConsoleImeInfo();
-    ~ConsoleImeInfo() = default;
-    ConsoleImeInfo(const ConsoleImeInfo&) = delete;
-    ConsoleImeInfo(ConsoleImeInfo&&) = delete;
-    ConsoleImeInfo& operator=(const ConsoleImeInfo&) & = delete;
-    ConsoleImeInfo& operator=(ConsoleImeInfo&&) & = delete;
-
-    void RefreshAreaAttributes();
-    void ClearAllAreas();
-
-    [[nodiscard]] HRESULT ResizeAllAreas(const til::size newSize);
-
-    void WriteCompMessage(const std::wstring_view text,
-                          const std::span<const BYTE> attributes,
-                          const std::span<const WORD> colorArray);
-
-    void WriteResultMessage(const std::wstring_view text);
-
-    void RedrawCompMessage();
-
-    void SaveCursorVisibility();
-    void RestoreCursorVisibility();
-
-private:
-    [[nodiscard]] HRESULT _AddConversionArea();
-
-    void _ClearComposition();
-
-    void _WriteUndeterminedChars(const std::wstring_view text,
-                                 const std::span<const BYTE> attributes,
-                                 const std::span<const WORD> colorArray);
-
-    void _InsertConvertedString(const std::wstring_view text);
-
-    static TextAttribute s_RetrieveAttributeAt(const size_t pos,
-                                               const std::span<const BYTE> attributes,
-                                               const std::span<const WORD> colorArray);
-
-    static std::vector<OutputCell> s_ConvertToCells(const std::wstring_view text,
-                                                    const std::span<const BYTE> attributes,
-                                                    const std::span<const WORD> colorArray);
-
-    std::vector<OutputCell>::const_iterator _WriteConversionArea(const std::vector<OutputCell>::const_iterator begin,
-                                                                 const std::vector<OutputCell>::const_iterator end,
-                                                                 til::point& pos,
-                                                                 const Microsoft::Console::Types::Viewport view,
-                                                                 SCREEN_INFORMATION& screenInfo);
-
-    bool _isSavedCursorVisible;
-
-    std::wstring _text;
-    std::vector<BYTE> _attributes;
-    std::vector<WORD> _colorArray;
-};
diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp
index 074bd56911f..12e8c5e5b55 100644
--- a/src/host/consoleInformation.cpp
+++ b/src/host/consoleInformation.cpp
@@ -102,8 +102,6 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept
 
     gci.GetActiveOutputBuffer().ScrollScale = gci.GetScrollScale();
 
-    gci.ConsoleIme.RefreshAreaAttributes();
-
     if (SUCCEEDED_NTSTATUS(Status))
     {
         return STATUS_SUCCESS;
diff --git a/src/host/conv.h b/src/host/conv.h
deleted file mode 100644
index fb1a02395e4..00000000000
--- a/src/host/conv.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*++
-Copyright (c) Microsoft Corporation
-Licensed under the MIT license.
-
-Module Name:
-- conv.h
-
-Abstract:
-- This module contains the internal structures and definitions used by the conversion area.
-- "Conversion area" refers to either the in-line area where the text color changes and suggests options in IME-based languages
-  or to the reserved line at the bottom of the screen offering suggestions and the current IME mode.
-
-Author:
-- KazuM March 8, 1993
-
-Revision History:
---*/
-
-#pragma once
-
-#include "server.h"
-
-#include "../types/inc/Viewport.hpp"
-
-void WriteConvRegionToScreen(const SCREEN_INFORMATION& ScreenInfo,
-                             const Microsoft::Console::Types::Viewport& convRegion);
-
-[[nodiscard]] HRESULT ConsoleImeResizeCompStrView();
-[[nodiscard]] HRESULT ConsoleImeResizeCompStrScreenBuffer(const til::size coordNewScreenSize);
diff --git a/src/host/convarea.cpp b/src/host/convarea.cpp
deleted file mode 100644
index 62c9796a56f..00000000000
--- a/src/host/convarea.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-
-#include "_output.h"
-
-#include "../interactivity/inc/ServiceLocator.hpp"
-
-#pragma hdrstop
-
-using namespace Microsoft::Console::Types;
-using Microsoft::Console::Interactivity::ServiceLocator;
-
-void WriteConvRegionToScreen(const SCREEN_INFORMATION& ScreenInfo,
-                             const Viewport& convRegion)
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    if (!ScreenInfo.IsActiveScreenBuffer())
-    {
-        return;
-    }
-
-    const auto pIme = &gci.ConsoleIme;
-
-    for (unsigned int i = 0; i < pIme->ConvAreaCompStr.size(); ++i)
-    {
-        const auto& ConvAreaInfo = pIme->ConvAreaCompStr[i];
-
-        if (!ConvAreaInfo.IsHidden())
-        {
-            const auto currentViewport = ScreenInfo.GetViewport().ToInclusive();
-            const auto areaInfo = ConvAreaInfo.GetAreaBufferInfo();
-
-            // Do clipping region
-            til::inclusive_rect Region;
-            Region.left = currentViewport.left + areaInfo.rcViewCaWindow.left + areaInfo.coordConView.x;
-            Region.right = Region.left + (areaInfo.rcViewCaWindow.right - areaInfo.rcViewCaWindow.left);
-            Region.top = currentViewport.top + areaInfo.rcViewCaWindow.top + areaInfo.coordConView.y;
-            Region.bottom = Region.top + (areaInfo.rcViewCaWindow.bottom - areaInfo.rcViewCaWindow.top);
-
-            Region.left = std::max(Region.left, currentViewport.left);
-            Region.top = std::max(Region.top, currentViewport.top);
-            Region.right = std::min(Region.right, currentViewport.right);
-            Region.bottom = std::min(Region.bottom, currentViewport.bottom);
-
-            if (Region)
-            {
-                Region.left = std::max(Region.left, convRegion.Left());
-                Region.top = std::max(Region.top, convRegion.Top());
-                Region.right = std::min(Region.right, convRegion.RightInclusive());
-                Region.bottom = std::min(Region.bottom, convRegion.BottomInclusive());
-                if (Region)
-                {
-                    // if we have a renderer, we need to update.
-                    // we've already confirmed (above with an early return) that we're on conversion areas that are a part of the active (visible/rendered) screen
-                    // so send invalidates to those regions such that we're queried for data on the next frame and repainted.
-                    if (ServiceLocator::LocateGlobals().pRender != nullptr)
-                    {
-                        ServiceLocator::LocateGlobals().pRender->TriggerRedraw(Viewport::FromInclusive(Region));
-                    }
-                }
-            }
-        }
-    }
-}
-
-[[nodiscard]] HRESULT ConsoleImeResizeCompStrView()
-{
-    try
-    {
-        auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-        const auto pIme = &gci.ConsoleIme;
-        pIme->RedrawCompMessage();
-    }
-    CATCH_RETURN();
-
-    return S_OK;
-}
-
-[[nodiscard]] HRESULT ConsoleImeResizeCompStrScreenBuffer(const til::size coordNewScreenSize)
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    const auto pIme = &gci.ConsoleIme;
-
-    return pIme->ResizeAllAreas(coordNewScreenSize);
-}
-
-[[nodiscard]] HRESULT ImeStartComposition()
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    gci.LockConsole();
-    auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
-
-    // MSFT:29219348 Some IME implementations do not produce composition strings, and
-    // their users have come to rely on the cursor that conhost traditionally left on
-    // until a composition string showed up.
-    // One such IME is WNWB's "Universal Wubi input method" from wnwb.com (v. 10+).
-    // We shouldn't hide the cursor here so as to not break those IMEs.
-
-    gci.pInputBuffer->fInComposition = true;
-    return S_OK;
-}
-
-[[nodiscard]] HRESULT ImeEndComposition()
-{
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    gci.LockConsole();
-    auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
-
-    const auto pIme = &gci.ConsoleIme;
-    pIme->RestoreCursorVisibility();
-
-    gci.pInputBuffer->fInComposition = false;
-    return S_OK;
-}
-
-[[nodiscard]] HRESULT ImeComposeData(std::wstring_view text,
-                                     std::span<const BYTE> attributes,
-                                     std::span<const WORD> colorArray)
-{
-    try
-    {
-        auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-        gci.LockConsole();
-        auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
-
-        const auto pIme = &gci.ConsoleIme;
-        pIme->WriteCompMessage(text, attributes, colorArray);
-    }
-    CATCH_RETURN();
-    return S_OK;
-}
-
-[[nodiscard]] HRESULT ImeClearComposeData()
-{
-    try
-    {
-        auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-        gci.LockConsole();
-        auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
-
-        const auto pIme = &gci.ConsoleIme;
-        pIme->ClearAllAreas();
-    }
-    CATCH_RETURN();
-    return S_OK;
-}
-
-[[nodiscard]] HRESULT ImeComposeResult(std::wstring_view text)
-{
-    try
-    {
-        auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-        gci.LockConsole();
-        auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
-
-        const auto pIme = &gci.ConsoleIme;
-        pIme->WriteResultMessage(text);
-    }
-    CATCH_RETURN();
-    return S_OK;
-}
diff --git a/src/host/getset.cpp b/src/host/getset.cpp
index 304b4fd65c7..3595da335cf 100644
--- a/src/host/getset.cpp
+++ b/src/host/getset.cpp
@@ -717,8 +717,6 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
 
         RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true));
 
-        LOG_IF_FAILED(ConsoleImeResizeCompStrView());
-
         // Attempt to "snap" the viewport to the cursor position. If the cursor
         // is not in the current viewport, we'll try and move the viewport so
         // that the cursor is visible.
@@ -976,7 +974,6 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
 [[nodiscard]] HRESULT ApiRoutines::SetConsoleTextAttributeImpl(SCREEN_INFORMATION& context,
                                                                const WORD attribute) noexcept
 {
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
     try
     {
         LockConsole();
@@ -987,8 +984,6 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
         const TextAttribute attr{ attribute };
         context.SetAttributes(attr);
 
-        gci.ConsoleIme.RefreshAreaAttributes();
-
         return S_OK;
     }
     CATCH_RETURN();
diff --git a/src/host/globals.h b/src/host/globals.h
index 9cebbc0808a..812bf059c41 100644
--- a/src/host/globals.h
+++ b/src/host/globals.h
@@ -25,6 +25,7 @@ Revision History:
 #include "../propslib/DelegationConfig.hpp"
 #include "../renderer/base/Renderer.hpp"
 #include "../server/DeviceComm.h"
+#include "../tsf/Handle.h"
 #include "../server/ConDrvDeviceComm.h"
 
 #include <TraceLoggingProvider.h>
@@ -60,9 +61,8 @@ class Globals
     DWORD dwInputThreadId;
 
     std::vector<wchar_t> WordDelimiters;
-
     Microsoft::Console::Render::Renderer* pRender;
-
+    Microsoft::Console::TSF::Handle tsf;
     Microsoft::Console::Render::IFontDefaultList* pFontDefaultList;
 
     bool IsHeadless() const;
diff --git a/src/host/host-common.vcxitems b/src/host/host-common.vcxitems
index 3456180ff1f..9e51ab38c88 100644
--- a/src/host/host-common.vcxitems
+++ b/src/host/host-common.vcxitems
@@ -6,10 +6,7 @@
     <ClCompile Include="..\ConsoleArguments.cpp" />
     <ClCompile Include="..\CursorBlinker.cpp" />
     <ClCompile Include="..\readDataCooked.cpp" />
-    <ClCompile Include="..\conareainfo.cpp" />
-    <ClCompile Include="..\conimeinfo.cpp" />
     <ClCompile Include="..\consoleInformation.cpp" />
-    <ClCompile Include="..\convarea.cpp" />
     <ClCompile Include="..\dbcs.cpp" />
     <ClCompile Include="..\directio.cpp" />
     <ClCompile Include="..\getset.cpp" />
@@ -58,11 +55,8 @@
     <ClInclude Include="..\cmdline.h" />
     <ClInclude Include="..\conapi.h" />
     <ClInclude Include="..\conddkrefs.h" />
-    <ClInclude Include="..\conareainfo.h" />
-    <ClInclude Include="..\conimeinfo.h" />
     <ClInclude Include="..\ConsoleArguments.hpp" />
     <ClInclude Include="..\conserv.h" />
-    <ClInclude Include="..\conv.h" />
     <ClInclude Include="..\conwinuserrefs.h" />
     <ClInclude Include="..\CursorBlinker.hpp" />
     <ClInclude Include="..\dbcs.h" />
diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp
index 2e28a17ef67..a843a01082a 100644
--- a/src/host/inputBuffer.cpp
+++ b/src/host/inputBuffer.cpp
@@ -27,8 +27,6 @@ using namespace Microsoft::Console;
 InputBuffer::InputBuffer() :
     InputMode{ INPUT_BUFFER_DEFAULT_INPUT_MODE }
 {
-    // initialize buffer header
-    fInComposition = false;
 }
 
 // Transfer as many `wchar_t`s from source over to the `char`/`wchar_t` buffer `target`. After it returns,
diff --git a/src/host/inputBuffer.hpp b/src/host/inputBuffer.hpp
index 019c6e628bd..653f04e36f1 100644
--- a/src/host/inputBuffer.hpp
+++ b/src/host/inputBuffer.hpp
@@ -23,7 +23,6 @@ class InputBuffer final : public ConsoleObjectHeader
 public:
     DWORD InputMode;
     ConsoleWaitQueue WaitQueue; // formerly ReadWaitQueue
-    bool fInComposition; // specifies if there's an ongoing text composition
 
     InputBuffer();
 
diff --git a/src/host/lib/hostlib.vcxproj.filters b/src/host/lib/hostlib.vcxproj.filters
index c70ea1a4d9f..a2e17a06cd9 100644
--- a/src/host/lib/hostlib.vcxproj.filters
+++ b/src/host/lib/hostlib.vcxproj.filters
@@ -27,9 +27,6 @@
     <ClCompile Include="..\consoleInformation.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\convarea.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\dbcs.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -105,9 +102,6 @@
     <ClCompile Include="..\utils.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\conimeinfo.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\ntprivapi.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -150,9 +144,6 @@
     <ClCompile Include="..\PtySignalInputThread.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\conareainfo.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\CursorBlinker.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -191,9 +182,6 @@
     <ClInclude Include="..\stream.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\telemetry.hpp">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\tracing.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -215,15 +203,9 @@
     <ClInclude Include="..\conddkrefs.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\conimeinfo.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\conserv.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\conv.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\conwinuserrefs.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -299,9 +281,6 @@
     <ClInclude Include="..\history.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\conareainfo.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\CursorBlinker.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -313,4 +292,4 @@
     <Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
     <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
   </ItemGroup>
-</Project>
+</Project>
\ No newline at end of file
diff --git a/src/host/output.cpp b/src/host/output.cpp
index bf1e0a91040..1958e0671a4 100644
--- a/src/host/output.cpp
+++ b/src/host/output.cpp
@@ -464,8 +464,6 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
     // Set window size.
     screenInfo.PostUpdateWindowSize();
 
-    gci.ConsoleIme.RefreshAreaAttributes();
-
     // Write data to screen.
     WriteToScreen(screenInfo, screenInfo.GetViewport());
 }
diff --git a/src/host/precomp.h b/src/host/precomp.h
index e28931ebaf4..11787fc7b10 100644
--- a/src/host/precomp.h
+++ b/src/host/precomp.h
@@ -48,8 +48,6 @@ Module Name:
 #include <dde.h>
 #include "conserv.h"
 
-#include "conv.h"
-
 #pragma prefast(push)
 #pragma prefast(disable : 26071, "Range violation in Intsafe. Not ours.")
 #define ENABLE_INTSAFE_SIGNED_FUNCTIONS // Only unsigned intsafe math/casts available without this def
@@ -84,7 +82,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hConhostV2EventTraceProvider);
 #define CON_DPIAPI_INDIRECT
 #endif
 
-#include "../inc/contsf.h"
 #include "../inc/conattrs.hpp"
 
 // TODO: MSFT 9355094 Find a better way of doing this. http://osgvsowi/9355094
diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp
index d9b094626ba..9b549dec65f 100644
--- a/src/host/renderData.cpp
+++ b/src/host/renderData.cpp
@@ -42,9 +42,9 @@ til::point RenderData::GetTextBufferEndPosition() const noexcept
 //   the appropriate windowing.
 // Return Value:
 // - Text buffer with cell information for display
-const TextBuffer& RenderData::GetTextBuffer() const noexcept
+TextBuffer& RenderData::GetTextBuffer() const noexcept
 {
-    const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
+    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
     return gci.GetActiveOutputBuffer().GetTextBuffer();
 }
 
@@ -210,47 +210,6 @@ ULONG RenderData::GetCursorPixelWidth() const noexcept
     return ServiceLocator::LocateGlobals().cursorPixelWidth;
 }
 
-// Routine Description:
-// - Retrieves overlays to be drawn on top of the main screen buffer area.
-// - Overlays are drawn from first to last
-//  (the highest overlay should be given last)
-// Return Value:
-// - Iterable set of overlays
-const std::vector<Microsoft::Console::Render::RenderOverlay> RenderData::GetOverlays() const noexcept
-{
-    std::vector<Microsoft::Console::Render::RenderOverlay> overlays;
-
-    try
-    {
-        // First retrieve the IME information and build overlays.
-        const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-        const auto& ime = gci.ConsoleIme;
-
-        for (const auto& composition : ime.ConvAreaCompStr)
-        {
-            // Only send the overlay to the renderer on request if it's not supposed to be hidden at this moment.
-            if (!composition.IsHidden())
-            {
-                // This is holding the data.
-                const auto& textBuffer = composition.GetTextBuffer();
-
-                // The origin of the text buffer above (top left corner) is supposed to sit at this
-                // point within the visible viewport of the current window.
-                const auto origin = composition.GetAreaBufferInfo().coordConView;
-
-                // This is the area of the viewport that is actually in use relative to the text buffer itself.
-                // (e.g. 0,0 is the origin of the text buffer above, not the placement within the visible viewport)
-                const auto used = Viewport::FromInclusive(composition.GetAreaBufferInfo().rcViewCaWindow);
-
-                overlays.emplace_back(Microsoft::Console::Render::RenderOverlay{ textBuffer, origin, used });
-            }
-        }
-    }
-    CATCH_LOG();
-
-    return overlays;
-}
-
 // Method Description:
 // - Returns true if the cursor should be drawn twice as wide as usual because
 //      the cursor is currently over a cell with a double-wide character in it.
diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp
index 52056d7a6f5..4f2ec061acf 100644
--- a/src/host/renderData.hpp
+++ b/src/host/renderData.hpp
@@ -22,7 +22,7 @@ class RenderData final :
 public:
     Microsoft::Console::Types::Viewport GetViewport() noexcept override;
     til::point GetTextBufferEndPosition() const noexcept override;
-    const TextBuffer& GetTextBuffer() const noexcept override;
+    TextBuffer& GetTextBuffer() const noexcept override;
     const FontInfo& GetFontInfo() const noexcept override;
 
     std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override;
@@ -39,8 +39,6 @@ class RenderData final :
     ULONG GetCursorPixelWidth() const noexcept override;
     bool IsCursorDoubleWidth() const override;
 
-    const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
-
     const bool IsGridLineDrawingAllowed() noexcept override;
 
     const std::wstring_view GetConsoleTitle() const noexcept override;
diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp
index 9b0ff2ca4e4..daaedf87d7a 100644
--- a/src/host/screenInfo.cpp
+++ b/src/host/screenInfo.cpp
@@ -40,7 +40,6 @@ SCREEN_INFORMATION::SCREEN_INFORMATION(
     Next{ nullptr },
     WriteConsoleDbcsLeadByte{ 0, 0 },
     FillOutDbcsLeadChar{ 0 },
-    ConvScreenInfo{ nullptr },
     ScrollScale{ 1ul },
     _pConsoleWindowMetrics{ pMetrics },
     _pAccessibilityNotifier{ pNotifier },
@@ -1496,15 +1495,6 @@ NT_CATCH_RETURN()
             NotifyAccessibilityEventing(0, 0, coordNewScreenSize.width - 1, coordNewScreenSize.height - 1);
         }
 
-        if ((!ConvScreenInfo))
-        {
-            if (FAILED(ConsoleImeResizeCompStrScreenBuffer(coordNewScreenSize)))
-            {
-                // If something went wrong, just bail out.
-                return STATUS_INVALID_HANDLE;
-            }
-        }
-
         // Fire off an event to let accessibility apps know the layout has changed.
         if (_pAccessibilityNotifier && IsActiveScreenBuffer())
         {
@@ -2114,8 +2104,6 @@ void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
         _textBuffer->TriggerRedrawAll();
     }
 
-    gci.ConsoleIme.RefreshAreaAttributes();
-
     // If we're an alt buffer, also update our main buffer.
     if (_psiMainBuffer)
     {
diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp
index c0351050566..035529e0d6e 100644
--- a/src/host/screenInfo.hpp
+++ b/src/host/screenInfo.hpp
@@ -176,9 +176,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console
     BYTE WriteConsoleDbcsLeadByte[2];
     BYTE FillOutDbcsLeadChar;
 
-    // non ownership pointer
-    ConversionAreaInfo* ConvScreenInfo;
-
     UINT ScrollScale;
 
     bool IsActiveScreenBuffer() const;
diff --git a/src/host/server.h b/src/host/server.h
index cfa1ba14dc6..0cf24ef42ff 100644
--- a/src/host/server.h
+++ b/src/host/server.h
@@ -16,7 +16,6 @@ Revision History:
 
 #pragma once
 
-#include "conimeinfo.h"
 #include "CursorBlinker.hpp"
 #include "IIoProvider.hpp"
 #include "readDataCooked.hpp"
@@ -98,8 +97,6 @@ class CONSOLE_INFORMATION :
     CPINFO CPInfo = {};
     CPINFO OutputCPInfo = {};
 
-    ConsoleImeInfo ConsoleIme;
-
     void LockConsole() noexcept;
     void UnlockConsole() noexcept;
     til::recursive_ticket_lock_suspension SuspendLock() noexcept;
diff --git a/src/host/sources.inc b/src/host/sources.inc
index 0c6b8fd14d2..adf9ee9fb4f 100644
--- a/src/host/sources.inc
+++ b/src/host/sources.inc
@@ -67,7 +67,6 @@ SOURCES = \
     ..\outputStream.cpp \
     ..\stream.cpp    \
     ..\dbcs.cpp      \
-    ..\convarea.cpp  \
     ..\screenInfo.cpp \
     ..\_output.cpp   \
     ..\_stream.cpp   \
@@ -83,8 +82,6 @@ SOURCES = \
     ..\writeData.cpp \
     ..\renderData.cpp \
     ..\renderFontDefaults.cpp \
-    ..\conareainfo.cpp \
-    ..\conimeinfo.cpp \
     ..\ConsoleArguments.cpp \
 
 
diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp
index 23d2d3a9c7e..70d08233ab2 100644
--- a/src/host/ut_host/VtIoTests.cpp
+++ b/src/host/ut_host/VtIoTests.cpp
@@ -259,7 +259,7 @@ class MockRenderData : public IRenderData
         return {};
     }
 
-    const TextBuffer& GetTextBuffer() const noexcept override
+    TextBuffer& GetTextBuffer() const noexcept override
     {
         FAIL_FAST_HR(E_NOTIMPL);
     }
@@ -327,11 +327,6 @@ class MockRenderData : public IRenderData
         return false;
     }
 
-    const std::vector<RenderOverlay> GetOverlays() const noexcept override
-    {
-        return std::vector<RenderOverlay>{};
-    }
-
     const bool IsGridLineDrawingAllowed() noexcept override
     {
         return false;
diff --git a/src/inc/conime.h b/src/inc/conime.h
deleted file mode 100644
index 96ca7c3e0b6..00000000000
--- a/src/inc/conime.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    conime.h
-
-Abstract:
-
-    This module contains the internal structures and definitions used
-    by the console IME.
-
-Author:
-
-    v-HirShi Jul.4.1995
-
-Revision History:
-
---*/
-
-#pragma once
-
-constexpr unsigned short CONIME_ATTRCOLOR_SIZE = 8;
-
-constexpr BYTE CONIME_CURSOR_RIGHT = 0x10;
-constexpr BYTE CONIME_CURSOR_LEFT = 0x20;
-
-[[nodiscard]] HRESULT ImeStartComposition();
-
-[[nodiscard]] HRESULT ImeEndComposition();
-
-[[nodiscard]] HRESULT ImeComposeData(std::wstring_view text,
-                                     std::span<const BYTE> attributes,
-                                     std::span<const WORD> colorArray);
-
-[[nodiscard]] HRESULT ImeClearComposeData();
-
-[[nodiscard]] HRESULT ImeComposeResult(std::wstring_view text);
-
-// Default composition color attributes
-#define DEFAULT_COMP_ENTERED                               \
-    (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | \
-     COMMON_LVB_UNDERSCORE)
-#define DEFAULT_COMP_ALREADY_CONVERTED                     \
-    (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | \
-     BACKGROUND_BLUE)
-#define DEFAULT_COMP_CONVERSION                            \
-    (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | \
-     COMMON_LVB_UNDERSCORE)
-#define DEFAULT_COMP_YET_CONVERTED                         \
-    (FOREGROUND_BLUE |                                     \
-     BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | \
-     COMMON_LVB_UNDERSCORE)
-#define DEFAULT_COMP_INPUT_ERROR \
-    (FOREGROUND_RED |            \
-     COMMON_LVB_UNDERSCORE)
diff --git a/src/inc/contsf.h b/src/inc/contsf.h
deleted file mode 100644
index 07594e104eb..00000000000
--- a/src/inc/contsf.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    contsf.h
-
-Abstract:
-
-    This module contains the internal structures and definitions used
-    by the console IME.
-
-Author:
-
-    v-HirShi Jul.4.1995
-
-Revision History:
-
---*/
-
-#pragma once
-
-#include "conime.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef RECT (*GetSuggestionWindowPos)();
-typedef RECT (*GetTextBoxAreaPos)();
-
-BOOL ActivateTextServices(HWND hwndConsole, GetSuggestionWindowPos pfnPosition, GetTextBoxAreaPos pfnTextArea);
-void DeactivateTextServices();
-BOOL NotifyTextServices(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* lplResult);
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/src/interactivity/win32/CustomWindowMessages.h b/src/interactivity/win32/CustomWindowMessages.h
index c203df2cac4..bf366602499 100644
--- a/src/interactivity/win32/CustomWindowMessages.h
+++ b/src/interactivity/win32/CustomWindowMessages.h
@@ -15,7 +15,7 @@
 // unused (CM_CONSOLE_SHUTDOWN)  (WM_USER + 7)
 // unused (CM_HIDE_WINDOW)       (WM_USER + 8)
 #define CM_CONIME_CREATE         (WM_USER+9)
-#define CM_SET_CONSOLEIME_WINDOW (WM_USER+10)
+// unused #define CM_SET_CONSOLEIME_WINDOW (WM_USER+10)
 #define CM_WAIT_CONIME_PROCESS   (WM_USER+11)
 // unused CM_SET_IME_CODEPAGE      (WM_USER+12)
 // unused CM_SET_NLSMODE           (WM_USER+13)
diff --git a/src/interactivity/win32/WindowIme.cpp b/src/interactivity/win32/WindowIme.cpp
deleted file mode 100644
index f66338d7de3..00000000000
--- a/src/interactivity/win32/WindowIme.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-
-#include "../inc/ServiceLocator.hpp"
-
-#include "window.hpp"
-
-#pragma hdrstop
-
-// Routine Description:
-// - This method gives a rectangle to where the command edit line text is currently rendered
-//   such that the IME suggestion window can pop up in a suitable location adjacent to the given rectangle.
-// Arguments:
-// - <none>
-// Return Value:
-// - Rectangle specifying current command line edit area.
-RECT GetImeSuggestionWindowPos()
-{
-    using Microsoft::Console::Interactivity::ServiceLocator;
-    const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    const auto& screenBuffer = gci.GetActiveOutputBuffer();
-
-    const auto coordFont = screenBuffer.GetCurrentFont().GetSize();
-    auto coordCursor = screenBuffer.GetTextBuffer().GetCursor().GetPosition();
-
-    // Adjust the cursor position to be relative to the viewport.
-    // This means that if the cursor is at row 30 in the buffer but the viewport is showing rows 20-40 right now on screen
-    // that the "relative" position is that it is on the 11th line from the top (or 10th by index).
-    // Correct by subtracting the top/left corner from the cursor's position.
-    const auto srViewport = screenBuffer.GetViewport().ToInclusive();
-    coordCursor.x -= srViewport.left;
-    coordCursor.y -= srViewport.top;
-
-    // Map the point to be just under the current cursor position. Convert from coordinate to pixels using font.
-    POINT ptSuggestion;
-    ptSuggestion.x = (coordCursor.x + 1) * coordFont.width;
-    ptSuggestion.y = (coordCursor.y) * coordFont.height;
-
-    // Adjust client point to screen point via HWND.
-    ClientToScreen(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), &ptSuggestion);
-
-    // Move into suggestion rectangle.
-    RECT rcSuggestion{};
-    rcSuggestion.top = rcSuggestion.bottom = ptSuggestion.y;
-    rcSuggestion.left = rcSuggestion.right = ptSuggestion.x;
-
-    // Add 1 line height and a few characters of width to represent the area where we're writing text.
-    // This could be more exact by looking up the CONVAREA but it works well enough this way.
-    // If there is a future issue with the pop-up window, tweak these metrics.
-    rcSuggestion.bottom += coordFont.height;
-    rcSuggestion.right += (coordFont.width * 10);
-
-    return rcSuggestion;
-}
-
-// Routine Description:
-// - This method gives a rectangle to where text box is currently rendered
-//   such that the touch keyboard can pop up when the rectangle is tapped.
-// Arguments:
-// - <none>
-// Return Value:
-// - Rectangle specifying current text box area.
-RECT GetTextBoxArea()
-{
-    return Microsoft::Console::Interactivity::ServiceLocator::LocateConsoleWindow()->GetWindowRect().to_win32_rect();
-}
diff --git a/src/interactivity/win32/lib/win32.LIB.vcxproj b/src/interactivity/win32/lib/win32.LIB.vcxproj
index 1988841434b..6d6df33f5f8 100644
--- a/src/interactivity/win32/lib/win32.LIB.vcxproj
+++ b/src/interactivity/win32/lib/win32.LIB.vcxproj
@@ -32,7 +32,6 @@
     <ClCompile Include="..\uiaTextRange.cpp" />
     <ClCompile Include="..\Window.cpp" />
     <ClCompile Include="..\WindowDpiApi.cpp" />
-    <ClCompile Include="..\WindowIme.cpp" />
     <ClCompile Include="..\WindowIo.cpp" />
     <ClCompile Include="..\WindowMetrics.cpp" />
     <ClCompile Include="..\WindowProc.cpp" />
@@ -54,7 +53,6 @@
     <ClInclude Include="..\uiaTextRange.hpp" />
     <ClInclude Include="..\Window.hpp" />
     <ClInclude Include="..\WindowDpiApi.hpp" />
-    <ClInclude Include="..\WindowIme.hpp" />
     <ClInclude Include="..\WindowMetrics.hpp" />
     <ClInclude Include="..\WindowIo.hpp" />
     <ClInclude Include="..\windowUiaProvider.hpp" />
diff --git a/src/interactivity/win32/lib/win32.LIB.vcxproj.filters b/src/interactivity/win32/lib/win32.LIB.vcxproj.filters
index 52ecd14f5b7..afe3fd0fba3 100644
--- a/src/interactivity/win32/lib/win32.LIB.vcxproj.filters
+++ b/src/interactivity/win32/lib/win32.LIB.vcxproj.filters
@@ -48,9 +48,6 @@
     <ClCompile Include="..\Window.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\WindowIme.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\WindowDpiApi.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -110,9 +107,6 @@
     <ClInclude Include="..\Window.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\WindowIme.hpp">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\WindowDpiApi.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/src/interactivity/win32/menu.cpp b/src/interactivity/win32/menu.cpp
index 44e803931ff..e9736728a74 100644
--- a/src/interactivity/win32/menu.cpp
+++ b/src/interactivity/win32/menu.cpp
@@ -595,8 +595,6 @@ void Menu::s_PropertiesUpdate(PCONSOLE_STATE_INFO pStateInfo)
     // those properties specifically from the registry in case they were changed.
     ServiceLocator::LocateConsoleWindow<Window>()->PostUpdateExtendedEditKeys();
 
-    gci.ConsoleIme.RefreshAreaAttributes();
-
     gci.SetInterceptCopyPaste(!!pStateInfo->InterceptCopyPaste);
 }
 
diff --git a/src/interactivity/win32/sources.inc b/src/interactivity/win32/sources.inc
index 3580a5d6186..f816c285db0 100644
--- a/src/interactivity/win32/sources.inc
+++ b/src/interactivity/win32/sources.inc
@@ -50,7 +50,6 @@ SOURCES = \
     ..\UiaTextRange.cpp \
     ..\window.cpp \
     ..\windowdpiapi.cpp \
-    ..\windowime.cpp \
     ..\windowio.cpp \
     ..\WindowMetrics.cpp \
     ..\windowproc.cpp \
diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp
index 3ef07add747..ed42acb60d5 100644
--- a/src/interactivity/win32/window.cpp
+++ b/src/interactivity/win32/window.cpp
@@ -334,8 +334,6 @@ void Window::_UpdateSystemMetrics() const
 
                 if (SUCCEEDED_NTSTATUS(status))
                 {
-                    gci.ConsoleIme.RefreshAreaAttributes();
-
                     // Do WM_GETICON workaround. Must call WM_SETICON once or apps calling WM_GETICON will get null.
                     LOG_IF_FAILED(Icon::Instance().ApplyWindowMessageWorkaround(hWnd));
 
@@ -455,8 +453,6 @@ void Window::ChangeViewport(const til::inclusive_rect& NewWindow)
         Tracing::s_TraceWindowViewport(ScreenInfo.GetViewport());
     }
 
-    LOG_IF_FAILED(ConsoleImeResizeCompStrView());
-
     ScreenInfo.UpdateScrollBars();
 }
 
@@ -686,8 +682,6 @@ void Window::_UpdateWindowSize(const til::size sizeNew)
         _resizingWindow--;
     }
 
-    LOG_IF_FAILED(ConsoleImeResizeCompStrView());
-
     return STATUS_SUCCESS;
 }
 
diff --git a/src/interactivity/win32/windowime.hpp b/src/interactivity/win32/windowime.hpp
deleted file mode 100644
index dc7e6d241dc..00000000000
--- a/src/interactivity/win32/windowime.hpp
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-
-#pragma hdrstop
-
-RECT GetImeSuggestionWindowPos();
-RECT GetTextBoxArea();
diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp
index 94d7f3b2a86..261ed4a80c8 100644
--- a/src/interactivity/win32/windowio.cpp
+++ b/src/interactivity/win32/windowio.cpp
@@ -2,22 +2,16 @@
 // Licensed under the MIT license.
 
 #include "precomp.h"
-
 #include "windowio.hpp"
 
-#include "ConsoleControl.hpp"
-#include "find.h"
 #include "clipboard.hpp"
+#include "ConsoleControl.hpp"
 #include "consoleKeyInfo.hpp"
+#include "find.h"
 #include "window.hpp"
-
-#include "../../host/ApiRoutines.h"
-#include "../../host/init.hpp"
-#include "../../host/input.h"
 #include "../../host/handle.h"
+#include "../../host/init.hpp"
 #include "../../host/scrolling.hpp"
-#include "../../host/output.h"
-
 #include "../inc/ServiceLocator.hpp"
 
 #pragma hdrstop
@@ -125,7 +119,8 @@ void HandleKeyEvent(const HWND hWnd,
                     const LPARAM lParam,
                     _Inout_opt_ PBOOL pfUnlockConsole)
 {
-    auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
+    auto& g = ServiceLocator::LocateGlobals();
+    auto& gci = g.getConsoleInformation();
 
     // BOGUS for WM_CHAR/WM_DEADCHAR, in which LOWORD(wParam) is a character
     auto VirtualKeyCode = LOWORD(wParam);
@@ -153,7 +148,7 @@ void HandleKeyEvent(const HWND hWnd,
         RetrieveKeyInfo(hWnd,
                         &VirtualKeyCode,
                         &VirtualScanCode,
-                        !gci.pInputBuffer->fInComposition);
+                        !g.tsf.HasActiveComposition());
 
         // --- END LOAD BEARING CODE ---
     }
@@ -363,7 +358,7 @@ void HandleKeyEvent(const HWND hWnd,
         return;
     }
 
-    if (gci.pInputBuffer->fInComposition)
+    if (g.tsf.HasActiveComposition())
     {
         return;
     }
@@ -1003,9 +998,6 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/)
         // -- END LOAD BEARING CODE
     }
 
-    // Free all resources used by this thread
-    DeactivateTextServices();
-
     if (nullptr != hhook)
     {
         UnhookWindowsHookEx(hhook);
diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp
index 28202eebc9f..0dc8cf3853b 100644
--- a/src/interactivity/win32/windowproc.cpp
+++ b/src/interactivity/win32/windowproc.cpp
@@ -2,41 +2,116 @@
 // Licensed under the MIT license.
 
 #include "precomp.h"
+#include "window.hpp"
 
-#include "Clipboard.hpp"
-#include "ConsoleControl.hpp"
+#include "clipboard.hpp"
 #include "find.h"
 #include "menu.hpp"
-#include "window.hpp"
 #include "windowdpiapi.hpp"
-#include "windowime.hpp"
 #include "windowio.hpp"
 #include "windowmetrics.hpp"
-
-#include "../../host/_output.h"
-#include "../../host/output.h"
-#include "../../host/dbcs.h"
 #include "../../host/handle.h"
-#include "../../host/input.h"
-#include "../../host/misc.h"
 #include "../../host/registry.hpp"
 #include "../../host/scrolling.hpp"
-#include "../../host/srvinit.h"
-
-#include "../inc/ServiceLocator.hpp"
-
 #include "../../inc/conint.h"
-
+#include "../inc/ServiceLocator.hpp"
 #include "../interactivity/win32/CustomWindowMessages.h"
-
 #include "../interactivity/win32/windowUiaProvider.hpp"
 
-#include <iomanip>
-#include <sstream>
-
 using namespace Microsoft::Console::Interactivity::Win32;
 using namespace Microsoft::Console::Types;
 
+// NOTE: We put this struct into a `static constexpr` (= ".rodata", read-only data segment), which means it
+// cannot have any mutable members right now. If you need any, you have to make it a non-const `static`.
+struct TsfDataProvider : Microsoft::Console::TSF::IDataProvider
+{
+    virtual ~TsfDataProvider() = default;
+
+    STDMETHODIMP TsfDataProvider::QueryInterface(REFIID, void**) noexcept override
+    {
+        return E_NOTIMPL;
+    }
+
+    ULONG STDMETHODCALLTYPE TsfDataProvider::AddRef() noexcept override
+    {
+        return 1;
+    }
+
+    ULONG STDMETHODCALLTYPE TsfDataProvider::Release() noexcept override
+    {
+        return 1;
+    }
+
+    HWND GetHwnd() override
+    {
+        return Microsoft::Console::Interactivity::ServiceLocator::LocateConsoleWindow()->GetWindowHandle();
+    }
+
+    RECT GetViewport() override
+    {
+        const auto hwnd = GetHwnd();
+
+        RECT rc;
+        GetClientRect(hwnd, &rc);
+
+        // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect
+        // > The left and top members are zero. The right and bottom members contain the width and height of the window.
+        // --> We can turn the client rect into a screen-relative rect by adding the left/top position.
+        ClientToScreen(hwnd, reinterpret_cast<POINT*>(&rc));
+        rc.right += rc.left;
+        rc.bottom += rc.top;
+
+        return rc;
+    }
+
+    RECT GetCursorPosition() override
+    {
+        const auto& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
+        const auto& screenBuffer = gci.GetActiveOutputBuffer();
+
+        // Map the absolute cursor position to a viewport-relative one.
+        const auto viewport = screenBuffer.GetViewport().ToExclusive();
+        auto coordCursor = screenBuffer.GetTextBuffer().GetCursor().GetPosition();
+        coordCursor.x -= viewport.left;
+        coordCursor.y -= viewport.top;
+
+        coordCursor.x = std::clamp(coordCursor.x, 0, viewport.width() - 1);
+        coordCursor.y = std::clamp(coordCursor.y, 0, viewport.height() - 1);
+
+        // Convert from columns/rows to pixels.
+        const auto coordFont = screenBuffer.GetCurrentFont().GetSize();
+        POINT ptSuggestion{
+            .x = coordCursor.x * coordFont.width,
+            .y = coordCursor.y * coordFont.height,
+        };
+
+        ClientToScreen(GetHwnd(), &ptSuggestion);
+
+        return {
+            .left = ptSuggestion.x,
+            .top = ptSuggestion.y,
+            .right = ptSuggestion.x + coordFont.width,
+            .bottom = ptSuggestion.y + coordFont.height,
+        };
+    }
+
+    void HandleOutput(std::wstring_view text) override
+    {
+        auto& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
+        gci.LockConsole();
+        const auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
+        gci.GetActiveInputBuffer()->WriteString(text);
+    }
+
+    Microsoft::Console::Render::Renderer* GetRenderer() override
+    {
+        auto& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
+        return g.pRender;
+    }
+};
+
+static constexpr TsfDataProvider s_tsfDataProvider;
+
 // The static and specific window procedures for this class are contained here
 #pragma region Window Procedure
 
@@ -256,8 +331,11 @@ using namespace Microsoft::Console::Types;
 
         HandleFocusEvent(TRUE);
 
-        // ActivateTextServices does nothing if already active so this is OK to be called every focus.
-        ActivateTextServices(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), GetImeSuggestionWindowPos, GetTextBoxArea);
+        if (!g.tsf)
+        {
+            g.tsf = TSF::Handle::Create();
+            g.tsf.AssociateFocus(const_cast<TsfDataProvider*>(&s_tsfDataProvider));
+        }
 
         // set the text area to have focus for accessibility consumers
         if (_pUiaProvider)
@@ -855,7 +933,9 @@ void Window::_HandleWindowPosChanged(const LPARAM lParam)
 // - <none>
 void Window::_HandleDrop(const WPARAM wParam) const
 {
-    Clipboard::Instance().PasteDrop((HDROP)wParam);
+    const auto drop = reinterpret_cast<HDROP>(wParam);
+    Clipboard::Instance().PasteDrop(drop);
+    DragFinish(drop);
 }
 
 [[nodiscard]] LRESULT Window::_HandleGetObject(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
@@ -887,11 +967,6 @@ BOOL Window::PostUpdateWindowSize() const
     auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
     const auto& ScreenInfo = GetScreenInfo();
 
-    if (ScreenInfo.ConvScreenInfo != nullptr)
-    {
-        return FALSE;
-    }
-
     if (gci.Flags & CONSOLE_SETTING_WINDOW_SIZE)
     {
         return FALSE;
diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp
index 8cfbe995a14..b3335057231 100644
--- a/src/renderer/atlas/AtlasEngine.cpp
+++ b/src/renderer/atlas/AtlasEngine.cpp
@@ -18,6 +18,7 @@
 // and thus may access both _r and _api.
 
 #pragma warning(disable : 4100) // '...': unreferenced formal parameter
+#pragma warning(disable : 4127) // conditional expression is constant
 // Disable a bunch of warnings which get in the way of writing performant code.
 #pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
 #pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp
index 4137d140d36..94d492ad39a 100644
--- a/src/renderer/base/renderer.cpp
+++ b/src/renderer/base/renderer.cpp
@@ -56,6 +56,11 @@ Renderer::~Renderer()
     _pThread.reset();
 }
 
+IRenderData* Renderer::GetRenderData() const noexcept
+{
+    return _pData;
+}
+
 // Routine Description:
 // - Walks through the console data structures to compose a new frame based on the data that has changed since last call and outputs it to the connected rendering engine.
 // Arguments:
@@ -118,24 +123,94 @@ Renderer::~Renderer()
 
         if (_currentCursorOptions)
         {
-            const auto coord = _currentCursorOptions->coordCursor;
             const auto& buffer = _pData->GetTextBuffer();
-            const auto lineRendition = buffer.GetLineRendition(coord.y);
-            const auto cursorWidth = _pData->IsCursorDoubleWidth() ? 2 : 1;
-
-            til::rect cursorRect{ coord.x, coord.y, coord.x + cursorWidth, coord.y + 1 };
-            cursorRect = BufferToScreenLine(cursorRect, lineRendition);
+            const auto view = buffer.GetSize();
+            const auto coord = _currentCursorOptions->coordCursor;
 
-            if (buffer.GetSize().TrimToViewport(&cursorRect))
+            // If we had previously drawn a composition at the previous cursor position
+            // we need to invalidate the entire line because who knows what changed.
+            // (It's possible to figure that out, but not worth the effort right now.)
+            if (_compositionCache)
             {
-                FOREACH_ENGINE(pEngine)
+                til::rect rect{ 0, coord.y, til::CoordTypeMax, coord.y + 1 };
+                if (view.TrimToViewport(&rect))
+                {
+                    FOREACH_ENGINE(pEngine)
+                    {
+                        LOG_IF_FAILED(pEngine->Invalidate(&rect));
+                    }
+                }
+            }
+            else
+            {
+                const auto lineRendition = buffer.GetLineRendition(coord.y);
+                const auto cursorWidth = _pData->IsCursorDoubleWidth() ? 2 : 1;
+
+                til::rect rect{ coord.x, coord.y, coord.x + cursorWidth, coord.y + 1 };
+                rect = BufferToScreenLine(rect, lineRendition);
+
+                if (view.TrimToViewport(&rect))
                 {
-                    LOG_IF_FAILED(pEngine->InvalidateCursor(&cursorRect));
+                    FOREACH_ENGINE(pEngine)
+                    {
+                        LOG_IF_FAILED(pEngine->InvalidateCursor(&rect));
+                    }
                 }
             }
         }
 
         _currentCursorOptions = _GetCursorInfo();
+        _compositionCache.reset();
+
+        // Invalidate the line that the active TSF composition is on,
+        // so that _PaintBufferOutput() actually gets a chance to draw it.
+        if (!_pData->activeComposition.text.empty())
+        {
+            const auto viewport = _pData->GetViewport();
+            const auto coordCursor = _pData->GetCursorPosition();
+
+            til::rect line{ 0, coordCursor.y, til::CoordTypeMax, coordCursor.y + 1 };
+            if (viewport.TrimToViewport(&line))
+            {
+                viewport.ConvertToOrigin(&line);
+
+                FOREACH_ENGINE(pEngine)
+                {
+                    LOG_IF_FAILED(pEngine->Invalidate(&line));
+                }
+
+                auto& buffer = _pData->GetTextBuffer();
+                auto& scratch = buffer.GetScratchpadRow();
+
+                std::wstring_view text{ _pData->activeComposition.text };
+                RowWriteState state{
+                    .columnLimit = buffer.GetRowByOffset(line.top).GetReadableColumnCount(),
+                };
+
+                state.text = text.substr(0, _pData->activeComposition.cursorPos);
+                scratch.ReplaceText(state);
+                const auto cursorOffset = state.columnEnd;
+
+                state.text = text.substr(_pData->activeComposition.cursorPos);
+                state.columnBegin = state.columnEnd;
+                scratch.ReplaceText(state);
+
+                // Ideally the text is inserted at the position of the cursor (`coordCursor`),
+                // but if we got more text than fits into the remaining space until the end of the line,
+                // then we'll insert the text aligned to the end of the line.
+                const auto remaining = state.columnLimit - state.columnEnd;
+                const auto beg = std::clamp(coordCursor.x, 0, remaining);
+
+                const auto baseAttribute = buffer.GetRowByOffset(coordCursor.y).GetAttrByColumn(coordCursor.x);
+                _compositionCache.emplace(til::point{ beg, coordCursor.y }, baseAttribute);
+
+                // Fake-move the cursor to where it needs to be in the active composition.
+                if (_currentCursorOptions)
+                {
+                    _currentCursorOptions->coordCursor.x = std::min(beg + cursorOffset, line.right - 1);
+                }
+            }
+        }
 
         FOREACH_ENGINE(pEngine)
         {
@@ -195,9 +270,6 @@ try
     // 2. Paint Rows of Text
     _PaintBufferOutput(pEngine);
 
-    // 3. Paint overlays that reside above the text buffer
-    _PaintOverlays(pEngine);
-
     // 4. Paint Selection
     _PaintSelection(pEngine);
 
@@ -692,6 +764,7 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
     // It can move left/right or top/bottom depending on how the viewport is scrolled
     // relative to the entire buffer.
     const auto view = _pData->GetViewport();
+    const auto compositionRow = _compositionCache ? _compositionCache->absoluteOrigin.y : -1;
 
     // This is effectively the number of cells on the visible screen that need to be redrawn.
     // The origin is always 0, 0 because it represents the screen itself, not the underlying buffer.
@@ -722,7 +795,7 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
         const auto redraw = Viewport::Intersect(dirty, view);
 
         // Retrieve the text buffer so we can read information out of it.
-        const auto& buffer = _pData->GetTextBuffer();
+        auto& buffer = _pData->GetTextBuffer();
 
         // Now walk through each row of text that we need to redraw.
         for (auto row = redraw.Top(); row < redraw.BottomExclusive(); row++)
@@ -730,6 +803,53 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
             // Calculate the boundaries of a single line. This is from the left to right edge of the dirty
             // area in width and exactly 1 tall.
             const auto screenLine = til::inclusive_rect{ redraw.Left(), row, redraw.RightInclusive(), row };
+            const auto& r = buffer.GetRowByOffset(row);
+
+            // Draw the active composition.
+            // We have to use some tricks here with const_cast, because the code after it relies on TextBufferCellIterator,
+            // which isn't compatible with the scratchpad row. This forces us to back up and modify the actual row `r`.
+            ROW* rowBackup = nullptr;
+            if (row == compositionRow)
+            {
+                auto& scratch = buffer.GetScratchpadRow();
+                scratch.CopyFrom(r);
+                rowBackup = &scratch;
+
+                std::wstring_view text{ _pData->activeComposition.text };
+                RowWriteState state{
+                    .columnLimit = r.GetReadableColumnCount(),
+                    .columnEnd = _compositionCache->absoluteOrigin.x,
+                };
+
+                size_t off = 0;
+                for (const auto& range : _pData->activeComposition.attributes)
+                {
+                    const auto len = range.len;
+                    auto attr = range.attr;
+
+                    // Use the color at the cursor if TSF didn't specify any explicit color.
+                    if (attr.GetBackground().IsDefault())
+                    {
+                        attr.SetBackground(_compositionCache->baseAttribute.GetBackground());
+                    }
+                    if (attr.GetForeground().IsDefault())
+                    {
+                        attr.SetForeground(_compositionCache->baseAttribute.GetForeground());
+                    }
+
+                    state.text = text.substr(off, len);
+                    state.columnBegin = state.columnEnd;
+                    const_cast<ROW&>(r).ReplaceText(state);
+                    const_cast<ROW&>(r).ReplaceAttributes(state.columnBegin, state.columnEnd, attr);
+                    off += len;
+                }
+            }
+            const auto restore = wil::scope_exit([&] {
+                if (rowBackup)
+                {
+                    const_cast<ROW&>(r).CopyFrom(*rowBackup);
+                }
+            });
 
             // Convert the screen coordinates of the line to an equivalent
             // range of buffer cells, taking line rendition into account.
@@ -1133,70 +1253,6 @@ void Renderer::_PaintCursor(_In_ IRenderEngine* const pEngine)
     return pEngine->PrepareRenderInfo(info);
 }
 
-// Routine Description:
-// - Paint helper to draw text that overlays the main buffer to provide user interactivity regions
-// - This supports IME composition.
-// Arguments:
-// - engine - The render engine that we're targeting.
-// - overlay - The overlay to draw.
-// Return Value:
-// - <none>
-void Renderer::_PaintOverlay(IRenderEngine& engine,
-                             const RenderOverlay& overlay)
-{
-    try
-    {
-        // Now get the overlay's viewport and adjust it to where it is supposed to be relative to the window.
-        auto srCaView = overlay.region.ToExclusive();
-        srCaView.top += overlay.origin.y;
-        srCaView.bottom += overlay.origin.y;
-        srCaView.left += overlay.origin.x;
-        srCaView.right += overlay.origin.x;
-
-        std::span<const til::rect> dirtyAreas;
-        LOG_IF_FAILED(engine.GetDirtyArea(dirtyAreas));
-
-        for (const auto& rect : dirtyAreas)
-        {
-            if (const auto viewDirty = rect & srCaView)
-            {
-                for (auto iRow = viewDirty.top; iRow < viewDirty.bottom; iRow++)
-                {
-                    const til::point target{ viewDirty.left, iRow };
-                    const auto source = target - overlay.origin;
-
-                    auto it = overlay.buffer.GetCellLineDataAt(source);
-
-                    _PaintBufferOutputHelper(&engine, it, target, false);
-                }
-            }
-        }
-    }
-    CATCH_LOG();
-}
-
-// Routine Description:
-// - Paint helper to draw the composition string portion of the IME.
-// - This specifically is the string that appears at the cursor on the input line showing what the user is currently typing.
-// - See also: Generic Paint IME helper method.
-// Arguments:
-// - <none>
-// Return Value:
-// - <none>
-void Renderer::_PaintOverlays(_In_ IRenderEngine* const pEngine)
-{
-    try
-    {
-        const auto overlays = _pData->GetOverlays();
-
-        for (const auto& overlay : overlays)
-        {
-            _PaintOverlay(*pEngine, overlay);
-        }
-    }
-    CATCH_LOG();
-}
-
 // Routine Description:
 // - Paint helper to draw the selected area of the window.
 // Arguments:
diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp
index dc027acea20..2b651177f14 100644
--- a/src/renderer/base/renderer.hpp
+++ b/src/renderer/base/renderer.hpp
@@ -44,6 +44,8 @@ namespace Microsoft::Console::Render
 
         ~Renderer();
 
+        IRenderData* GetRenderData() const noexcept;
+
         [[nodiscard]] HRESULT PaintFrame();
 
         void NotifyPaintFrame() noexcept;
@@ -92,6 +94,14 @@ namespace Microsoft::Console::Render
         void UpdateLastHoveredInterval(const std::optional<interval_tree::IntervalTree<til::point, size_t>::interval>& newInterval);
 
     private:
+        // Caches some essential information about the active composition.
+        // This allows us to properly invalidate it between frames, etc.
+        struct CompositionCache
+        {
+            til::point absoluteOrigin;
+            TextAttribute baseAttribute;
+        };
+
         static GridLineSet s_GetGridlines(const TextAttribute& textAttribute) noexcept;
         static bool s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar);
 
@@ -105,8 +115,6 @@ namespace Microsoft::Console::Render
         bool _isHoveredHyperlink(const TextAttribute& textAttribute) const noexcept;
         void _PaintSelection(_In_ IRenderEngine* const pEngine);
         void _PaintCursor(_In_ IRenderEngine* const pEngine);
-        void _PaintOverlays(_In_ IRenderEngine* const pEngine);
-        void _PaintOverlay(IRenderEngine& engine, const RenderOverlay& overlay);
         [[nodiscard]] HRESULT _UpdateDrawingBrushes(_In_ IRenderEngine* const pEngine, const TextAttribute attr, const bool usingSoftFont, const bool isSettingDefaultBrushes);
         [[nodiscard]] HRESULT _PerformScrolling(_In_ IRenderEngine* const pEngine);
         std::vector<til::rect> _GetSelectionRects() const;
@@ -127,6 +135,7 @@ namespace Microsoft::Console::Render
         std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _hoveredInterval;
         Microsoft::Console::Types::Viewport _viewport;
         std::optional<CursorOptions> _currentCursorOptions;
+        std::optional<CompositionCache> _compositionCache;
         std::vector<Cluster> _clusterBuffer;
         std::vector<til::rect> _previousSelection;
         std::vector<til::rect> _previousSearchSelection;
diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp
index cfc035a7f9b..f13dc2959de 100644
--- a/src/renderer/inc/IRenderData.hpp
+++ b/src/renderer/inc/IRenderData.hpp
@@ -14,26 +14,26 @@ Author(s):
 
 #pragma once
 
-#include "../../host/conimeinfo.h"
 #include "../../buffer/out/TextAttribute.hpp"
+#include "../../renderer/inc/FontInfo.hpp"
+#include "../../types/inc/viewport.hpp"
 
 class Cursor;
+class TextBuffer;
 
 namespace Microsoft::Console::Render
 {
-    struct RenderOverlay final
+    struct CompositionRange
     {
-        // This is where the data is stored
-        const TextBuffer& buffer;
-
-        // This is where the top left of the stored buffer should be overlaid on the screen
-        // (relative to the current visible viewport)
-        const til::point origin;
+        size_t len; // The number of chars in Composition::text that this .attr applies to
+        TextAttribute attr;
+    };
 
-        // This is the area of the buffer that is actually used for overlay.
-        // Anything outside of this is considered empty by the overlay and shouldn't be used
-        // for painting purposes.
-        const Microsoft::Console::Types::Viewport region;
+    struct Composition
+    {
+        std::wstring text;
+        til::small_vector<CompositionRange, 2> attributes;
+        size_t cursorPos = 0;
     };
 
     class IRenderData
@@ -44,7 +44,7 @@ namespace Microsoft::Console::Render
         // This block used to be IBaseData.
         virtual Microsoft::Console::Types::Viewport GetViewport() noexcept = 0;
         virtual til::point GetTextBufferEndPosition() const noexcept = 0;
-        virtual const TextBuffer& GetTextBuffer() const noexcept = 0;
+        virtual TextBuffer& GetTextBuffer() const noexcept = 0;
         virtual const FontInfo& GetFontInfo() const noexcept = 0;
         virtual std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept = 0;
         virtual std::vector<Microsoft::Console::Types::Viewport> GetSearchSelectionRects() noexcept = 0;
@@ -59,7 +59,6 @@ namespace Microsoft::Console::Render
         virtual CursorType GetCursorStyle() const noexcept = 0;
         virtual ULONG GetCursorPixelWidth() const noexcept = 0;
         virtual bool IsCursorDoubleWidth() const = 0;
-        virtual const std::vector<RenderOverlay> GetOverlays() const noexcept = 0;
         virtual const bool IsGridLineDrawingAllowed() noexcept = 0;
         virtual const std::wstring_view GetConsoleTitle() const noexcept = 0;
         virtual const std::wstring GetHyperlinkUri(uint16_t id) const = 0;
@@ -76,5 +75,10 @@ namespace Microsoft::Console::Render
         virtual const til::point GetSelectionAnchor() const noexcept = 0;
         virtual const til::point GetSelectionEnd() const noexcept = 0;
         virtual const bool IsUiaDataInitialized() const noexcept = 0;
+
+        // Ideally this would not be stored on an interface, however ideally IRenderData should not be an interface in the first place.
+        // This is because we should have only 1 way how to represent render data across the codebase anyway, and it should
+        // be by-value in a struct so that we can snapshot it and release the terminal lock as quickly as possible.
+        Composition activeComposition;
     };
 }
diff --git a/src/tsf/ConsoleTSF.cpp b/src/tsf/ConsoleTSF.cpp
deleted file mode 100644
index c3e1b04b33a..00000000000
--- a/src/tsf/ConsoleTSF.cpp
+++ /dev/null
@@ -1,549 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-#include "TfConvArea.h"
-#include "TfEditSession.h"
-
-/* 626761ad-78d2-44d2-be8b-752cf122acec */
-const GUID GUID_APPLICATION = { 0x626761ad, 0x78d2, 0x44d2, { 0xbe, 0x8b, 0x75, 0x2c, 0xf1, 0x22, 0xac, 0xec } };
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::Initialize
-//
-//----------------------------------------------------------------------------
-
-#define Init_CheckResult() \
-    if (FAILED(hr))        \
-    {                      \
-        Uninitialize();    \
-        return hr;         \
-    }
-
-[[nodiscard]] HRESULT CConsoleTSF::Initialize()
-{
-    HRESULT hr;
-
-    if (_spITfThreadMgr)
-    {
-        return S_FALSE;
-    }
-
-    // Activate per-thread Cicero in custom UI mode (TF_TMAE_UIELEMENTENABLEDONLY).
-
-    hr = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
-    Init_CheckResult();
-    _fCoInitialized = TRUE;
-
-    hr = ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&_spITfThreadMgr));
-    Init_CheckResult();
-
-    hr = _spITfThreadMgr->ActivateEx(&_tid, TF_TMAE_CONSOLE);
-    Init_CheckResult();
-
-    // Create Cicero document manager and input context.
-
-    hr = _spITfThreadMgr->CreateDocumentMgr(&_spITfDocumentMgr);
-    Init_CheckResult();
-
-    TfEditCookie ecTmp;
-    hr = _spITfDocumentMgr->CreateContext(_tid,
-                                          0,
-                                          static_cast<ITfContextOwnerCompositionSink*>(this),
-                                          &_spITfInputContext,
-                                          &ecTmp);
-    Init_CheckResult();
-
-    // Set the context owner before attaching the context to the doc.
-    wil::com_ptr_nothrow<ITfSource> spSrcIC;
-    hr = _spITfInputContext.query_to(&spSrcIC);
-    Init_CheckResult();
-
-    hr = spSrcIC->AdviseSink(IID_ITfContextOwner, static_cast<ITfContextOwner*>(this), &_dwContextOwnerCookie);
-    Init_CheckResult();
-
-    hr = _spITfDocumentMgr->Push(_spITfInputContext.get());
-    Init_CheckResult();
-
-    // Collect the active keyboard layout info.
-
-    wil::com_ptr_nothrow<ITfInputProcessorProfileMgr> spITfProfilesMgr;
-    hr = ::CoCreateInstance(CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spITfProfilesMgr));
-    if (SUCCEEDED(hr))
-    {
-        TF_INPUTPROCESSORPROFILE ipp;
-        hr = spITfProfilesMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &ipp);
-        if (SUCCEEDED(hr))
-        {
-            OnActivated(ipp.dwProfileType, ipp.langid, ipp.clsid, ipp.catid, ipp.guidProfile, ipp.hkl, ipp.dwFlags);
-        }
-    }
-    Init_CheckResult();
-
-    // Setup some useful Cicero event sinks and callbacks.
-    // _spITfThreadMgr && _spITfInputContext must be non-null for checks above to have succeeded, so
-    // we're not going to check them again here. try_query will A/V if they're null.
-    auto spSrcTIM = _spITfThreadMgr.try_query<ITfSource>();
-    auto spSrcICS = _spITfInputContext.try_query<ITfSourceSingle>();
-
-    hr = (spSrcTIM && spSrcIC && spSrcICS) ? S_OK : E_FAIL;
-    Init_CheckResult();
-
-    hr = spSrcTIM->AdviseSink(IID_ITfInputProcessorProfileActivationSink,
-                              static_cast<ITfInputProcessorProfileActivationSink*>(this),
-                              &_dwActivationSinkCookie);
-    Init_CheckResult();
-
-    hr = spSrcTIM->AdviseSink(IID_ITfUIElementSink, static_cast<ITfUIElementSink*>(this), &_dwUIElementSinkCookie);
-    Init_CheckResult();
-
-    hr = spSrcIC->AdviseSink(IID_ITfTextEditSink, static_cast<ITfTextEditSink*>(this), &_dwTextEditSinkCookie);
-    Init_CheckResult();
-
-    hr = spSrcICS->AdviseSingleSink(_tid, IID_ITfCleanupContextSink, static_cast<ITfCleanupContextSink*>(this));
-    Init_CheckResult();
-
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::Uninitialize()
-//
-//----------------------------------------------------------------------------
-
-void CConsoleTSF::Uninitialize()
-{
-    // Destroy the current conversion area object
-
-    if (_pConversionArea)
-    {
-        delete _pConversionArea;
-        _pConversionArea = nullptr;
-    }
-
-    // Detach Cicero event sinks.
-    if (_spITfInputContext)
-    {
-        auto spSrcICS = _spITfInputContext.try_query<ITfSourceSingle>();
-        if (spSrcICS)
-        {
-            spSrcICS->UnadviseSingleSink(_tid, IID_ITfCleanupContextSink);
-        }
-    }
-
-    // Associate the document\context with the console window.
-
-    if (_spITfThreadMgr)
-    {
-        auto spSrcTIM = _spITfThreadMgr.try_query<ITfSource>();
-        if (spSrcTIM)
-        {
-            if (_dwUIElementSinkCookie)
-            {
-                spSrcTIM->UnadviseSink(_dwUIElementSinkCookie);
-            }
-            if (_dwActivationSinkCookie)
-            {
-                spSrcTIM->UnadviseSink(_dwActivationSinkCookie);
-            }
-        }
-    }
-
-    _dwUIElementSinkCookie = 0;
-    _dwActivationSinkCookie = 0;
-
-    if (_spITfInputContext)
-    {
-        auto spSrcIC = _spITfInputContext.try_query<ITfSource>();
-        if (spSrcIC)
-        {
-            if (_dwContextOwnerCookie)
-            {
-                spSrcIC->UnadviseSink(_dwContextOwnerCookie);
-            }
-            if (_dwTextEditSinkCookie)
-            {
-                spSrcIC->UnadviseSink(_dwTextEditSinkCookie);
-            }
-        }
-    }
-    _dwContextOwnerCookie = 0;
-    _dwTextEditSinkCookie = 0;
-
-    // Clear the Cicero reference to our document manager.
-
-    if (_spITfThreadMgr && _spITfDocumentMgr)
-    {
-        wil::com_ptr_nothrow<ITfDocumentMgr> spDocMgr;
-        _spITfThreadMgr->AssociateFocus(_hwndConsole, nullptr, &spDocMgr);
-    }
-
-    // Dismiss the input context and document manager.
-
-    if (_spITfDocumentMgr)
-    {
-        _spITfDocumentMgr->Pop(TF_POPF_ALL);
-    }
-
-    _spITfInputContext.reset();
-    _spITfDocumentMgr.reset();
-
-    // Deactivate per-thread Cicero and uninitialize COM.
-
-    if (_spITfThreadMgr)
-    {
-        _spITfThreadMgr->Deactivate();
-        _spITfThreadMgr.reset();
-    }
-    if (_fCoInitialized)
-    {
-        ::CoUninitialize();
-        _fCoInitialized = FALSE;
-    }
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::IUnknown::QueryInterface
-// CConsoleTSF::IUnknown::AddRef
-// CConsoleTSF::IUnknown::Release
-//
-//----------------------------------------------------------------------------
-
-STDMETHODIMP CConsoleTSF::QueryInterface(REFIID riid, void** ppvObj)
-{
-    if (!ppvObj)
-    {
-        return E_FAIL;
-    }
-    *ppvObj = nullptr;
-
-    if (IsEqualIID(riid, IID_ITfCleanupContextSink))
-    {
-        *ppvObj = static_cast<ITfCleanupContextSink*>(this);
-    }
-    else if (IsEqualGUID(riid, IID_ITfContextOwnerCompositionSink))
-    {
-        *ppvObj = static_cast<ITfContextOwnerCompositionSink*>(this);
-    }
-    else if (IsEqualIID(riid, IID_ITfUIElementSink))
-    {
-        *ppvObj = static_cast<ITfUIElementSink*>(this);
-    }
-    else if (IsEqualIID(riid, IID_ITfContextOwner))
-    {
-        *ppvObj = static_cast<ITfContextOwner*>(this);
-    }
-    else if (IsEqualIID(riid, IID_ITfInputProcessorProfileActivationSink))
-    {
-        *ppvObj = static_cast<ITfInputProcessorProfileActivationSink*>(this);
-    }
-    else if (IsEqualIID(riid, IID_ITfTextEditSink))
-    {
-        *ppvObj = static_cast<ITfTextEditSink*>(this);
-    }
-    else if (IsEqualGUID(riid, IID_IUnknown))
-    {
-        *ppvObj = this;
-    }
-    if (*ppvObj)
-    {
-        AddRef();
-    }
-    return (*ppvObj) ? S_OK : E_NOINTERFACE;
-}
-
-STDAPI_(ULONG)
-CConsoleTSF::AddRef()
-{
-    return InterlockedIncrement(&_cRef);
-}
-
-STDAPI_(ULONG)
-CConsoleTSF::Release()
-{
-    auto cr = InterlockedDecrement(&_cRef);
-    if (cr == 0)
-    {
-        if (g_pConsoleTSF == this)
-        {
-            g_pConsoleTSF = nullptr;
-        }
-        delete this;
-    }
-    return cr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::ITfCleanupContextSink::OnCleanupContext
-//
-//----------------------------------------------------------------------------
-
-STDMETHODIMP CConsoleTSF::OnCleanupContext(TfEditCookie ecWrite, ITfContext* pic)
-{
-    //
-    // Remove GUID_PROP_COMPOSING
-    //
-    wil::com_ptr_nothrow<ITfProperty> prop;
-    if (SUCCEEDED(pic->GetProperty(GUID_PROP_COMPOSING, &prop)))
-    {
-        wil::com_ptr_nothrow<IEnumTfRanges> enumranges;
-        if (SUCCEEDED(prop->EnumRanges(ecWrite, &enumranges, nullptr)))
-        {
-            wil::com_ptr_nothrow<ITfRange> rangeTmp;
-            while (enumranges->Next(1, &rangeTmp, nullptr) == S_OK)
-            {
-                VARIANT var;
-                VariantInit(&var);
-                prop->GetValue(ecWrite, rangeTmp.get(), &var);
-                if ((var.vt == VT_I4) && (var.lVal != 0))
-                {
-                    prop->Clear(ecWrite, rangeTmp.get());
-                }
-            }
-        }
-    }
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::ITfContextOwnerCompositionSink::OnStartComposition
-// CConsoleTSF::ITfContextOwnerCompositionSink::OnUpdateComposition
-// CConsoleTSF::ITfContextOwnerCompositionSink::OnEndComposition
-//
-//----------------------------------------------------------------------------
-
-STDMETHODIMP CConsoleTSF::OnStartComposition(ITfCompositionView* pCompView, BOOL* pfOk)
-{
-    if (!_pConversionArea || (_cCompositions > 0 && (!_fModifyingDoc)))
-    {
-        *pfOk = FALSE;
-    }
-    else
-    {
-        *pfOk = TRUE;
-        // Ignore compositions triggered by our own edit sessions
-        // (i.e. when the application is the composition owner)
-        auto clsidCompositionOwner = GUID_APPLICATION;
-        pCompView->GetOwnerClsid(&clsidCompositionOwner);
-        if (!IsEqualGUID(clsidCompositionOwner, GUID_APPLICATION))
-        {
-            _cCompositions++;
-            if (_cCompositions == 1)
-            {
-                LOG_IF_FAILED(ImeStartComposition());
-            }
-        }
-    }
-    return S_OK;
-}
-
-STDMETHODIMP CConsoleTSF::OnUpdateComposition(ITfCompositionView* /*pComp*/, ITfRange*)
-{
-    return S_OK;
-}
-
-STDMETHODIMP CConsoleTSF::OnEndComposition(ITfCompositionView* pCompView)
-{
-    if (!_cCompositions || !_pConversionArea)
-    {
-        return E_FAIL;
-    }
-    // Ignore compositions triggered by our own edit sessions
-    // (i.e. when the application is the composition owner)
-    auto clsidCompositionOwner = GUID_APPLICATION;
-    pCompView->GetOwnerClsid(&clsidCompositionOwner);
-    if (!IsEqualGUID(clsidCompositionOwner, GUID_APPLICATION))
-    {
-        _cCompositions--;
-        if (!_cCompositions)
-        {
-            LOG_IF_FAILED(_OnCompleteComposition());
-            LOG_IF_FAILED(ImeEndComposition());
-        }
-    }
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::ITfTextEditSink::OnEndEdit
-//
-//----------------------------------------------------------------------------
-
-STDMETHODIMP CConsoleTSF::OnEndEdit(ITfContext* pInputContext, TfEditCookie ecReadOnly, ITfEditRecord* pEditRecord)
-{
-    if (_cCompositions && _pConversionArea && _HasCompositionChanged(pInputContext, ecReadOnly, pEditRecord))
-    {
-        LOG_IF_FAILED(_OnUpdateComposition());
-    }
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::ITfInputProcessorProfileActivationSink::OnActivated
-//
-//----------------------------------------------------------------------------
-
-STDMETHODIMP CConsoleTSF::OnActivated(DWORD /*dwProfileType*/,
-                                      LANGID /*langid*/,
-                                      REFCLSID /*clsid*/,
-                                      REFGUID catid,
-                                      REFGUID /*guidProfile*/,
-                                      HKL /*hkl*/,
-                                      DWORD dwFlags)
-{
-    if (!(dwFlags & TF_IPSINK_FLAG_ACTIVE))
-    {
-        return S_OK;
-    }
-    if (!IsEqualGUID(catid, GUID_TFCAT_TIP_KEYBOARD))
-    {
-        // Don't care for non-keyboard profiles.
-        return S_OK;
-    }
-
-    try
-    {
-        CreateConversionArea();
-    }
-    CATCH_RETURN();
-
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::ITfUIElementSink::BeginUIElement
-//
-//----------------------------------------------------------------------------
-
-STDMETHODIMP CConsoleTSF::BeginUIElement(DWORD /*dwUIElementId*/, BOOL* pbShow)
-{
-    *pbShow = TRUE;
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::ITfUIElementSink::UpdateUIElement
-//
-//----------------------------------------------------------------------------
-
-STDMETHODIMP CConsoleTSF::UpdateUIElement(DWORD /*dwUIElementId*/)
-{
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::ITfUIElementSink::EndUIElement
-//
-//----------------------------------------------------------------------------
-
-STDMETHODIMP CConsoleTSF::EndUIElement(DWORD /*dwUIElementId*/)
-{
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::CreateConversionAreaService
-//
-//----------------------------------------------------------------------------
-
-CConversionArea* CConsoleTSF::CreateConversionArea()
-{
-    BOOL fHadConvArea = (_pConversionArea != nullptr);
-
-    if (!_pConversionArea)
-    {
-        _pConversionArea = new CConversionArea();
-    }
-
-    // Associate the document\context with the console window.
-    if (!fHadConvArea)
-    {
-        wil::com_ptr_nothrow<ITfDocumentMgr> spPrevDocMgr;
-        _spITfThreadMgr->AssociateFocus(_hwndConsole, _pConversionArea ? _spITfDocumentMgr.get() : nullptr, &spPrevDocMgr);
-    }
-
-    return _pConversionArea;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::OnUpdateComposition()
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CConsoleTSF::_OnUpdateComposition()
-{
-    if (_fEditSessionRequested)
-    {
-        return S_FALSE;
-    }
-
-    auto hr = E_OUTOFMEMORY;
-    auto pEditSession = new (std::nothrow) CEditSessionUpdateCompositionString();
-    if (pEditSession)
-    {
-        // Can't use TF_ES_SYNC because called from OnEndEdit.
-        _fEditSessionRequested = TRUE;
-        _spITfInputContext->RequestEditSession(_tid, pEditSession, TF_ES_READWRITE, &hr);
-        if (FAILED(hr))
-        {
-            pEditSession->Release();
-            _fEditSessionRequested = FALSE;
-        }
-    }
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::OnCompleteComposition()
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CConsoleTSF::_OnCompleteComposition()
-{
-    // Update the composition area.
-
-    auto hr = E_OUTOFMEMORY;
-    auto pEditSession = new (std::nothrow) CEditSessionCompositionComplete();
-    if (pEditSession)
-    {
-        // The composition could have been finalized because of a caret move, therefore it must be
-        // inserted synchronously while at the original caret position.(TF_ES_SYNC is ok for a nested RO session).
-        _spITfInputContext->RequestEditSession(_tid, pEditSession, TF_ES_READ | TF_ES_SYNC, &hr);
-        if (FAILED(hr))
-        {
-            pEditSession->Release();
-        }
-    }
-
-    // Cleanup (empty the context range) after the last composition, unless a new one has started.
-    if (!_fCleanupSessionRequested)
-    {
-        _fCleanupSessionRequested = TRUE;
-        auto pEditSessionCleanup = new (std::nothrow) CEditSessionCompositionCleanup();
-        if (pEditSessionCleanup)
-        {
-            // Can't use TF_ES_SYNC because requesting RW while called within another session.
-            // For the same reason, must use explicit TF_ES_ASYNC, or the request will be rejected otherwise.
-            _spITfInputContext->RequestEditSession(_tid, pEditSessionCleanup, TF_ES_READWRITE | TF_ES_ASYNC, &hr);
-            if (FAILED(hr))
-            {
-                pEditSessionCleanup->Release();
-                _fCleanupSessionRequested = FALSE;
-            }
-        }
-    }
-    return hr;
-}
diff --git a/src/tsf/ConsoleTSF.h b/src/tsf/ConsoleTSF.h
deleted file mode 100644
index 9a1b582bf22..00000000000
--- a/src/tsf/ConsoleTSF.h
+++ /dev/null
@@ -1,220 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfContext.h
-
-Abstract:
-
-    This file defines the CConsoleTSF Interface Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#pragma once
-
-class CConversionArea;
-
-class CConsoleTSF final :
-    public ITfContextOwner,
-    public ITfContextOwnerCompositionSink,
-    public ITfInputProcessorProfileActivationSink,
-    public ITfUIElementSink,
-    public ITfCleanupContextSink,
-    public ITfTextEditSink
-{
-public:
-    CConsoleTSF(HWND hwndConsole,
-                GetSuggestionWindowPos pfnPosition,
-                GetTextBoxAreaPos pfnTextArea) :
-        _hwndConsole(hwndConsole),
-        _pfnPosition(pfnPosition),
-        _pfnTextArea(pfnTextArea),
-        _cRef(1),
-        _tid()
-    {
-    }
-
-    virtual ~CConsoleTSF() = default;
-    [[nodiscard]] HRESULT Initialize();
-    void Uninitialize();
-
-public:
-    // IUnknown methods
-    STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj);
-    STDMETHODIMP_(ULONG)
-    AddRef(void);
-    STDMETHODIMP_(ULONG)
-    Release(void);
-
-    // ITfContextOwner
-    STDMETHODIMP GetACPFromPoint(const POINT*, DWORD, LONG* pCP)
-    {
-        if (pCP)
-        {
-            *pCP = 0;
-        }
-
-        return S_OK;
-    }
-
-    // This returns Rectangle of the text box of whole console.
-    // When a user taps inside the rectangle while hardware keyboard is not available,
-    // touch keyboard is invoked.
-    STDMETHODIMP GetScreenExt(RECT* pRect)
-    {
-        if (pRect)
-        {
-            *pRect = _pfnTextArea();
-        }
-
-        return S_OK;
-    }
-
-    // This returns rectangle of current command line edit area.
-    // When a user types in East Asian language, candidate window is shown at this position.
-    // Emoji and more panel (Win+.) is shown at the position, too.
-    STDMETHODIMP GetTextExt(LONG, LONG, RECT* pRect, BOOL* pbClipped)
-    {
-        if (pRect)
-        {
-            *pRect = _pfnPosition();
-        }
-
-        if (pbClipped)
-        {
-            *pbClipped = FALSE;
-        }
-
-        return S_OK;
-    }
-
-    STDMETHODIMP GetStatus(TF_STATUS* pTfStatus)
-    {
-        if (pTfStatus)
-        {
-            pTfStatus->dwDynamicFlags = 0;
-            pTfStatus->dwStaticFlags = TF_SS_TRANSITORY;
-        }
-        return pTfStatus ? S_OK : E_INVALIDARG;
-    }
-    STDMETHODIMP GetWnd(HWND* phwnd)
-    {
-        *phwnd = _hwndConsole;
-        return S_OK;
-    }
-    STDMETHODIMP GetAttribute(REFGUID, VARIANT*)
-    {
-        return E_NOTIMPL;
-    }
-
-    // ITfContextOwnerCompositionSink methods
-    STDMETHODIMP OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk);
-    STDMETHODIMP OnUpdateComposition(ITfCompositionView* pComposition, ITfRange* pRangeNew);
-    STDMETHODIMP OnEndComposition(ITfCompositionView* pComposition);
-
-    // ITfInputProcessorProfileActivationSink
-    STDMETHODIMP OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid, REFGUID guidProfile, HKL hkl, DWORD dwFlags);
-
-    // ITfUIElementSink methods
-    STDMETHODIMP BeginUIElement(DWORD dwUIElementId, BOOL* pbShow);
-    STDMETHODIMP UpdateUIElement(DWORD dwUIElementId);
-    STDMETHODIMP EndUIElement(DWORD dwUIElementId);
-
-    // ITfCleanupContextSink methods
-    STDMETHODIMP OnCleanupContext(TfEditCookie ecWrite, ITfContext* pic);
-
-    // ITfTextEditSink methods
-    STDMETHODIMP OnEndEdit(ITfContext* pInputContext, TfEditCookie ecReadOnly, ITfEditRecord* pEditRecord);
-
-public:
-    CConversionArea* CreateConversionArea();
-    CConversionArea* GetConversionArea() { return _pConversionArea; }
-    ITfContext* GetInputContext() { return _spITfInputContext.get(); }
-    HWND GetConsoleHwnd() { return _hwndConsole; }
-    TfClientId GetTfClientId() { return _tid; }
-    BOOL IsInComposition() { return (_cCompositions > 0); }
-    void OnEditSession() { _fEditSessionRequested = FALSE; }
-    BOOL IsPendingCompositionCleanup() { return _fCleanupSessionRequested || _fCompositionCleanupSkipped; }
-    void OnCompositionCleanup(BOOL bSucceeded)
-    {
-        _fCleanupSessionRequested = FALSE;
-        _fCompositionCleanupSkipped = !bSucceeded;
-    }
-    void SetModifyingDocFlag(BOOL fSet) { _fModifyingDoc = fSet; }
-    void SetFocus(BOOL fSet)
-    {
-        if (!fSet && _cCompositions)
-        {
-            // Close (terminate) any open compositions when losing the input focus.
-            if (_spITfInputContext)
-            {
-                auto spCompositionServices = _spITfInputContext.try_query<ITfContextOwnerCompositionServices>();
-                if (spCompositionServices)
-                {
-                    spCompositionServices->TerminateComposition(nullptr);
-                }
-            }
-        }
-    }
-
-    // A workaround for a MS Korean IME scenario where the IME appends a whitespace
-    // composition programmatically right after completing a keyboard input composition.
-    // Since post-composition clean-up is an async operation, the programmatic whitespace
-    // composition gets completed before the previous composition cleanup happened,
-    // and this results in a double insertion of the first composition. To avoid that, we'll
-    // store the length of the last completed composition here until it's cleaned up.
-    // (for simplicity, this patch doesn't provide a generic solution for all possible
-    // scenarios with subsequent synchronous compositions, only for the known 'append').
-    long GetCompletedRangeLength() const { return _cchCompleted; }
-    void SetCompletedRangeLength(long cch) { _cchCompleted = cch; }
-
-private:
-    [[nodiscard]] HRESULT _OnUpdateComposition();
-    [[nodiscard]] HRESULT _OnCompleteComposition();
-    BOOL _HasCompositionChanged(ITfContext* pInputContext, TfEditCookie ecReadOnly, ITfEditRecord* pEditRecord);
-
-private:
-    // ref count.
-    DWORD _cRef;
-
-    // Cicero stuff.
-    TfClientId _tid;
-    wil::com_ptr_nothrow<ITfThreadMgrEx> _spITfThreadMgr;
-    wil::com_ptr_nothrow<ITfDocumentMgr> _spITfDocumentMgr;
-    wil::com_ptr_nothrow<ITfContext> _spITfInputContext;
-
-    // Event sink cookies.
-    DWORD _dwContextOwnerCookie = 0;
-    DWORD _dwUIElementSinkCookie = 0;
-    DWORD _dwTextEditSinkCookie = 0;
-    DWORD _dwActivationSinkCookie = 0;
-
-    // Conversion area object for the languages.
-    CConversionArea* _pConversionArea = nullptr;
-
-    // Console info.
-    HWND _hwndConsole;
-    GetSuggestionWindowPos _pfnPosition;
-    GetTextBoxAreaPos _pfnTextArea;
-
-    // Miscellaneous flags
-    BOOL _fModifyingDoc = FALSE; // Set TRUE, when calls ITfRange::SetText
-    BOOL _fCoInitialized = FALSE;
-    BOOL _fEditSessionRequested = FALSE;
-    BOOL _fCleanupSessionRequested = FALSE;
-    BOOL _fCompositionCleanupSkipped = FALSE;
-
-    int _cCompositions = 0;
-    long _cchCompleted = 0; // length of completed composition waiting for cleanup
-};
-
-extern CConsoleTSF* g_pConsoleTSF;
diff --git a/src/tsf/Handle.cpp b/src/tsf/Handle.cpp
new file mode 100644
index 00000000000..ab3f7c5f786
--- /dev/null
+++ b/src/tsf/Handle.cpp
@@ -0,0 +1,82 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "precomp.h"
+#include "Handle.h"
+
+#include "Implementation.h"
+
+using namespace Microsoft::Console::TSF;
+
+Handle Handle::Create()
+{
+    Handle handle;
+    handle._impl = new Implementation();
+    handle._impl->Initialize();
+    return handle;
+}
+
+Handle::~Handle()
+{
+    if (_impl)
+    {
+        _impl->Uninitialize();
+        _impl->Release();
+    }
+}
+
+Handle::Handle(Handle&& other) noexcept :
+    _impl{ other._impl }
+{
+    other._impl = nullptr;
+}
+
+Handle& Handle::operator=(Handle&& other) noexcept
+{
+    if (this != &other)
+    {
+        this->~Handle();
+        _impl = other._impl;
+        other._impl = nullptr;
+    }
+    return *this;
+}
+
+Handle::operator bool() const noexcept
+{
+    return _impl != nullptr;
+}
+
+HWND Handle::FindWindowOfActiveTSF() const noexcept
+{
+    return _impl ? _impl->FindWindowOfActiveTSF() : nullptr;
+}
+
+void Handle::AssociateFocus(IDataProvider* provider) const
+{
+    if (_impl)
+    {
+        _impl->AssociateFocus(provider);
+    }
+}
+
+void Handle::Focus(IDataProvider* provider) const
+{
+    if (_impl)
+    {
+        _impl->Focus(provider);
+    }
+}
+
+void Handle::Unfocus(IDataProvider* provider) const
+{
+    if (_impl)
+    {
+        _impl->Unfocus(provider);
+    }
+}
+
+bool Handle::HasActiveComposition() const noexcept
+{
+    return _impl ? _impl->HasActiveComposition() : false;
+}
diff --git a/src/tsf/Handle.h b/src/tsf/Handle.h
new file mode 100644
index 00000000000..fc56920ae40
--- /dev/null
+++ b/src/tsf/Handle.h
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+namespace Microsoft::Console::Render
+{
+    class Renderer;
+}
+
+namespace Microsoft::Console::TSF
+{
+    struct Implementation;
+
+    // It is fine for any of the IDataProvider functions to throw.
+    // However, this doesn't apply to the IUnknown ones.
+    //
+    // NOTE: This is a pure virtual interface, just like those for COM.
+    // It cannot have a `~IDataProvider() = default;` destructor, because then it would need a vtable.
+    MIDL_INTERFACE("A86B8AAF-1531-40F5-95BB-611AA9DBDC18")
+    IDataProvider : IUnknown
+    {
+        virtual HWND GetHwnd() = 0;
+        virtual RECT GetViewport() = 0;
+        virtual RECT GetCursorPosition() = 0;
+        virtual void HandleOutput(std::wstring_view text) = 0;
+        virtual Render::Renderer* GetRenderer() = 0;
+    };
+
+    // A pimpl idiom wrapper for `Implementation` so that we don't pull in all the TSF headers everywhere.
+    // Simultaneously it allows us to handle AdviseSink/UnadviseSink properly, because those hold strong
+    // references on `Implementation` which results in an (unfortunate but intentional) reference cycle.
+    struct Handle
+    {
+        static Handle Create();
+
+        Handle() = default;
+        ~Handle();
+        Handle(const Handle&) = delete;
+        Handle& operator=(const Handle&) = delete;
+        Handle(Handle&& other) noexcept;
+        Handle& operator=(Handle&& other) noexcept;
+
+        explicit operator bool() const noexcept;
+        HWND FindWindowOfActiveTSF() const noexcept;
+        void AssociateFocus(IDataProvider* provider) const;
+        void Focus(IDataProvider* provider) const;
+        void Unfocus(IDataProvider* provider) const;
+        bool HasActiveComposition() const noexcept;
+
+    private:
+        Implementation* _impl = nullptr;
+    };
+}
diff --git a/src/tsf/Implementation.cpp b/src/tsf/Implementation.cpp
new file mode 100644
index 00000000000..d3ad65ae9c5
--- /dev/null
+++ b/src/tsf/Implementation.cpp
@@ -0,0 +1,663 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "precomp.h"
+#include "Implementation.h"
+
+#include "Handle.h"
+#include "../buffer/out/TextAttribute.hpp"
+#include "../renderer/base/renderer.hpp"
+
+#pragma warning(disable : 4100) // '...': unreferenced formal parameter
+
+using namespace Microsoft::Console::TSF;
+
+static void TfSelectionClose(const TF_SELECTION* sel)
+{
+    if (const auto r = sel->range)
+    {
+        r->Release();
+    }
+}
+using unique_tf_selection = wil::unique_struct<TF_SELECTION, decltype(&TfSelectionClose), &TfSelectionClose>;
+
+static void TfPropertyvalClose(TF_PROPERTYVAL* val)
+{
+    VariantClear(&val->varValue);
+}
+using unique_tf_propertyval = wil::unique_struct<TF_PROPERTYVAL, decltype(&TfPropertyvalClose), &TfPropertyvalClose>;
+
+void Implementation::Initialize()
+{
+    _categoryMgr = wil::CoCreateInstance<ITfCategoryMgr>(CLSID_TF_CategoryMgr, CLSCTX_INPROC_SERVER);
+    _displayAttributeMgr = wil::CoCreateInstance<ITfDisplayAttributeMgr>(CLSID_TF_DisplayAttributeMgr);
+
+    // There's no point in calling TF_GetThreadMgr. ITfThreadMgr is a per-thread singleton.
+    _threadMgrEx = wil::CoCreateInstance<ITfThreadMgrEx>(CLSID_TF_ThreadMgr, CLSCTX_INPROC_SERVER);
+
+    THROW_IF_FAILED(_threadMgrEx->ActivateEx(&_clientId, TF_TMAE_CONSOLE));
+    THROW_IF_FAILED(_threadMgrEx->CreateDocumentMgr(_documentMgr.addressof()));
+
+    TfEditCookie ecTextStore;
+    THROW_IF_FAILED(_documentMgr->CreateContext(_clientId, 0, static_cast<ITfContextOwnerCompositionSink*>(this), _context.addressof(), &ecTextStore));
+
+    _contextSource = _context.query<ITfSource>();
+    THROW_IF_FAILED(_contextSource->AdviseSink(IID_ITfContextOwner, static_cast<ITfContextOwner*>(this), &_cookieContextOwner));
+    THROW_IF_FAILED(_contextSource->AdviseSink(IID_ITfTextEditSink, static_cast<ITfTextEditSink*>(this), &_cookieTextEditSink));
+
+    THROW_IF_FAILED(_documentMgr->Push(_context.get()));
+}
+
+void Implementation::Uninitialize() noexcept
+{
+    _provider.reset();
+
+    if (_associatedHwnd)
+    {
+        wil::com_ptr<ITfDocumentMgr> prev;
+        std::ignore = _threadMgrEx->AssociateFocus(_associatedHwnd, nullptr, prev.addressof());
+    }
+
+    if (_cookieTextEditSink != TF_INVALID_COOKIE)
+    {
+        std::ignore = _contextSource->UnadviseSink(_cookieTextEditSink);
+    }
+    if (_cookieContextOwner != TF_INVALID_COOKIE)
+    {
+        std::ignore = _contextSource->UnadviseSink(_cookieContextOwner);
+    }
+
+    if (_documentMgr)
+    {
+        std::ignore = _documentMgr->Pop(TF_POPF_ALL);
+    }
+    if (_threadMgrEx)
+    {
+        std::ignore = _threadMgrEx->Deactivate();
+    }
+}
+
+HWND Implementation::FindWindowOfActiveTSF() const noexcept
+{
+    wil::com_ptr<IEnumTfDocumentMgrs> enumDocumentMgrs;
+    if (FAILED_LOG(_threadMgrEx->EnumDocumentMgrs(enumDocumentMgrs.addressof())))
+    {
+        return nullptr;
+    }
+
+    wil::com_ptr<ITfDocumentMgr> document;
+    if (FAILED_LOG(enumDocumentMgrs->Next(1, document.addressof(), nullptr)))
+    {
+        return nullptr;
+    }
+
+    wil::com_ptr<ITfContext> context;
+    if (FAILED_LOG(document->GetTop(context.addressof())))
+    {
+        return nullptr;
+    }
+
+    wil::com_ptr<ITfContextView> view;
+    if (FAILED_LOG(context->GetActiveView(view.addressof())))
+    {
+        return nullptr;
+    }
+
+    HWND hwnd;
+    if (FAILED_LOG(view->GetWnd(&hwnd)))
+    {
+        return nullptr;
+    }
+
+    return hwnd;
+}
+
+void Implementation::AssociateFocus(IDataProvider* provider)
+{
+    _provider = provider;
+    _associatedHwnd = _provider->GetHwnd();
+
+    wil::com_ptr<ITfDocumentMgr> prev;
+    THROW_IF_FAILED(_threadMgrEx->AssociateFocus(_associatedHwnd, _documentMgr.get(), prev.addressof()));
+}
+
+void Implementation::Focus(IDataProvider* provider)
+{
+    _provider = provider;
+
+    THROW_IF_FAILED(_threadMgrEx->SetFocus(_documentMgr.get()));
+}
+
+void Implementation::Unfocus(IDataProvider* provider)
+{
+    if (!_provider || _provider != provider)
+    {
+        return;
+    }
+
+    {
+        const auto renderer = _provider->GetRenderer();
+        const auto renderData = renderer->GetRenderData();
+
+        renderData->LockConsole();
+        const auto unlock = wil::scope_exit([&]() {
+            renderData->UnlockConsole();
+        });
+
+        if (!renderData->activeComposition.text.empty())
+        {
+            auto& comp = renderData->activeComposition;
+            comp.text.clear();
+            comp.attributes.clear();
+            renderer->NotifyPaintFrame();
+        }
+    }
+
+    _provider.reset();
+
+    if (_compositions > 0)
+    {
+        if (const auto svc = _context.try_query<ITfContextOwnerCompositionServices>())
+        {
+            svc->TerminateComposition(nullptr);
+        }
+    }
+}
+
+bool Implementation::HasActiveComposition() const noexcept
+{
+    return _compositions > 0;
+}
+
+#pragma region IUnknown
+
+STDMETHODIMP Implementation::QueryInterface(REFIID riid, void** ppvObj) noexcept
+{
+    if (!ppvObj)
+    {
+        return E_POINTER;
+    }
+
+    if (IsEqualGUID(riid, IID_ITfContextOwner))
+    {
+        *ppvObj = static_cast<ITfContextOwner*>(this);
+    }
+    else if (IsEqualGUID(riid, IID_ITfContextOwnerCompositionSink))
+    {
+        *ppvObj = static_cast<ITfContextOwnerCompositionSink*>(this);
+    }
+    else if (IsEqualGUID(riid, IID_ITfTextEditSink))
+    {
+        *ppvObj = static_cast<ITfTextEditSink*>(this);
+    }
+    else if (IsEqualGUID(riid, IID_IUnknown))
+    {
+        *ppvObj = static_cast<IUnknown*>(static_cast<ITfContextOwner*>(this));
+    }
+    else
+    {
+        *ppvObj = nullptr;
+        return E_NOINTERFACE;
+    }
+
+    AddRef();
+    return S_OK;
+}
+
+ULONG STDMETHODCALLTYPE Implementation::AddRef() noexcept
+{
+    return InterlockedIncrement(&_referenceCount);
+}
+
+ULONG STDMETHODCALLTYPE Implementation::Release() noexcept
+{
+    const auto r = InterlockedDecrement(&_referenceCount);
+    if (r == 0)
+    {
+        delete this;
+    }
+    return r;
+}
+
+#pragma endregion IUnknown
+
+#pragma region ITfContextOwner
+
+STDMETHODIMP Implementation::GetACPFromPoint(const POINT* ptScreen, DWORD dwFlags, LONG* pacp) noexcept
+{
+    assert(false);
+    return E_NOTIMPL;
+}
+
+// This returns rectangle of current command line edit area.
+// When a user types in East Asian language, candidate window is shown at this position.
+// Emoji and more panel (Win+.) is shown at the position, too.
+STDMETHODIMP Implementation::GetTextExt(LONG acpStart, LONG acpEnd, RECT* prc, BOOL* pfClipped) noexcept
+try
+{
+    if (prc)
+    {
+        *prc = _provider ? _provider->GetCursorPosition() : RECT{};
+    }
+
+    if (pfClipped)
+    {
+        *pfClipped = FALSE;
+    }
+
+    return S_OK;
+}
+CATCH_RETURN()
+
+// This returns Rectangle of the text box of whole console.
+// When a user taps inside the rectangle while hardware keyboard is not available, touch keyboard is invoked.
+STDMETHODIMP Implementation::GetScreenExt(RECT* prc) noexcept
+try
+{
+    if (prc)
+    {
+        *prc = _provider ? _provider->GetViewport() : RECT{};
+    }
+
+    return S_OK;
+}
+CATCH_RETURN()
+
+STDMETHODIMP Implementation::GetStatus(TF_STATUS* pdcs) noexcept
+{
+    if (pdcs)
+    {
+        pdcs->dwDynamicFlags = 0;
+        // The use of TF_SS_TRANSITORY / TS_SS_TRANSITORY is incredibly important...
+        // ...and it has the least complete description:
+        // > TS_SS_TRANSITORY: The document is expected to have a short usage cycle.
+        //
+        // Proper documentation about the flag has been lost and can only be found via archive.org:
+        //   http://web.archive.org/web/20140520210042/http://blogs.msdn.com/b/tsfaware/archive/2007/04/25/transitory-contexts.aspx
+        // It states:
+        // > The most significant difference is that Transitory contexts don't retain state - once you end the composition [...],
+        // > any knowledge of the document (or any previous insertions/modifications/etc.) is gone.
+        // In other words, non-transitory contexts expect access to previously completed contents, which is something we cannot provide.
+        // Because once some text has finished composition we'll immediately send it to the shell via HandleOutput(), which we cannot undo.
+        // It's also the primary reason why we cannot use the WinRT CoreTextServices APIs, as they don't set TS_SS_TRANSITORY.
+        //
+        // Additionally, "short usage cycle" also significantly undersells another importance of the flag:
+        // If set, it enables CUAS, the Cicero Unaware Application Support, which is an emulation layer that fakes IMM32.
+        // Cicero is the internal code name for TSF. In other words, "TS_SS_TRANSITORY" = "Disable modern TSF".
+        // This results in a couple modern composition features not working (Korean reconversion primarily),
+        // but it's a trade-off we're forced to make, because otherwise it doesn't work at all.
+        //
+        // TS_SS_NOHIDDENTEXT tells TSF that we don't support TS_RT_HIDDEN, which is used if a document contains hidden markup
+        // inside the text. For instance an HTML document contains tags which aren't visible, but nonetheless exist.
+        // It's not publicly documented, but allegedly specifying this flag results in a minor performance uplift.
+        // Ironically, the only two places that mention this flag internally state:
+        // > perf: we could check TS_SS_NOHIDDENTEXT for better perf
+        pdcs->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT;
+    }
+
+    return S_OK;
+}
+
+STDMETHODIMP Implementation::GetWnd(HWND* phwnd) noexcept
+{
+    *phwnd = _provider ? _provider->GetHwnd() : nullptr;
+    return S_OK;
+}
+
+STDMETHODIMP Implementation::GetAttribute(REFGUID rguidAttribute, VARIANT* pvarValue) noexcept
+{
+    return E_NOTIMPL;
+}
+
+#pragma endregion ITfContextOwner
+
+#pragma region ITfContextOwnerCompositionSink
+
+STDMETHODIMP Implementation::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) noexcept
+try
+{
+    _compositions++;
+    *pfOk = TRUE;
+    return S_OK;
+}
+CATCH_RETURN()
+
+STDMETHODIMP Implementation::OnUpdateComposition(ITfCompositionView* pComposition, ITfRange* pRangeNew) noexcept
+{
+    return S_OK;
+}
+
+STDMETHODIMP Implementation::OnEndComposition(ITfCompositionView* pComposition) noexcept
+try
+{
+    if (_compositions <= 0)
+    {
+        return E_FAIL;
+    }
+
+    _compositions--;
+    if (_compositions == 0)
+    {
+        // https://learn.microsoft.com/en-us/windows/win32/api/msctf/nf-msctf-itfcontext-requesteditsession
+        // > A text service can request an edit session within the context of an existing edit session,
+        // > provided a write access session is not requested within a read-only session.
+        // --> Requires TF_ES_ASYNC to work properly. TF_ES_ASYNCDONTCARE randomly fails because... TSF.
+        std::ignore = _request(_editSessionCompositionUpdate, TF_ES_READWRITE | TF_ES_ASYNC);
+    }
+
+    return S_OK;
+}
+CATCH_RETURN()
+
+#pragma endregion ITfContextOwnerCompositionSink
+
+#pragma region ITfTextEditSink
+
+STDMETHODIMP Implementation::OnEndEdit(ITfContext* pic, TfEditCookie ecReadOnly, ITfEditRecord* pEditRecord) noexcept
+try
+{
+    if (_compositions == 1)
+    {
+        // https://learn.microsoft.com/en-us/windows/win32/api/msctf/nf-msctf-itfcontext-requesteditsession
+        // > A text service can request an edit session within the context of an existing edit session,
+        // > provided a write access session is not requested within a read-only session.
+        // --> Requires TF_ES_ASYNC to work properly. TF_ES_ASYNCDONTCARE randomly fails because... TSF.
+        std::ignore = _request(_editSessionCompositionUpdate, TF_ES_READWRITE | TF_ES_ASYNC);
+    }
+
+    return S_OK;
+}
+CATCH_RETURN()
+
+#pragma endregion ITfTextEditSink
+
+Implementation::EditSessionProxyBase::EditSessionProxyBase(Implementation* self) noexcept :
+    self{ self }
+{
+}
+
+STDMETHODIMP Implementation::EditSessionProxyBase::QueryInterface(REFIID riid, void** ppvObj) noexcept
+{
+    if (!ppvObj)
+    {
+        return E_POINTER;
+    }
+
+    if (IsEqualGUID(riid, IID_ITfEditSession))
+    {
+        *ppvObj = static_cast<ITfEditSession*>(this);
+    }
+    else if (IsEqualGUID(riid, IID_IUnknown))
+    {
+        *ppvObj = static_cast<IUnknown*>(this);
+    }
+    else
+    {
+        *ppvObj = nullptr;
+        return E_NOINTERFACE;
+    }
+
+    AddRef();
+    return S_OK;
+}
+
+ULONG STDMETHODCALLTYPE Implementation::EditSessionProxyBase::AddRef() noexcept
+{
+    return InterlockedIncrement(&referenceCount);
+}
+
+ULONG STDMETHODCALLTYPE Implementation::EditSessionProxyBase::Release() noexcept
+{
+    FAIL_FAST_IF(referenceCount == 0);
+    return InterlockedDecrement(&referenceCount);
+}
+
+[[nodiscard]] HRESULT Implementation::_request(EditSessionProxyBase& session, DWORD flags) const
+{
+    // Some of the sessions are async, and we don't want to send another request if one is still in flight.
+    if (session.referenceCount)
+    {
+        return S_FALSE;
+    }
+
+    HRESULT hr = S_OK;
+    THROW_IF_FAILED(_context->RequestEditSession(_clientId, &session, flags, &hr));
+    RETURN_IF_FAILED(hr);
+    return S_OK;
+}
+
+void Implementation::_doCompositionUpdate(TfEditCookie ec)
+{
+    wil::com_ptr<ITfRange> fullRange;
+    LONG fullRangeLength;
+    THROW_IF_FAILED(_context->GetStart(ec, fullRange.addressof()));
+    THROW_IF_FAILED(fullRange->ShiftEnd(ec, LONG_MAX, &fullRangeLength, nullptr));
+
+    std::wstring finalizedString;
+    std::wstring activeComposition;
+    til::small_vector<Render::CompositionRange, 2> activeCompositionRanges;
+    bool firstRange = true;
+
+    const GUID* guids[] = { &GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE };
+    wil::com_ptr<ITfReadOnlyProperty> props;
+    THROW_IF_FAILED(_context->TrackProperties(&guids[0], ARRAYSIZE(guids), nullptr, 0, props.addressof()));
+
+    wil::com_ptr<IEnumTfRanges> enumRanges;
+    THROW_IF_FAILED(props->EnumRanges(ec, enumRanges.addressof(), fullRange.get()));
+
+    // IEnumTfRanges::Next returns S_FALSE when it has reached the end of the list.
+    // This includes any call where the number of returned items is less than what was requested.
+    for (HRESULT nextResult = S_OK; nextResult == S_OK;)
+    {
+        ITfRange* ranges[8];
+        ULONG rangesCount;
+        nextResult = enumRanges->Next(ARRAYSIZE(ranges), &ranges[0], &rangesCount);
+
+        const auto cleanup = wil::scope_exit([&] {
+            for (ULONG i = 0; i < rangesCount; ++i)
+            {
+                ranges[i]->Release();
+            }
+        });
+
+        for (ULONG i = 0; i < rangesCount; ++i)
+        {
+            const auto range = ranges[i];
+
+            bool composing = false;
+            TfGuidAtom atom = TF_INVALID_GUIDATOM;
+            {
+                wil::unique_variant var;
+                THROW_IF_FAILED(props->GetValue(ec, range, var.addressof()));
+
+                wil::com_ptr<IEnumTfPropertyValue> propVal;
+                wil::com_query_to(var.punkVal, propVal.addressof());
+
+                unique_tf_propertyval propVals[2];
+                THROW_IF_FAILED(propVal->Next(2, propVals[0].addressof(), nullptr));
+
+                for (const auto& val : propVals)
+                {
+                    if (IsEqualGUID(val.guidId, GUID_PROP_COMPOSING))
+                    {
+                        composing = V_VT(&val.varValue) == VT_I4 && V_I4(&val.varValue) != 0;
+                    }
+                    else if (IsEqualGUID(val.guidId, GUID_PROP_ATTRIBUTE))
+                    {
+                        atom = V_VT(&val.varValue) == VT_I4 ? static_cast<TfGuidAtom>(V_I4(&val.varValue)) : TF_INVALID_GUIDATOM;
+                    }
+                }
+            }
+
+            size_t totalLen = 0;
+            for (;;)
+            {
+                // GetText() won't throw if the range is empty. It'll simply return len == 0.
+                // However, you'll likely never see this happen with a bufCap this large (try 16 instead or something).
+                // It seems TSF doesn't support such large compositions in any language.
+                static constexpr ULONG bufCap = 128;
+                WCHAR buf[bufCap];
+                ULONG len = bufCap;
+                THROW_IF_FAILED(range->GetText(ec, TF_TF_MOVESTART, buf, len, &len));
+
+                if (!composing && firstRange)
+                {
+                    finalizedString.append(buf, len);
+                }
+                else
+                {
+                    activeComposition.append(buf, len);
+                }
+
+                totalLen += len;
+
+                if (len < bufCap)
+                {
+                    break;
+                }
+            }
+
+            const auto attr = _textAttributeFromAtom(atom);
+            activeCompositionRanges.emplace_back(totalLen, attr);
+
+            firstRange = false;
+        }
+    }
+
+    LONG cursorPos = LONG_MAX;
+    {
+        // According to the docs this may result in TF_E_NOSELECTION. While I haven't actually seen that happen myself yet,
+        // I don't want this to result in log-spam, which is why this doesn't use SUCCEEDED_LOG().
+        unique_tf_selection sel;
+        ULONG selCount;
+        if (SUCCEEDED(_context->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &sel, &selCount)) && selCount == 1)
+        {
+            wil::com_ptr<ITfRange> start;
+            THROW_IF_FAILED(_context->GetStart(ec, start.addressof()));
+
+            TF_HALTCOND hc{
+                .pHaltRange = sel.range,
+                .aHaltPos = sel.style.ase == TF_AE_START ? TF_ANCHOR_START : TF_ANCHOR_END,
+            };
+            THROW_IF_FAILED(start->ShiftEnd(ec, LONG_MAX, &cursorPos, &hc));
+        }
+
+        // Compensate for the fact that we'll be erasing the start of the string below.
+        cursorPos -= static_cast<LONG>(finalizedString.size());
+        cursorPos = std::clamp(cursorPos, 0l, static_cast<LONG>(activeComposition.size()));
+    }
+
+    if (!finalizedString.empty())
+    {
+        // Erase the text that's done with composition from the context.
+        wil::com_ptr<ITfRange> range;
+        LONG cch;
+        THROW_IF_FAILED(_context->GetStart(ec, range.addressof()));
+        THROW_IF_FAILED(range->ShiftEnd(ec, static_cast<LONG>(finalizedString.size()), &cch, nullptr));
+        THROW_IF_FAILED(range->SetText(ec, 0, nullptr, 0));
+    }
+
+    if (_provider)
+    {
+        {
+            const auto renderer = _provider->GetRenderer();
+            const auto renderData = renderer->GetRenderData();
+
+            renderData->LockConsole();
+            const auto unlock = wil::scope_exit([&]() {
+                renderData->UnlockConsole();
+            });
+
+            auto& comp = renderData->activeComposition;
+            comp.text = std::move(activeComposition);
+            comp.attributes = std::move(activeCompositionRanges);
+            // The code block above that calculates the `cursorPos` will clamp it to a positive number.
+            comp.cursorPos = static_cast<size_t>(cursorPos);
+            renderer->NotifyPaintFrame();
+        }
+
+        if (!finalizedString.empty())
+        {
+            _provider->HandleOutput(finalizedString);
+        }
+    }
+}
+
+TextAttribute Implementation::_textAttributeFromAtom(TfGuidAtom atom) const
+{
+    TextAttribute attr;
+
+    // You get TF_INVALID_GUIDATOM by (for instance) using the Vietnamese Telex IME.
+    // A dashed underline is used because that's what Firefox used at the time and it
+    // looked kind of neat. In the past, conhost used a blue background and white text.
+    if (atom == TF_INVALID_GUIDATOM)
+    {
+        attr.SetUnderlineStyle(UnderlineStyle::DashedUnderlined);
+        return attr;
+    }
+
+    GUID guid;
+    if (FAILED_LOG(_categoryMgr->GetGUID(atom, &guid)))
+    {
+        return attr;
+    }
+
+    wil::com_ptr<ITfDisplayAttributeInfo> dai;
+    if (FAILED_LOG(_displayAttributeMgr->GetDisplayAttributeInfo(guid, dai.addressof(), nullptr)))
+    {
+        return attr;
+    }
+
+    TF_DISPLAYATTRIBUTE da;
+    THROW_IF_FAILED(dai->GetAttributeInfo(&da));
+
+    if (da.crText.type != TF_CT_NONE)
+    {
+        attr.SetForeground(_colorFromDisplayAttribute(da.crText));
+    }
+    if (da.crBk.type != TF_CT_NONE)
+    {
+        attr.SetBackground(_colorFromDisplayAttribute(da.crBk));
+    }
+    if (da.lsStyle >= TF_LS_NONE && da.lsStyle <= TF_LS_SQUIGGLE)
+    {
+        static constexpr UnderlineStyle lut[] = {
+            /* TF_LS_NONE     */ UnderlineStyle::NoUnderline,
+            /* TF_LS_SOLID    */ UnderlineStyle::SinglyUnderlined,
+            /* TF_LS_DOT      */ UnderlineStyle::DottedUnderlined,
+            /* TF_LS_DASH     */ UnderlineStyle::DashedUnderlined,
+            /* TF_LS_SQUIGGLE */ UnderlineStyle::CurlyUnderlined,
+        };
+        attr.SetUnderlineStyle(lut[da.lsStyle]);
+    }
+    // You can reproduce bold lines with the Japanese IME by typing "kyouhaishaheiku" and pressing space.
+    // The IME will allow you to navigate between the 3 parts of the composition and the current one is
+    // marked as fBoldLine. We don't support bold lines so we just use a double underline instead.
+    if (da.fBoldLine)
+    {
+        attr.SetUnderlineStyle(UnderlineStyle::DoublyUnderlined);
+    }
+    if (da.crLine.type != TF_CT_NONE)
+    {
+        attr.SetUnderlineColor(_colorFromDisplayAttribute(da.crLine));
+    }
+
+    return attr;
+}
+
+COLORREF Implementation::_colorFromDisplayAttribute(TF_DA_COLOR color)
+{
+    switch (color.type)
+    {
+    case TF_CT_SYSCOLOR:
+        return GetSysColor(color.nIndex);
+    case TF_CT_COLORREF:
+        return color.cr;
+    default:
+        // If you get here you either called this when .type is TF_CT_NONE
+        // (don't call in that case; there's no color to be had), or
+        // there's a new .type which you need to add.
+        assert(false);
+        return 0;
+    }
+}
diff --git a/src/tsf/Implementation.h b/src/tsf/Implementation.h
new file mode 100644
index 00000000000..7071141cae5
--- /dev/null
+++ b/src/tsf/Implementation.h
@@ -0,0 +1,108 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+class TextAttribute;
+
+namespace Microsoft::Console::Render
+{
+    class Renderer;
+}
+
+namespace Microsoft::Console::TSF
+{
+    struct IDataProvider;
+
+    struct Implementation : ITfContextOwner, ITfContextOwnerCompositionSink, ITfTextEditSink
+    {
+        virtual ~Implementation() = default;
+
+        void Initialize();
+        void Uninitialize() noexcept;
+        HWND FindWindowOfActiveTSF() const noexcept;
+        void AssociateFocus(IDataProvider* provider);
+        void Focus(IDataProvider* provider);
+        void Unfocus(IDataProvider* provider);
+        bool HasActiveComposition() const noexcept;
+
+        // IUnknown methods
+        STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj) noexcept override;
+        ULONG STDMETHODCALLTYPE AddRef() noexcept override;
+        ULONG STDMETHODCALLTYPE Release() noexcept override;
+
+        // ITfContextOwner
+        STDMETHODIMP GetACPFromPoint(const POINT* ptScreen, DWORD dwFlags, LONG* pacp) noexcept override;
+        STDMETHODIMP GetTextExt(LONG acpStart, LONG acpEnd, RECT* prc, BOOL* pfClipped) noexcept override;
+        STDMETHODIMP GetScreenExt(RECT* prc) noexcept override;
+        STDMETHODIMP GetStatus(TF_STATUS* pdcs) noexcept override;
+        STDMETHODIMP GetWnd(HWND* phwnd) noexcept override;
+        STDMETHODIMP GetAttribute(REFGUID rguidAttribute, VARIANT* pvarValue) noexcept override;
+
+        // ITfContextOwnerCompositionSink methods
+        STDMETHODIMP OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) noexcept override;
+        STDMETHODIMP OnUpdateComposition(ITfCompositionView* pComposition, ITfRange* pRangeNew) noexcept override;
+        STDMETHODIMP OnEndComposition(ITfCompositionView* pComposition) noexcept override;
+
+        // ITfTextEditSink methods
+        STDMETHODIMP OnEndEdit(ITfContext* pic, TfEditCookie ecReadOnly, ITfEditRecord* pEditRecord) noexcept override;
+
+    private:
+        struct EditSessionProxyBase : ITfEditSession
+        {
+            explicit EditSessionProxyBase(Implementation* self) noexcept;
+            virtual ~EditSessionProxyBase() = default;
+
+            // IUnknown methods
+            STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj) noexcept override;
+            ULONG STDMETHODCALLTYPE AddRef() noexcept override;
+            ULONG STDMETHODCALLTYPE Release() noexcept override;
+
+            ULONG referenceCount = 0;
+            Implementation* self = nullptr;
+        };
+
+        // In the past we had 3 different `ITfEditSession`s (update, finish, cleanup).
+        // Due to refactoring only 1 is left now, but this code remains in case we need more in the future.
+        // It allows you to statically bind a callback function to a `ITfEditSession` proxy type.
+        template<void (Implementation::*Callback)(TfEditCookie)>
+        struct EditSessionProxy : EditSessionProxyBase
+        {
+            using EditSessionProxyBase::EditSessionProxyBase;
+
+            // ITfEditSession method
+            STDMETHODIMP DoEditSession(TfEditCookie ec) noexcept override
+            {
+                try
+                {
+                    (self->*Callback)(ec);
+                    return S_OK;
+                }
+                CATCH_RETURN();
+            }
+        };
+
+        [[nodiscard]] HRESULT _request(EditSessionProxyBase& session, DWORD flags) const;
+        void _doCompositionUpdate(TfEditCookie ec);
+        TextAttribute _textAttributeFromAtom(TfGuidAtom atom) const;
+        static COLORREF _colorFromDisplayAttribute(TF_DA_COLOR color);
+
+        ULONG _referenceCount = 1;
+
+        wil::com_ptr<IDataProvider> _provider;
+        HWND _associatedHwnd = nullptr;
+
+        wil::com_ptr_t<ITfCategoryMgr> _categoryMgr;
+        wil::com_ptr<ITfDisplayAttributeMgr> _displayAttributeMgr;
+        wil::com_ptr<ITfThreadMgrEx> _threadMgrEx;
+        wil::com_ptr<ITfDocumentMgr> _documentMgr;
+        wil::com_ptr<ITfContext> _context;
+        wil::com_ptr<ITfSource> _contextSource;
+        DWORD _cookieContextOwner = TF_INVALID_COOKIE;
+        DWORD _cookieTextEditSink = TF_INVALID_COOKIE;
+        TfClientId _clientId = TF_CLIENTID_NULL;
+
+        EditSessionProxy<&Implementation::_doCompositionUpdate> _editSessionCompositionUpdate{ this };
+        int _compositions = 0;
+    };
+}
diff --git a/src/tsf/TfCatUtil.cpp b/src/tsf/TfCatUtil.cpp
deleted file mode 100644
index 2ded71dd88e..00000000000
--- a/src/tsf/TfCatUtil.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfCatUtil.cpp
-
-Abstract:
-
-    This file implements the CicCategoryMgr Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#include "precomp.h"
-#include "TfCatUtil.h"
-
-//+---------------------------------------------------------------------------
-//
-// CicCategoryMgr::ctor
-// CicCategoryMgr::dtor
-//
-//----------------------------------------------------------------------------
-
-CicCategoryMgr::CicCategoryMgr() = default;
-
-CicCategoryMgr::~CicCategoryMgr() = default;
-
-//+---------------------------------------------------------------------------
-//
-// CicCategoryMgr::GetGUIDFromGUIDATOM
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CicCategoryMgr::GetGUIDFromGUIDATOM(TfGuidAtom guidatom, GUID* pguid)
-{
-    return m_pcat->GetGUID(guidatom, pguid);
-}
-
-//+---------------------------------------------------------------------------
-//
-// CicCategoryMgr::InitCategoryInstance
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CicCategoryMgr::InitCategoryInstance()
-{
-    //
-    // Create ITfCategoryMgr instance.
-    //
-    return ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_pcat));
-}
diff --git a/src/tsf/TfCatUtil.h b/src/tsf/TfCatUtil.h
deleted file mode 100644
index c749098dd50..00000000000
--- a/src/tsf/TfCatUtil.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfCatUtil.h
-
-Abstract:
-
-    This file defines the CicCategoryMgr Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#pragma once
-
-class CicCategoryMgr
-{
-public:
-    CicCategoryMgr();
-    virtual ~CicCategoryMgr();
-
-public:
-    [[nodiscard]] HRESULT GetGUIDFromGUIDATOM(TfGuidAtom guidatom, GUID* pguid);
-    [[nodiscard]] HRESULT InitCategoryInstance();
-
-    inline ITfCategoryMgr* GetCategoryMgr() { return m_pcat.get(); }
-
-private:
-    wil::com_ptr_nothrow<ITfCategoryMgr> m_pcat;
-};
diff --git a/src/tsf/TfConvArea.cpp b/src/tsf/TfConvArea.cpp
deleted file mode 100644
index 940bb7af098..00000000000
--- a/src/tsf/TfConvArea.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfConvArea.cpp
-
-Abstract:
-
-    This file implements the CConversionArea Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#include "precomp.h"
-#include "ConsoleTSF.h"
-#include "TfCtxtComp.h"
-#include "TfConvArea.h"
-
-//+---------------------------------------------------------------------------
-// CConversionArea
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CConversionArea::DrawComposition(const std::wstring_view CompStr,
-                                                       const std::vector<TF_DISPLAYATTRIBUTE>& DisplayAttributes,
-                                                       const DWORD CompCursorPos)
-{
-    // Set up colors.
-    static const std::array<WORD, CONIME_ATTRCOLOR_SIZE> colors{ DEFAULT_COMP_ENTERED,
-                                                                 DEFAULT_COMP_ALREADY_CONVERTED,
-                                                                 DEFAULT_COMP_CONVERSION,
-                                                                 DEFAULT_COMP_YET_CONVERTED,
-                                                                 DEFAULT_COMP_INPUT_ERROR,
-                                                                 DEFAULT_COMP_INPUT_ERROR,
-                                                                 DEFAULT_COMP_INPUT_ERROR,
-                                                                 DEFAULT_COMP_INPUT_ERROR };
-
-    const auto encodedAttributes = _DisplayAttributesToEncodedAttributes(DisplayAttributes,
-                                                                         CompCursorPos);
-
-    std::span<const BYTE> attributes(encodedAttributes.data(), encodedAttributes.size());
-    std::span<const WORD> colorArray(colors.data(), colors.size());
-
-    return ImeComposeData(CompStr, attributes, colorArray);
-}
-
-[[nodiscard]] HRESULT CConversionArea::ClearComposition()
-{
-    return ImeClearComposeData();
-}
-
-[[nodiscard]] HRESULT CConversionArea::DrawResult(const std::wstring_view ResultStr)
-{
-    return ImeComposeResult(ResultStr);
-}
-
-[[nodiscard]] std::vector<BYTE> CConversionArea::_DisplayAttributesToEncodedAttributes(const std::vector<TF_DISPLAYATTRIBUTE>& DisplayAttributes,
-                                                                                       const DWORD CompCursorPos)
-{
-    std::vector<BYTE> encodedAttrs;
-    for (const auto& da : DisplayAttributes)
-    {
-        BYTE bAttr;
-
-        if (da.bAttr == TF_ATTR_OTHER || da.bAttr > TF_ATTR_FIXEDCONVERTED)
-        {
-            bAttr = ATTR_TARGET_CONVERTED;
-        }
-        else
-        {
-            if (da.bAttr == TF_ATTR_INPUT_ERROR)
-            {
-                bAttr = ATTR_CONVERTED;
-            }
-            else
-            {
-                bAttr = (BYTE)da.bAttr;
-            }
-        }
-        encodedAttrs.emplace_back(bAttr);
-    }
-
-    if (CompCursorPos != -1)
-    {
-        if (CompCursorPos == 0)
-        {
-            encodedAttrs[CompCursorPos] |= CONIME_CURSOR_LEFT; // special handling for ConSrv... 0x20 = COMMON_LVB_GRID_SINGLEFLAG + COMMON_LVB_GRID_LVERTICAL
-        }
-        else if (CompCursorPos - 1 < DisplayAttributes.size())
-        {
-            encodedAttrs[CompCursorPos - 1] |= CONIME_CURSOR_RIGHT; // special handling for ConSrv... 0x10 = COMMON_LVB_GRID_SINGLEFLAG + COMMON_LVB_GRID_RVERTICAL
-        }
-    }
-
-    return encodedAttrs;
-}
diff --git a/src/tsf/TfConvArea.h b/src/tsf/TfConvArea.h
deleted file mode 100644
index e6ae9466ed5..00000000000
--- a/src/tsf/TfConvArea.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfConvArea.h
-
-Abstract:
-
-    This file defines the CConversionAreaJapanese Interface Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#pragma once
-
-//+---------------------------------------------------------------------------
-//
-// CConversionArea::Pure virtual class
-//
-//----------------------------------------------------------------------------
-
-class CConversionArea
-{
-public:
-    [[nodiscard]] HRESULT DrawComposition(const std::wstring_view CompStr,
-                                          const std::vector<TF_DISPLAYATTRIBUTE>& DisplayAttributes,
-                                          const DWORD CompCursorPos = -1);
-
-    [[nodiscard]] HRESULT ClearComposition();
-
-    [[nodiscard]] HRESULT DrawResult(const std::wstring_view ResultStr);
-
-private:
-    [[nodiscard]] std::vector<BYTE> _DisplayAttributesToEncodedAttributes(const std::vector<TF_DISPLAYATTRIBUTE>& DisplayAttributes,
-                                                                          const DWORD CompCursorPos);
-};
diff --git a/src/tsf/TfCtxtComp.h b/src/tsf/TfCtxtComp.h
deleted file mode 100644
index 5b5ef460319..00000000000
--- a/src/tsf/TfCtxtComp.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfCtxtComp.h
-
-Abstract:
-
-    This file defines the Context of Composition Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#pragma once
-
-/////////////////////////////////////////////////////////////////////////////
-// CCompCursorPos
-
-class CCompCursorPos
-{
-public:
-    CCompCursorPos()
-    {
-        m_CursorPosition = 0;
-    }
-
-    void SetCursorPosition(DWORD CursorPosition)
-    {
-        m_CursorPosition = CursorPosition;
-    }
-
-    DWORD GetCursorPosition() { return m_CursorPosition; }
-
-private:
-    DWORD m_CursorPosition;
-};
diff --git a/src/tsf/TfDispAttr.cpp b/src/tsf/TfDispAttr.cpp
deleted file mode 100644
index 48e1d044e85..00000000000
--- a/src/tsf/TfDispAttr.cpp
+++ /dev/null
@@ -1,199 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfDispAttr.cpp
-
-Abstract:
-
-    This file implements the CicDisplayAttributeMgr Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#include "precomp.h"
-#include "TfDispAttr.h"
-
-//+---------------------------------------------------------------------------
-//
-// CicDisplayAttributeMgr::ctor
-// CicDisplayAttributeMgr::dtor
-//
-//----------------------------------------------------------------------------
-
-CicDisplayAttributeMgr::CicDisplayAttributeMgr() = default;
-
-CicDisplayAttributeMgr::~CicDisplayAttributeMgr() = default;
-
-//+---------------------------------------------------------------------------
-//
-// CicDisplayAttributeMgr::GetDisplayAttributeTrackPropertyRange
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CicDisplayAttributeMgr::GetDisplayAttributeTrackPropertyRange(TfEditCookie ec,
-                                                                                    ITfContext* pic,
-                                                                                    ITfRange* pRange,
-                                                                                    ITfReadOnlyProperty** ppProp,
-                                                                                    IEnumTfRanges** ppEnum,
-                                                                                    ULONG* pulNumProp)
-{
-    auto hr = E_FAIL;
-    try
-    {
-        auto ulNumProp = static_cast<ULONG>(m_DispAttrProp.size());
-        if (ulNumProp)
-        {
-            // TrackProperties wants an array of GUID *'s
-            auto ppguidProp = std::make_unique<const GUID*[]>(ulNumProp);
-            for (ULONG i = 0; i < ulNumProp; i++)
-            {
-                ppguidProp[i] = &m_DispAttrProp.at(i);
-            }
-
-            wil::com_ptr<ITfReadOnlyProperty> pProp;
-            if (SUCCEEDED(hr = pic->TrackProperties(ppguidProp.get(), ulNumProp, nullptr, NULL, &pProp)))
-            {
-                hr = pProp->EnumRanges(ec, ppEnum, pRange);
-                if (SUCCEEDED(hr))
-                {
-                    *ppProp = pProp.detach();
-                }
-            }
-
-            if (SUCCEEDED(hr))
-            {
-                *pulNumProp = ulNumProp;
-            }
-        }
-    }
-    CATCH_RETURN();
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CicDisplayAttributeMgr::GetDisplayAttributeData
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CicDisplayAttributeMgr::GetDisplayAttributeData(ITfCategoryMgr* pcat,
-                                                                      TfEditCookie ec,
-                                                                      ITfReadOnlyProperty* pProp,
-                                                                      ITfRange* pRange,
-                                                                      TF_DISPLAYATTRIBUTE* pda,
-                                                                      TfGuidAtom* pguid,
-                                                                      ULONG /*ulNumProp*/)
-{
-    VARIANT var;
-
-    auto hr = E_FAIL;
-
-    if (SUCCEEDED(pProp->GetValue(ec, pRange, &var)))
-    {
-        FAIL_FAST_IF(!(var.vt == VT_UNKNOWN));
-
-        wil::com_ptr_nothrow<IEnumTfPropertyValue> pEnumPropertyVal;
-        if (wil::try_com_query_to(var.punkVal, &pEnumPropertyVal))
-        {
-            TF_PROPERTYVAL tfPropVal;
-            while (pEnumPropertyVal->Next(1, &tfPropVal, nullptr) == S_OK)
-            {
-                if (tfPropVal.varValue.vt == VT_EMPTY)
-                {
-                    continue; // prop has no value over this span
-                }
-
-                FAIL_FAST_IF(!(tfPropVal.varValue.vt == VT_I4)); // expecting GUIDATOMs
-
-                auto gaVal = (TfGuidAtom)tfPropVal.varValue.lVal;
-
-                GUID guid;
-                pcat->GetGUID(gaVal, &guid);
-
-                wil::com_ptr_nothrow<ITfDisplayAttributeInfo> pDAI;
-                if (SUCCEEDED(m_pDAM->GetDisplayAttributeInfo(guid, &pDAI, NULL)))
-                {
-                    //
-                    // Issue: for simple apps.
-                    //
-                    // Small apps can not show multi underline. So
-                    // this helper function returns only one
-                    // DISPLAYATTRIBUTE structure.
-                    //
-                    if (pda)
-                    {
-                        pDAI->GetAttributeInfo(pda);
-                    }
-
-                    if (pguid)
-                    {
-                        *pguid = gaVal;
-                    }
-
-                    hr = S_OK;
-                    break;
-                }
-            }
-        }
-        VariantClear(&var);
-    }
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CicDisplayAttributeMgr::InitCategoryInstance
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CicDisplayAttributeMgr::InitDisplayAttributeInstance(ITfCategoryMgr* pcat)
-{
-    HRESULT hr;
-
-    //
-    // Create ITfDisplayAttributeMgr instance.
-    //
-    if (FAILED(hr = ::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_pDAM))))
-    {
-        return hr;
-    }
-
-    wil::com_ptr_nothrow<IEnumGUID> pEnumProp;
-    pcat->EnumItemsInCategory(GUID_TFCAT_DISPLAYATTRIBUTEPROPERTY, &pEnumProp);
-
-    //
-    // make a database for Display Attribute Properties.
-    //
-    if (pEnumProp)
-    {
-        GUID guidProp;
-
-        try
-        {
-            //
-            // add System Display Attribute first.
-            // so no other Display Attribute property overwrite it.
-            //
-            m_DispAttrProp.emplace_back(GUID_PROP_ATTRIBUTE);
-
-            while (pEnumProp->Next(1, &guidProp, nullptr) == S_OK)
-            {
-                if (!IsEqualGUID(guidProp, GUID_PROP_ATTRIBUTE))
-                {
-                    m_DispAttrProp.emplace_back(guidProp);
-                }
-            }
-        }
-        CATCH_RETURN();
-    }
-    return hr;
-}
diff --git a/src/tsf/TfDispAttr.h b/src/tsf/TfDispAttr.h
deleted file mode 100644
index 74fc4dd7f25..00000000000
--- a/src/tsf/TfDispAttr.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfDispAttr.h
-
-Abstract:
-
-    This file defines the CicDisplayAttributeMgr Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#pragma once
-
-class CicDisplayAttributeMgr
-{
-public:
-    CicDisplayAttributeMgr();
-    virtual ~CicDisplayAttributeMgr();
-
-public:
-    [[nodiscard]] HRESULT GetDisplayAttributeTrackPropertyRange(TfEditCookie ec,
-                                                                ITfContext* pic,
-                                                                ITfRange* pRange,
-                                                                ITfReadOnlyProperty** ppProp,
-                                                                IEnumTfRanges** ppEnum,
-                                                                ULONG* pulNumProp);
-    [[nodiscard]] HRESULT GetDisplayAttributeData(ITfCategoryMgr* pcat,
-                                                  TfEditCookie ec,
-                                                  ITfReadOnlyProperty* pProp,
-                                                  ITfRange* pRange,
-                                                  TF_DISPLAYATTRIBUTE* pda,
-                                                  TfGuidAtom* pguid,
-                                                  ULONG ulNumProp);
-    [[nodiscard]] HRESULT InitDisplayAttributeInstance(ITfCategoryMgr* pcat);
-
-    inline ITfDisplayAttributeMgr* GetDisplayAttributeMgr() { return m_pDAM.get(); }
-
-private:
-    wil::com_ptr_nothrow<ITfDisplayAttributeMgr> m_pDAM;
-    std::vector<GUID> m_DispAttrProp;
-};
diff --git a/src/tsf/TfEditSession.cpp b/src/tsf/TfEditSession.cpp
deleted file mode 100644
index 2e728d0c9ed..00000000000
--- a/src/tsf/TfEditSession.cpp
+++ /dev/null
@@ -1,1167 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfEditSession.cpp
-
-Abstract:
-
-    This file implements the CEditSessionObject Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#include "precomp.h"
-#include "TfConvArea.h"
-#include "TfCatUtil.h"
-#include "TfDispAttr.h"
-#include "TfEditSession.h"
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::IUnknown::QueryInterface
-// CEditSessionObject::IUnknown::AddRef
-// CEditSessionObject::IUnknown::Release
-//
-//----------------------------------------------------------------------------
-
-STDAPI CEditSessionObject::QueryInterface(REFIID riid, void** ppvObj)
-{
-    *ppvObj = nullptr;
-
-    if (IsEqualIID(riid, IID_ITfEditSession))
-    {
-        *ppvObj = static_cast<ITfEditSession*>(this);
-    }
-    else if (IsEqualIID(riid, IID_IUnknown))
-    {
-        *ppvObj = static_cast<IUnknown*>(this);
-    }
-
-    if (*ppvObj)
-    {
-        AddRef();
-        return S_OK;
-    }
-
-    return E_NOINTERFACE;
-}
-
-STDAPI_(ULONG)
-CEditSessionObject::AddRef()
-{
-    return ++m_cRef;
-}
-
-STDAPI_(ULONG)
-CEditSessionObject::Release()
-{
-    long cr;
-
-    cr = --m_cRef;
-    FAIL_FAST_IF(!(cr >= 0));
-
-    if (cr == 0)
-    {
-        delete this;
-    }
-
-    return cr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::GetAllTextRange
-//
-//----------------------------------------------------------------------------
-
-// static
-[[nodiscard]] HRESULT CEditSessionObject::GetAllTextRange(TfEditCookie ec,
-                                                          ITfContext* ic,
-                                                          ITfRange** range,
-                                                          LONG* lpTextLength,
-                                                          TF_HALTCOND* lpHaltCond)
-{
-    HRESULT hr;
-
-    //
-    // init lpTextLength first.
-    //
-    *lpTextLength = 0;
-
-    //
-    // Create the range that covers all the text.
-    //
-    wil::com_ptr_nothrow<ITfRange> rangeFull;
-    if (FAILED(hr = ic->GetStart(ec, &rangeFull)))
-    {
-        return hr;
-    }
-
-    LONG cch = 0;
-    if (FAILED(hr = rangeFull->ShiftEnd(ec, LONG_MAX, &cch, lpHaltCond)))
-    {
-        return hr;
-    }
-
-    if (FAILED(hr = rangeFull->Clone(range)))
-    {
-        return hr;
-    }
-
-    *lpTextLength = cch;
-
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::SetTextInRange
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionObject::SetTextInRange(TfEditCookie ec,
-                                                         ITfRange* range,
-                                                         __in_ecount_opt(len) LPWSTR psz,
-                                                         DWORD len)
-{
-    auto hr = E_FAIL;
-    if (g_pConsoleTSF)
-    {
-        g_pConsoleTSF->SetModifyingDocFlag(TRUE);
-        hr = range->SetText(ec, 0, psz, len);
-        g_pConsoleTSF->SetModifyingDocFlag(FALSE);
-    }
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::ClearTextInRange
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionObject::ClearTextInRange(TfEditCookie ec, ITfRange* range)
-{
-    //
-    // Clear the text in Cicero TOM
-    //
-    return SetTextInRange(ec, range, nullptr, 0);
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::_GetCursorPosition
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionObject::_GetCursorPosition(TfEditCookie ec, CCompCursorPos& CompCursorPos)
-{
-    auto pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
-    if (pic == nullptr)
-    {
-        return E_FAIL;
-    }
-
-    HRESULT hr;
-    ULONG cFetched;
-
-    TF_SELECTION sel;
-    sel.range = nullptr;
-
-    if (SUCCEEDED(hr = pic->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &sel, &cFetched)))
-    {
-        wil::com_ptr_nothrow<ITfRange> start;
-        LONG ich;
-        TF_HALTCOND hc;
-
-        hc.pHaltRange = sel.range;
-        hc.aHaltPos = (sel.style.ase == TF_AE_START) ? TF_ANCHOR_START : TF_ANCHOR_END;
-        hc.dwFlags = 0;
-
-        if (SUCCEEDED(hr = GetAllTextRange(ec, pic, &start, &ich, &hc)))
-        {
-            CompCursorPos.SetCursorPosition(ich);
-        }
-
-        SafeReleaseClear(sel.range);
-    }
-
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::_GetTextAndAttribute
-//
-//----------------------------------------------------------------------------
-
-//
-// Get text and attribute in given range
-//
-//                                ITfRange::range
-//   TF_ANCHOR_START
-//    |======================================================================|
-//                        +--------------------+          #+----------+
-//                        |ITfRange::pPropRange|          #|pPropRange|
-//                        +--------------------+          #+----------+
-//                        |     GUID_ATOM      |          #
-//                        +--------------------+          #
-//    ^^^^^^^^^^^^^^^^^^^^                      ^^^^^^^^^^#
-//    ITfRange::gap_range                       gap_range #
-//                                                        #
-//                                                        V
-//                                                        ITfRange::no_display_attribute_range
-//                                                   result_comp
-//                                          +1   <-       0    ->     -1
-//
-
-[[nodiscard]] HRESULT CEditSessionObject::_GetTextAndAttribute(TfEditCookie ec,
-                                                               ITfRange* rangeIn,
-                                                               std::wstring& CompStr,
-                                                               std::vector<TfGuidAtom>& CompGuid,
-                                                               std::wstring& ResultStr,
-                                                               BOOL bInWriteSession,
-                                                               CicCategoryMgr* pCicCatMgr,
-                                                               CicDisplayAttributeMgr* pCicDispAttr)
-{
-    HRESULT hr;
-
-    auto pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
-    if (pic == nullptr)
-    {
-        return E_FAIL;
-    }
-
-    //
-    // Get no display attribute range if there exist.
-    // Otherwise, result range is the same to input range.
-    //
-    LONG result_comp;
-    wil::com_ptr_nothrow<ITfRange> no_display_attribute_range;
-    if (FAILED(hr = rangeIn->Clone(&no_display_attribute_range)))
-    {
-        return hr;
-    }
-
-    const GUID* guids[] = { &GUID_PROP_COMPOSING };
-    const int guid_size = sizeof(guids) / sizeof(GUID*);
-
-    if (FAILED(hr = _GetNoDisplayAttributeRange(ec, rangeIn, guids, guid_size, no_display_attribute_range.get())))
-    {
-        return hr;
-    }
-
-    wil::com_ptr_nothrow<ITfReadOnlyProperty> propComp;
-    if (FAILED(hr = pic->TrackProperties(guids, guid_size, // system property
-                                         NULL,
-                                         0, // application property
-                                         &propComp)))
-    {
-        return hr;
-    }
-
-    wil::com_ptr_nothrow<IEnumTfRanges> enumComp;
-    if (FAILED(hr = propComp->EnumRanges(ec, &enumComp, rangeIn)))
-    {
-        return hr;
-    }
-
-    wil::com_ptr_nothrow<ITfRange> range;
-    while (enumComp->Next(1, &range, nullptr) == S_OK)
-    {
-        VARIANT var;
-        auto fCompExist = FALSE;
-
-        hr = propComp->GetValue(ec, range.get(), &var);
-        if (S_OK == hr)
-        {
-            wil::com_ptr_nothrow<IEnumTfPropertyValue> EnumPropVal;
-            if (wil::try_com_query_to(var.punkVal, &EnumPropVal))
-            {
-                TF_PROPERTYVAL tfPropertyVal;
-
-                while (EnumPropVal->Next(1, &tfPropertyVal, nullptr) == S_OK)
-                {
-                    for (auto i = 0; i < guid_size; i++)
-                    {
-                        if (IsEqualGUID(tfPropertyVal.guidId, *guids[i]))
-                        {
-                            if ((V_VT(&tfPropertyVal.varValue) == VT_I4 && V_I4(&tfPropertyVal.varValue) != 0))
-                            {
-                                fCompExist = TRUE;
-                                break;
-                            }
-                        }
-                    }
-
-                    VariantClear(&tfPropertyVal.varValue);
-
-                    if (fCompExist)
-                    {
-                        break;
-                    }
-                }
-            }
-        }
-
-        VariantClear(&var);
-
-        ULONG ulNumProp;
-
-        wil::com_ptr_nothrow<IEnumTfRanges> enumProp;
-        wil::com_ptr_nothrow<ITfReadOnlyProperty> prop;
-        if (FAILED(hr = pCicDispAttr->GetDisplayAttributeTrackPropertyRange(ec, pic, range.get(), &prop, &enumProp, &ulNumProp)))
-        {
-            return hr;
-        }
-
-        // use text range for get text
-        wil::com_ptr_nothrow<ITfRange> textRange;
-        if (FAILED(hr = range->Clone(&textRange)))
-        {
-            return hr;
-        }
-
-        // use text range for gap text (no property range).
-        wil::com_ptr_nothrow<ITfRange> gap_range;
-        if (FAILED(hr = range->Clone(&gap_range)))
-        {
-            return hr;
-        }
-
-        wil::com_ptr_nothrow<ITfRange> pPropRange;
-        while (enumProp->Next(1, &pPropRange, nullptr) == S_OK)
-        {
-            // pick up the gap up to the next property
-            gap_range->ShiftEndToRange(ec, pPropRange.get(), TF_ANCHOR_START);
-
-            //
-            // GAP range
-            //
-            no_display_attribute_range->CompareStart(ec, gap_range.get(), TF_ANCHOR_START, &result_comp);
-            LOG_IF_FAILED(_GetTextAndAttributeGapRange(ec,
-                                                       gap_range.get(),
-                                                       result_comp,
-                                                       CompStr,
-                                                       CompGuid,
-                                                       ResultStr));
-
-            //
-            // Get display attribute data if some GUID_ATOM exist.
-            //
-            TF_DISPLAYATTRIBUTE da;
-            auto guidatom = TF_INVALID_GUIDATOM;
-
-            LOG_IF_FAILED(pCicDispAttr->GetDisplayAttributeData(pCicCatMgr->GetCategoryMgr(),
-                                                                ec,
-                                                                prop.get(),
-                                                                pPropRange.get(),
-                                                                &da,
-                                                                &guidatom,
-                                                                ulNumProp));
-
-            //
-            // Property range
-            //
-            no_display_attribute_range->CompareStart(ec, pPropRange.get(), TF_ANCHOR_START, &result_comp);
-
-            // Adjust GAP range's start anchor to the end of property range.
-            gap_range->ShiftStartToRange(ec, pPropRange.get(), TF_ANCHOR_END);
-
-            //
-            // Get property text
-            //
-            LOG_IF_FAILED(_GetTextAndAttributePropertyRange(ec,
-                                                            pPropRange.get(),
-                                                            fCompExist,
-                                                            result_comp,
-                                                            bInWriteSession,
-                                                            da,
-                                                            guidatom,
-                                                            CompStr,
-                                                            CompGuid,
-                                                            ResultStr));
-
-        } // while
-
-        // the last non-attr
-        textRange->ShiftStartToRange(ec, gap_range.get(), TF_ANCHOR_START);
-        textRange->ShiftEndToRange(ec, range.get(), TF_ANCHOR_END);
-
-        BOOL fEmpty;
-        while (textRange->IsEmpty(ec, &fEmpty) == S_OK && !fEmpty)
-        {
-            WCHAR wstr0[256 + 1];
-            ULONG ulcch0 = ARRAYSIZE(wstr0) - 1;
-            textRange->GetText(ec, TF_TF_MOVESTART, wstr0, ulcch0, &ulcch0);
-
-            TfGuidAtom guidatom;
-            guidatom = TF_INVALID_GUIDATOM;
-
-            TF_DISPLAYATTRIBUTE da;
-            da.bAttr = TF_ATTR_INPUT;
-
-            try
-            {
-                CompGuid.insert(CompGuid.end(), ulcch0, guidatom);
-                CompStr.append(wstr0, ulcch0);
-            }
-            CATCH_RETURN();
-        }
-
-        textRange->Collapse(ec, TF_ANCHOR_END);
-
-    } // out-most while for GUID_PROP_COMPOSING
-
-    //
-    // set GUID_PROP_CONIME_TRACKCOMPOSITION
-    //
-    wil::com_ptr_nothrow<ITfProperty> PropertyTrackComposition;
-    if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_CONIME_TRACKCOMPOSITION, &PropertyTrackComposition)))
-    {
-        VARIANT var;
-        var.vt = VT_I4;
-        var.lVal = 1;
-        PropertyTrackComposition->SetValue(ec, rangeIn, &var);
-    }
-
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::_GetTextAndAttributeGapRange
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionObject::_GetTextAndAttributeGapRange(TfEditCookie ec,
-                                                                       ITfRange* gap_range,
-                                                                       LONG result_comp,
-                                                                       std::wstring& CompStr,
-                                                                       std::vector<TfGuidAtom>& CompGuid,
-                                                                       std::wstring& ResultStr)
-{
-    TfGuidAtom guidatom;
-    guidatom = TF_INVALID_GUIDATOM;
-
-    TF_DISPLAYATTRIBUTE da;
-    da.bAttr = TF_ATTR_INPUT;
-
-    BOOL fEmpty;
-    WCHAR wstr0[256 + 1];
-    ULONG ulcch0;
-
-    while (gap_range->IsEmpty(ec, &fEmpty) == S_OK && !fEmpty)
-    {
-        wil::com_ptr_nothrow<ITfRange> backup_range;
-        if (FAILED(gap_range->Clone(&backup_range)))
-        {
-            return E_FAIL;
-        }
-
-        //
-        // Retrieve gap text if there exist.
-        //
-        ulcch0 = ARRAYSIZE(wstr0) - 1;
-        if (FAILED(gap_range->GetText(ec,
-                                      TF_TF_MOVESTART, // Move range to next after get text.
-                                      wstr0,
-                                      ulcch0,
-                                      &ulcch0)))
-        {
-            return E_FAIL;
-        }
-
-        try
-        {
-            if (result_comp <= 0)
-            {
-                CompGuid.insert(CompGuid.end(), ulcch0, guidatom);
-                CompStr.append(wstr0, ulcch0);
-            }
-            else
-            {
-                ResultStr.append(wstr0, ulcch0);
-                LOG_IF_FAILED(ClearTextInRange(ec, backup_range.get()));
-            }
-        }
-        CATCH_RETURN();
-    }
-
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::_GetTextAndAttributePropertyRange
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionObject::_GetTextAndAttributePropertyRange(TfEditCookie ec,
-                                                                            ITfRange* pPropRange,
-                                                                            BOOL fCompExist,
-                                                                            LONG result_comp,
-                                                                            BOOL bInWriteSession,
-                                                                            TF_DISPLAYATTRIBUTE da,
-                                                                            TfGuidAtom guidatom,
-                                                                            std::wstring& CompStr,
-                                                                            std::vector<TfGuidAtom>& CompGuid,
-                                                                            std::wstring& ResultStr)
-{
-    BOOL fEmpty;
-    WCHAR wstr0[256 + 1];
-    ULONG ulcch0;
-
-    while (pPropRange->IsEmpty(ec, &fEmpty) == S_OK && !fEmpty)
-    {
-        wil::com_ptr_nothrow<ITfRange> backup_range;
-        if (FAILED(pPropRange->Clone(&backup_range)))
-        {
-            return E_FAIL;
-        }
-
-        //
-        // Retrieve property text if there exist.
-        //
-        ulcch0 = ARRAYSIZE(wstr0) - 1;
-        if (FAILED(pPropRange->GetText(ec,
-                                       TF_TF_MOVESTART, // Move range to next after get text.
-                                       wstr0,
-                                       ulcch0,
-                                       &ulcch0)))
-        {
-            return E_FAIL;
-        }
-
-        try
-        {
-            // see if there is a valid disp attribute
-            if (fCompExist == TRUE && result_comp <= 0)
-            {
-                if (guidatom == TF_INVALID_GUIDATOM)
-                {
-                    da.bAttr = TF_ATTR_INPUT;
-                }
-                CompGuid.insert(CompGuid.end(), ulcch0, guidatom);
-                CompStr.append(wstr0, ulcch0);
-            }
-            else if (bInWriteSession)
-            {
-                // if there's no disp attribute attached, it probably means
-                // the part of string is finalized.
-                //
-                ResultStr.append(wstr0, ulcch0);
-
-                // it was a 'determined' string
-                // so the doc has to shrink
-                //
-                LOG_IF_FAILED(ClearTextInRange(ec, backup_range.get()));
-            }
-            else
-            {
-                //
-                // Prevent infinite loop
-                //
-                break;
-            }
-        }
-        CATCH_RETURN();
-    }
-
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject::_GetNoDisplayAttributeRange
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionObject::_GetNoDisplayAttributeRange(TfEditCookie ec,
-                                                                      ITfRange* rangeIn,
-                                                                      const GUID** guids,
-                                                                      const int guid_size,
-                                                                      ITfRange* no_display_attribute_range)
-{
-    auto pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
-    if (pic == nullptr)
-    {
-        return E_FAIL;
-    }
-
-    wil::com_ptr_nothrow<ITfReadOnlyProperty> propComp;
-    auto hr = pic->TrackProperties(guids, guid_size, // system property
-                                   nullptr,
-                                   0, // application property
-                                   &propComp);
-    if (FAILED(hr))
-    {
-        return hr;
-    }
-
-    wil::com_ptr_nothrow<IEnumTfRanges> enumComp;
-    hr = propComp->EnumRanges(ec, &enumComp, rangeIn);
-    if (FAILED(hr))
-    {
-        return hr;
-    }
-
-    wil::com_ptr_nothrow<ITfRange> pRange;
-
-    while (enumComp->Next(1, &pRange, nullptr) == S_OK)
-    {
-        VARIANT var;
-        auto fCompExist = FALSE;
-
-        hr = propComp->GetValue(ec, pRange.get(), &var);
-        if (S_OK == hr)
-        {
-            wil::com_ptr_nothrow<IEnumTfPropertyValue> EnumPropVal;
-            if (wil::try_com_query_to(var.punkVal, &EnumPropVal))
-            {
-                TF_PROPERTYVAL tfPropertyVal;
-
-                while (EnumPropVal->Next(1, &tfPropertyVal, nullptr) == S_OK)
-                {
-                    for (auto i = 0; i < guid_size; i++)
-                    {
-                        if (IsEqualGUID(tfPropertyVal.guidId, *guids[i]))
-                        {
-                            if ((V_VT(&tfPropertyVal.varValue) == VT_I4 && V_I4(&tfPropertyVal.varValue) != 0))
-                            {
-                                fCompExist = TRUE;
-                                break;
-                            }
-                        }
-                    }
-
-                    VariantClear(&tfPropertyVal.varValue);
-
-                    if (fCompExist)
-                    {
-                        break;
-                    }
-                }
-            }
-        }
-
-        if (!fCompExist)
-        {
-            // Adjust GAP range's start anchor to the end of property range.
-            no_display_attribute_range->ShiftStartToRange(ec, pRange.get(), TF_ANCHOR_START);
-        }
-
-        VariantClear(&var);
-    }
-
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionCompositionComplete::CompComplete
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionCompositionComplete::CompComplete(TfEditCookie ec)
-{
-    auto pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
-    RETURN_HR_IF_NULL(E_FAIL, pic);
-
-    // Get the whole text, finalize it, and set empty string in TOM
-    wil::com_ptr_nothrow<ITfRange> spRange;
-    LONG cch;
-
-    RETURN_IF_FAILED(GetAllTextRange(ec, pic, &spRange, &cch));
-
-    // Check if a part of the range has already been finalized but not removed yet.
-    // Adjust the range appropriately to avoid inserting the same text twice.
-    auto cchCompleted = g_pConsoleTSF->GetCompletedRangeLength();
-    if ((cchCompleted > 0) &&
-        (cchCompleted < cch) &&
-        SUCCEEDED(spRange->ShiftStart(ec, cchCompleted, &cchCompleted, NULL)))
-    {
-        FAIL_FAST_IF(!((cchCompleted > 0) && (cchCompleted < cch)));
-        cch -= cchCompleted;
-    }
-    else
-    {
-        cchCompleted = 0;
-    }
-
-    // Get conversion area service.
-    auto conv_area = g_pConsoleTSF->GetConversionArea();
-    RETURN_HR_IF_NULL(E_FAIL, conv_area);
-
-    // If there is no string in TextStore we don't have to do anything.
-    if (!cch)
-    {
-        // Clear composition
-        LOG_IF_FAILED(conv_area->ClearComposition());
-        return S_OK;
-    }
-
-    auto hr = S_OK;
-    try
-    {
-        auto wstr = std::make_unique<WCHAR[]>(cch + 1);
-
-        // Get the whole text, finalize it, and erase the whole text.
-        if (SUCCEEDED(spRange->GetText(ec, TF_TF_IGNOREEND, wstr.get(), (ULONG)cch, (ULONG*)&cch)))
-        {
-            // Make Result String.
-            hr = conv_area->DrawResult({ wstr.get(), static_cast<size_t>(cch) });
-        }
-    }
-    CATCH_RETURN();
-
-    // Update the stored length of the completed fragment.
-    g_pConsoleTSF->SetCompletedRangeLength(cchCompleted + cch);
-
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionCompositionCleanup::EmptyCompositionRange()
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionCompositionCleanup::EmptyCompositionRange(TfEditCookie ec)
-{
-    if (!g_pConsoleTSF)
-    {
-        return E_FAIL;
-    }
-    if (!g_pConsoleTSF->IsPendingCompositionCleanup())
-    {
-        return S_OK;
-    }
-
-    auto hr = E_FAIL;
-    auto pic = g_pConsoleTSF->GetInputContext();
-    if (pic != nullptr)
-    {
-        // Cleanup (empty the context range) after the last composition.
-
-        hr = S_OK;
-        auto cchCompleted = g_pConsoleTSF->GetCompletedRangeLength();
-        if (cchCompleted != 0)
-        {
-            wil::com_ptr_nothrow<ITfRange> spRange;
-            LONG cch;
-            hr = GetAllTextRange(ec, pic, &spRange, &cch);
-            if (SUCCEEDED(hr))
-            {
-                // Clean up only the completed part (which start is expected to coincide with the start of the full range).
-                if (cchCompleted < cch)
-                {
-                    spRange->ShiftEnd(ec, (cchCompleted - cch), &cch, nullptr);
-                }
-                hr = ClearTextInRange(ec, spRange.get());
-                g_pConsoleTSF->SetCompletedRangeLength(0); // cleaned up all completed text
-            }
-        }
-    }
-    g_pConsoleTSF->OnCompositionCleanup(SUCCEEDED(hr));
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionUpdateCompositionString::UpdateCompositionString
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::UpdateCompositionString(TfEditCookie ec)
-{
-    HRESULT hr;
-
-    auto pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
-    if (pic == nullptr)
-    {
-        return E_FAIL;
-    }
-
-    // Reset the 'edit session requested' flag.
-    g_pConsoleTSF->OnEditSession();
-
-    // If the composition has been cancelled\finalized, no update necessary.
-    if (!g_pConsoleTSF->IsInComposition())
-    {
-        return S_OK;
-    }
-
-    BOOL bInWriteSession;
-    if (FAILED(hr = pic->InWriteSession(g_pConsoleTSF->GetTfClientId(), &bInWriteSession)))
-    {
-        return hr;
-    }
-
-    wil::com_ptr_nothrow<ITfRange> FullTextRange;
-    LONG lTextLength;
-    if (FAILED(hr = GetAllTextRange(ec, pic, &FullTextRange, &lTextLength)))
-    {
-        return hr;
-    }
-
-    wil::com_ptr_nothrow<ITfRange> InterimRange;
-    auto fInterim = FALSE;
-    if (FAILED(hr = _IsInterimSelection(ec, &InterimRange, &fInterim)))
-    {
-        return hr;
-    }
-
-    CicCategoryMgr* pCicCat = nullptr;
-    CicDisplayAttributeMgr* pDispAttr = nullptr;
-
-    //
-    // Create Cicero Category Manager and Display Attribute Manager
-    //
-    hr = _CreateCategoryAndDisplayAttributeManager(&pCicCat, &pDispAttr);
-    if (SUCCEEDED(hr))
-    {
-        if (fInterim)
-        {
-            hr = _MakeInterimString(ec,
-                                    FullTextRange.get(),
-                                    InterimRange.get(),
-                                    lTextLength,
-                                    bInWriteSession,
-                                    pCicCat,
-                                    pDispAttr);
-        }
-        else
-        {
-            hr = _MakeCompositionString(ec, FullTextRange.get(), bInWriteSession, pCicCat, pDispAttr);
-        }
-    }
-
-    if (pCicCat)
-    {
-        delete pCicCat;
-    }
-    if (pDispAttr)
-    {
-        delete pDispAttr;
-    }
-
-    return hr;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionUpdateCompositionString::_IsInterimSelection
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::_IsInterimSelection(TfEditCookie ec,
-                                                                               ITfRange** pInterimRange,
-                                                                               BOOL* pfInterim)
-{
-    auto pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
-    if (pic == nullptr)
-    {
-        return E_FAIL;
-    }
-
-    ULONG cFetched;
-
-    TF_SELECTION sel;
-    sel.range = nullptr;
-
-    *pfInterim = FALSE;
-    if (pic->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &sel, &cFetched) != S_OK)
-    {
-        // no selection. we can return S_OK.
-        return S_OK;
-    }
-
-    if (sel.style.fInterimChar && sel.range)
-    {
-        HRESULT hr;
-        if (FAILED(hr = sel.range->Clone(pInterimRange)))
-        {
-            SafeReleaseClear(sel.range);
-            return hr;
-        }
-
-        *pfInterim = TRUE;
-    }
-
-    SafeReleaseClear(sel.range);
-
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionUpdateCompositionString::_MakeCompositionString
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::_MakeCompositionString(TfEditCookie ec,
-                                                                                  ITfRange* FullTextRange,
-                                                                                  BOOL bInWriteSession,
-                                                                                  CicCategoryMgr* pCicCatMgr,
-                                                                                  CicDisplayAttributeMgr* pCicDispAttr)
-{
-    std::wstring CompStr;
-    std::vector<TfGuidAtom> CompGuid;
-    CCompCursorPos CompCursorPos;
-    std::wstring ResultStr;
-    auto fIgnorePreviousCompositionResult = FALSE;
-
-    RETURN_IF_FAILED(_GetTextAndAttribute(ec,
-                                          FullTextRange,
-                                          CompStr,
-                                          CompGuid,
-                                          ResultStr,
-                                          bInWriteSession,
-                                          pCicCatMgr,
-                                          pCicDispAttr));
-
-    if (g_pConsoleTSF && g_pConsoleTSF->IsPendingCompositionCleanup())
-    {
-        // Don't draw the previous composition result if there was a cleanup session requested for it.
-        fIgnorePreviousCompositionResult = TRUE;
-        // Cancel pending cleanup, since the ResultStr was cleared from the composition in _GetTextAndAttribute.
-        g_pConsoleTSF->OnCompositionCleanup(TRUE);
-    }
-
-    RETURN_IF_FAILED(_GetCursorPosition(ec, CompCursorPos));
-
-    // Get display attribute manager
-    auto dam = pCicDispAttr->GetDisplayAttributeMgr();
-    RETURN_HR_IF_NULL(E_FAIL, dam);
-
-    // Get category manager
-    auto cat = pCicCatMgr->GetCategoryMgr();
-    RETURN_HR_IF_NULL(E_FAIL, cat);
-
-    // Allocate and fill TF_DISPLAYATTRIBUTE
-    try
-    {
-        // Get conversion area service.
-        auto conv_area = g_pConsoleTSF ? g_pConsoleTSF->GetConversionArea() : nullptr;
-        RETURN_HR_IF_NULL(E_FAIL, conv_area);
-
-        if (!ResultStr.empty() && !fIgnorePreviousCompositionResult)
-        {
-            return conv_area->DrawResult(ResultStr);
-        }
-        if (!CompStr.empty())
-        {
-            const auto cchDisplayAttribute = CompGuid.size();
-            std::vector<TF_DISPLAYATTRIBUTE> DisplayAttributes;
-            DisplayAttributes.reserve(cchDisplayAttribute);
-
-            for (size_t i = 0; i < cchDisplayAttribute; i++)
-            {
-                TF_DISPLAYATTRIBUTE da;
-                ZeroMemory(&da, sizeof(da));
-                da.bAttr = TF_ATTR_OTHER;
-
-                GUID guid;
-                if (SUCCEEDED(cat->GetGUID(CompGuid.at(i), &guid)))
-                {
-                    CLSID clsid;
-                    wil::com_ptr_nothrow<ITfDisplayAttributeInfo> dai;
-                    if (SUCCEEDED(dam->GetDisplayAttributeInfo(guid, &dai, &clsid)))
-                    {
-                        dai->GetAttributeInfo(&da);
-                    }
-                }
-
-                DisplayAttributes.emplace_back(da);
-            }
-
-            return conv_area->DrawComposition(CompStr, // composition string
-                                              DisplayAttributes, // display attributes
-                                              CompCursorPos.GetCursorPosition()); // cursor position
-        }
-    }
-    CATCH_RETURN();
-
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionUpdateCompositionString::_MakeInterimString
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::_MakeInterimString(TfEditCookie ec,
-                                                                              ITfRange* FullTextRange,
-                                                                              ITfRange* InterimRange,
-                                                                              LONG lTextLength,
-                                                                              BOOL bInWriteSession,
-                                                                              CicCategoryMgr* pCicCatMgr,
-                                                                              CicDisplayAttributeMgr* pCicDispAttr)
-{
-    LONG lStartResult;
-    LONG lEndResult;
-
-    FullTextRange->CompareStart(ec, InterimRange, TF_ANCHOR_START, &lStartResult);
-    RETURN_HR_IF(E_FAIL, lStartResult > 0);
-
-    FullTextRange->CompareEnd(ec, InterimRange, TF_ANCHOR_END, &lEndResult);
-    RETURN_HR_IF(E_FAIL, lEndResult < 0);
-
-    if (lStartResult < 0)
-    {
-        // Make result string.
-        RETURN_IF_FAILED(FullTextRange->ShiftEndToRange(ec, InterimRange, TF_ANCHOR_START));
-
-        // Interim char assume 1 char length.
-        // Full text length - 1 means result string length.
-        lTextLength--;
-
-        FAIL_FAST_IF(!(lTextLength > 0));
-
-        if (lTextLength > 0)
-        {
-            try
-            {
-                auto wstr = std::make_unique<WCHAR[]>(lTextLength + 1);
-
-                // Get the result text, finalize it, and erase the result text.
-                if (SUCCEEDED(FullTextRange->GetText(ec, TF_TF_IGNOREEND, wstr.get(), (ULONG)lTextLength, (ULONG*)&lTextLength)))
-                {
-                    // Clear the TOM
-                    LOG_IF_FAILED(ClearTextInRange(ec, FullTextRange));
-                }
-            }
-            CATCH_RETURN();
-        }
-    }
-
-    // Make interim character
-    std::wstring CompStr;
-    std::vector<TfGuidAtom> CompGuid;
-    std::wstring _tempResultStr;
-
-    RETURN_IF_FAILED(_GetTextAndAttribute(ec,
-                                          InterimRange,
-                                          CompStr,
-                                          CompGuid,
-                                          _tempResultStr,
-                                          bInWriteSession,
-                                          pCicCatMgr,
-                                          pCicDispAttr));
-
-    // Get display attribute manager
-    auto dam = pCicDispAttr->GetDisplayAttributeMgr();
-    RETURN_HR_IF_NULL(E_FAIL, dam);
-
-    // Get category manager
-    auto cat = pCicCatMgr->GetCategoryMgr();
-    RETURN_HR_IF_NULL(E_FAIL, cat);
-
-    // Allocate and fill TF_DISPLAYATTRIBUTE
-    try
-    {
-        // Get conversion area service.
-        auto conv_area = g_pConsoleTSF ? g_pConsoleTSF->GetConversionArea() : nullptr;
-        RETURN_HR_IF_NULL(E_FAIL, conv_area);
-
-        if (!CompStr.empty())
-        {
-            const auto cchDisplayAttribute = CompGuid.size();
-            std::vector<TF_DISPLAYATTRIBUTE> DisplayAttributes;
-            DisplayAttributes.reserve(cchDisplayAttribute);
-
-            for (size_t i = 0; i < cchDisplayAttribute; i++)
-            {
-                TF_DISPLAYATTRIBUTE da;
-                ZeroMemory(&da, sizeof(da));
-                da.bAttr = TF_ATTR_OTHER;
-                GUID guid;
-                if (SUCCEEDED(cat->GetGUID(CompGuid.at(i), &guid)))
-                {
-                    CLSID clsid;
-                    wil::com_ptr_nothrow<ITfDisplayAttributeInfo> dai;
-                    if (SUCCEEDED(dam->GetDisplayAttributeInfo(guid, &dai, &clsid)))
-                    {
-                        dai->GetAttributeInfo(&da);
-                    }
-                }
-
-                DisplayAttributes.emplace_back(da);
-            }
-
-            return conv_area->DrawComposition(CompStr, // composition string (Interim string)
-                                              DisplayAttributes); // display attributes
-        }
-    }
-    CATCH_RETURN();
-
-    return S_OK;
-}
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionUpdateCompositionString::_CreateCategoryAndDisplayAttributeManager
-//
-//----------------------------------------------------------------------------
-
-[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::_CreateCategoryAndDisplayAttributeManager(
-    CicCategoryMgr** pCicCatMgr,
-    CicDisplayAttributeMgr** pCicDispAttr)
-{
-    auto hr = E_OUTOFMEMORY;
-
-    CicCategoryMgr* pTmpCat = nullptr;
-    CicDisplayAttributeMgr* pTmpDispAttr = nullptr;
-
-    //
-    // Create Cicero Category Manager
-    //
-    pTmpCat = new (std::nothrow) CicCategoryMgr;
-    if (pTmpCat)
-    {
-        if (SUCCEEDED(hr = pTmpCat->InitCategoryInstance()))
-        {
-            auto pcat = pTmpCat->GetCategoryMgr();
-            if (pcat)
-            {
-                //
-                // Create Cicero Display Attribute Manager
-                //
-                pTmpDispAttr = new (std::nothrow) CicDisplayAttributeMgr;
-                if (pTmpDispAttr)
-                {
-                    if (SUCCEEDED(hr = pTmpDispAttr->InitDisplayAttributeInstance(pcat)))
-                    {
-                        *pCicCatMgr = pTmpCat;
-                        *pCicDispAttr = pTmpDispAttr;
-                    }
-                }
-            }
-        }
-    }
-
-    if (FAILED(hr))
-    {
-        if (pTmpCat)
-        {
-            delete pTmpCat;
-        }
-        if (pTmpDispAttr)
-        {
-            delete pTmpDispAttr;
-        }
-    }
-
-    return hr;
-}
diff --git a/src/tsf/TfEditSession.h b/src/tsf/TfEditSession.h
deleted file mode 100644
index 61ac5c1cac5..00000000000
--- a/src/tsf/TfEditSession.h
+++ /dev/null
@@ -1,219 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfEditSession.h
-
-Abstract:
-
-    This file defines the CEditSessionObject Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#pragma once
-
-class CicCategoryMgr;
-class CicDisplayAttributeMgr;
-
-/* 183C627A-B46C-44ad-B797-82F6BEC82131 */
-const GUID GUID_PROP_CONIME_TRACKCOMPOSITION = {
-    0x183c627a,
-    0xb46c,
-    0x44ad,
-    { 0xb7, 0x97, 0x82, 0xf6, 0xbe, 0xc8, 0x21, 0x31 }
-};
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionObject
-//
-//----------------------------------------------------------------------------
-
-class CEditSessionObject : public ITfEditSession
-{
-public:
-    CEditSessionObject() :
-        m_cRef(1) {}
-    virtual ~CEditSessionObject() = default;
-
-public:
-    //
-    // IUnknown methods
-    //
-    STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj);
-    STDMETHODIMP_(ULONG)
-    AddRef(void);
-    STDMETHODIMP_(ULONG)
-    Release(void);
-
-    //
-    // ITfEditSession method
-    //
-    STDMETHODIMP DoEditSession(TfEditCookie ec)
-    {
-        auto hr = _DoEditSession(ec);
-        Release(); // Release reference count for asynchronous edit session.
-        return hr;
-    }
-
-    //
-    // ImmIfSessionObject methods
-    //
-protected:
-    [[nodiscard]] virtual HRESULT _DoEditSession(TfEditCookie ec) = 0;
-
-    //
-    // EditSession methods.
-    //
-public:
-    [[nodiscard]] static HRESULT GetAllTextRange(TfEditCookie ec,
-                                                 ITfContext* ic,
-                                                 ITfRange** range,
-                                                 LONG* lpTextLength,
-                                                 TF_HALTCOND* lpHaltCond = nullptr);
-
-protected:
-    [[nodiscard]] HRESULT SetTextInRange(TfEditCookie ec,
-                                         ITfRange* range,
-                                         __in_ecount_opt(len) LPWSTR psz,
-                                         DWORD len);
-    [[nodiscard]] HRESULT ClearTextInRange(TfEditCookie ec,
-                                           ITfRange* range);
-
-    [[nodiscard]] HRESULT _GetTextAndAttribute(TfEditCookie ec,
-                                               ITfRange* range,
-                                               std::wstring& CompStr,
-                                               std::vector<TfGuidAtom> CompGuid,
-                                               BOOL bInWriteSession,
-                                               CicCategoryMgr* pCicCatMgr,
-                                               CicDisplayAttributeMgr* pCicDispAttr)
-    {
-        std::wstring ResultStr;
-        return _GetTextAndAttribute(ec, range, CompStr, CompGuid, ResultStr, bInWriteSession, pCicCatMgr, pCicDispAttr);
-    }
-
-    [[nodiscard]] HRESULT _GetTextAndAttribute(TfEditCookie ec,
-                                               ITfRange* range,
-                                               std::wstring& CompStr,
-                                               std::vector<TfGuidAtom>& CompGuid,
-                                               std::wstring& ResultStr,
-                                               BOOL bInWriteSession,
-                                               CicCategoryMgr* pCicCatMgr,
-                                               CicDisplayAttributeMgr* pCicDispAttr);
-
-    [[nodiscard]] HRESULT _GetTextAndAttributeGapRange(TfEditCookie ec,
-                                                       ITfRange* gap_range,
-                                                       LONG result_comp,
-                                                       std::wstring& CompStr,
-                                                       std::vector<TfGuidAtom>& CompGuid,
-                                                       std::wstring& ResultStr);
-
-    [[nodiscard]] HRESULT _GetTextAndAttributePropertyRange(TfEditCookie ec,
-                                                            ITfRange* pPropRange,
-                                                            BOOL fDispAttribute,
-                                                            LONG result_comp,
-                                                            BOOL bInWriteSession,
-                                                            TF_DISPLAYATTRIBUTE da,
-                                                            TfGuidAtom guidatom,
-                                                            std::wstring& CompStr,
-                                                            std::vector<TfGuidAtom>& CompGuid,
-                                                            std::wstring& ResultStr);
-
-    [[nodiscard]] HRESULT _GetNoDisplayAttributeRange(TfEditCookie ec,
-                                                      ITfRange* range,
-                                                      const GUID** guids,
-                                                      const int guid_size,
-                                                      ITfRange* no_display_attribute_range);
-
-    [[nodiscard]] HRESULT _GetCursorPosition(TfEditCookie ec,
-                                             CCompCursorPos& CompCursorPos);
-
-private:
-    int m_cRef;
-};
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionCompositionComplete
-//
-//----------------------------------------------------------------------------
-
-class CEditSessionCompositionComplete : public CEditSessionObject
-{
-public:
-    CEditSessionCompositionComplete() = default;
-
-    [[nodiscard]] HRESULT _DoEditSession(TfEditCookie ec)
-    {
-        return CompComplete(ec);
-    }
-
-    [[nodiscard]] HRESULT CompComplete(TfEditCookie ec);
-};
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionCompositionCleanup
-//
-//----------------------------------------------------------------------------
-
-class CEditSessionCompositionCleanup : public CEditSessionObject
-{
-public:
-    CEditSessionCompositionCleanup() = default;
-
-    [[nodiscard]] HRESULT _DoEditSession(TfEditCookie ec)
-    {
-        return EmptyCompositionRange(ec);
-    }
-
-    [[nodiscard]] HRESULT EmptyCompositionRange(TfEditCookie ec);
-};
-
-//+---------------------------------------------------------------------------
-//
-// CEditSessionUpdateCompositionString
-//
-//----------------------------------------------------------------------------
-
-class CEditSessionUpdateCompositionString : public CEditSessionObject
-{
-public:
-    CEditSessionUpdateCompositionString() = default;
-
-    [[nodiscard]] HRESULT _DoEditSession(TfEditCookie ec)
-    {
-        return UpdateCompositionString(ec);
-    }
-
-    [[nodiscard]] HRESULT UpdateCompositionString(TfEditCookie ec);
-
-private:
-    [[nodiscard]] HRESULT _IsInterimSelection(TfEditCookie ec, ITfRange** pInterimRange, BOOL* pfInterim);
-
-    [[nodiscard]] HRESULT _MakeCompositionString(TfEditCookie ec,
-                                                 ITfRange* FullTextRange,
-                                                 BOOL bInWriteSession,
-                                                 CicCategoryMgr* pCicCatMgr,
-                                                 CicDisplayAttributeMgr* pCicDispAttr);
-
-    [[nodiscard]] HRESULT _MakeInterimString(TfEditCookie ec,
-                                             ITfRange* FullTextRange,
-                                             ITfRange* InterimRange,
-                                             LONG lTextLength,
-                                             BOOL bInWriteSession,
-                                             CicCategoryMgr* pCicCatMgr,
-                                             CicDisplayAttributeMgr* pCicDispAttr);
-
-    [[nodiscard]] HRESULT _CreateCategoryAndDisplayAttributeManager(CicCategoryMgr** pCicCatMgr,
-                                                                    CicDisplayAttributeMgr** pCicDispAttr);
-};
diff --git a/src/tsf/TfTxtevCb.cpp b/src/tsf/TfTxtevCb.cpp
deleted file mode 100644
index 90bac9db2da..00000000000
--- a/src/tsf/TfTxtevCb.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    TfTxtevCb.cpp
-
-Abstract:
-
-    This file implements the CTextEventSinkCallBack Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#include "precomp.h"
-#include "ConsoleTSF.h"
-#include "TfEditSession.h"
-
-//+---------------------------------------------------------------------------
-//
-// CConsoleTSF::HasCompositionChanged
-//
-//----------------------------------------------------------------------------
-
-BOOL CConsoleTSF::_HasCompositionChanged(ITfContext* pInputContext, TfEditCookie ecReadOnly, ITfEditRecord* pEditRecord)
-{
-    BOOL fChanged;
-    if (SUCCEEDED(pEditRecord->GetSelectionStatus(&fChanged)))
-    {
-        if (fChanged)
-        {
-            return TRUE;
-        }
-    }
-
-    //
-    // Find GUID_PROP_CONIME_TRACKCOMPOSITION property.
-    //
-
-    wil::com_ptr_nothrow<ITfProperty> Property;
-    wil::com_ptr_nothrow<ITfRange> FoundRange;
-    wil::com_ptr_nothrow<ITfProperty> PropertyTrackComposition;
-
-    auto bFound = FALSE;
-
-    if (SUCCEEDED(pInputContext->GetProperty(GUID_PROP_CONIME_TRACKCOMPOSITION, &Property)))
-    {
-        wil::com_ptr_nothrow<IEnumTfRanges> EnumFindFirstTrackCompRange;
-
-        if (SUCCEEDED(Property->EnumRanges(ecReadOnly, &EnumFindFirstTrackCompRange, NULL)))
-        {
-            HRESULT hr;
-            wil::com_ptr_nothrow<ITfRange> range;
-
-            while ((hr = EnumFindFirstTrackCompRange->Next(1, &range, nullptr)) == S_OK)
-            {
-                VARIANT var;
-                VariantInit(&var);
-
-                hr = Property->GetValue(ecReadOnly, range.get(), &var);
-                if (SUCCEEDED(hr))
-                {
-                    if ((V_VT(&var) == VT_I4 && V_I4(&var) != 0))
-                    {
-                        range->Clone(&FoundRange);
-                        bFound = TRUE; // FOUND!!
-                        break;
-                    }
-                }
-
-                VariantClear(&var);
-
-                if (bFound)
-                {
-                    break; // FOUND!!
-                }
-            }
-        }
-    }
-
-    //
-    // if there is no track composition property,
-    // the composition has been changed since we put it.
-    //
-    if (!bFound)
-    {
-        return TRUE;
-    }
-
-    if (FoundRange == nullptr)
-    {
-        return FALSE;
-    }
-
-    bFound = FALSE; // RESET bFound flag...
-
-    wil::com_ptr_nothrow<ITfRange> rangeTrackComposition;
-    if (SUCCEEDED(FoundRange->Clone(&rangeTrackComposition)))
-    {
-        //
-        // get the text range that does not include read only area for
-        // reconversion.
-        //
-        wil::com_ptr_nothrow<ITfRange> rangeAllText;
-        LONG cch;
-        if (SUCCEEDED(CEditSessionObject::GetAllTextRange(ecReadOnly, pInputContext, &rangeAllText, &cch)))
-        {
-            LONG lResult;
-            if (SUCCEEDED(rangeTrackComposition->CompareStart(ecReadOnly, rangeAllText.get(), TF_ANCHOR_START, &lResult)))
-            {
-                //
-                // if the start position of the track composition range is not
-                // the beginning of IC,
-                // the composition has been changed since we put it.
-                //
-                if (lResult != 0)
-                {
-                    bFound = TRUE; // FOUND!!
-                }
-                else if (SUCCEEDED(rangeTrackComposition->CompareEnd(ecReadOnly, rangeAllText.get(), TF_ANCHOR_END, &lResult)))
-                {
-                    //
-                    // if the start position of the track composition range is not
-                    // the beginning of IC,
-                    // the composition has been changed since we put it.
-                    //
-                    //
-                    // If we find the changes in these property, we need to update hIMC.
-                    //
-                    const GUID* guids[] = { &GUID_PROP_COMPOSING,
-                                            &GUID_PROP_ATTRIBUTE };
-                    const int guid_size = sizeof(guids) / sizeof(GUID*);
-
-                    wil::com_ptr_nothrow<IEnumTfRanges> EnumPropertyChanged;
-
-                    if (lResult != 0)
-                    {
-                        bFound = TRUE; // FOUND!!
-                    }
-                    else if (SUCCEEDED(pEditRecord->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, guids, guid_size, &EnumPropertyChanged)))
-                    {
-                        HRESULT hr;
-                        wil::com_ptr_nothrow<ITfRange> range;
-
-                        while ((hr = EnumPropertyChanged->Next(1, &range, nullptr)) == S_OK)
-                        {
-                            BOOL empty;
-                            if (range->IsEmpty(ecReadOnly, &empty) == S_OK && empty)
-                            {
-                                continue;
-                            }
-
-                            bFound = TRUE; // FOUND!!
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-    }
-    return bFound;
-}
diff --git a/src/tsf/contsf.cpp b/src/tsf/contsf.cpp
deleted file mode 100644
index 81a98c5b84e..00000000000
--- a/src/tsf/contsf.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-
-CConsoleTSF* g_pConsoleTSF = nullptr;
-
-extern "C" BOOL ActivateTextServices(HWND hwndConsole, GetSuggestionWindowPos pfnPosition, GetTextBoxAreaPos pfnTextArea)
-{
-    if (!g_pConsoleTSF && hwndConsole)
-    {
-        g_pConsoleTSF = new (std::nothrow) CConsoleTSF(hwndConsole, pfnPosition, pfnTextArea);
-        if (g_pConsoleTSF && SUCCEEDED(g_pConsoleTSF->Initialize()))
-        {
-            // Conhost calls this function only when the console window has focus.
-            g_pConsoleTSF->SetFocus(TRUE);
-        }
-        else
-        {
-            SafeReleaseClear(g_pConsoleTSF);
-        }
-    }
-    return g_pConsoleTSF ? TRUE : FALSE;
-}
-
-extern "C" void DeactivateTextServices()
-{
-    if (g_pConsoleTSF)
-    {
-        g_pConsoleTSF->Uninitialize();
-        SafeReleaseClear(g_pConsoleTSF);
-    }
-}
diff --git a/src/tsf/globals.h b/src/tsf/globals.h
deleted file mode 100644
index 0fc503f0702..00000000000
--- a/src/tsf/globals.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*++
-
-Copyright (c) Microsoft Corporation.
-Licensed under the MIT license.
-
-Module Name:
-
-    globals.h
-
-Abstract:
-
-    Contains declarations for all globally scoped names in the program.
-    This file defines the CBoolean Interface Class.
-
-Author:
-
-Revision History:
-
-Notes:
-
---*/
-
-#pragma once
-
-//
-// SAFECAST(obj, type)
-//
-// This macro is extremely useful for enforcing strong typechecking on other
-// macros.  It generates no code.
-//
-// Simply insert this macro at the beginning of an expression list for
-// each parameter that must be typechecked.  For example, for the
-// definition of MYMAX(x, y), where x and y absolutely must be integers,
-// use:
-//
-//   #define MYMAX(x, y)    (SAFECAST(x, int), SAFECAST(y, int), ((x) > (y) ? (x) : (y)))
-//
-//
-#define SAFECAST(_obj, _type) (((_type)(_obj) == (_obj) ? 0 : 0), (_type)(_obj))
-
-//
-// Bitfields don't get along too well with bools,
-// so here's an easy way to convert them:
-//
-#define BOOLIFY(expr) (!!(expr))
-
-//
-// generic COM stuff
-//
-#define SafeReleaseClear(punk) \
-    {                          \
-        if ((punk) != NULL)    \
-        {                      \
-            (punk)->Release(); \
-            (punk) = NULL;     \
-        }                      \
-    }
diff --git a/src/tsf/precomp.h b/src/tsf/precomp.h
index d81ef6efb8c..bf7aac22627 100644
--- a/src/tsf/precomp.h
+++ b/src/tsf/precomp.h
@@ -37,16 +37,11 @@ extern "C" {
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
+#include <string_view>
+#include <span>
 
 #include <msctf.h> // Cicero header
 #include <tsattrs.h> // ITextStore standard attributes
 
 // This includes support libraries from the CRT, STL, WIL, and GSL
 #include "LibraryIncludes.h"
-
-#include "../inc/contsf.h"
-
-#include "globals.h"
-
-#include "ConsoleTSF.h"
-#include "TfCtxtComp.h"
diff --git a/src/tsf/sources b/src/tsf/sources
index 54c79162097..648f380a056 100644
--- a/src/tsf/sources
+++ b/src/tsf/sources
@@ -42,17 +42,10 @@ PRECOMPILED_PCH         = precomp.pch
 PRECOMPILED_OBJ         = precomp.obj
 
 SOURCES = \
-    contsf.cpp \
-    ConsoleTSF.cpp \
-    TfConvArea.cpp \
-    TfCatUtil.cpp \
-    TfDispAttr.cpp \
-    TfEditSession.cpp \
-    TfTxtevCb.cpp \
+    Handle.cpp \
+    Implementation.cpp \
 
 INCLUDES = \
     $(INCLUDES); \
     ..\inc; \
     $(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \
-    $(SDK_INC_PATH)\atl30; \
-    $(ONECORE_EXTERNAL_SDK_INC_PATH)\atl30; \
diff --git a/src/tsf/tsf.vcxproj b/src/tsf/tsf.vcxproj
index 26da838865e..97217966bd0 100644
--- a/src/tsf/tsf.vcxproj
+++ b/src/tsf/tsf.vcxproj
@@ -11,29 +11,18 @@
   <Import Project="$(SolutionDir)src\common.build.pre.props" />
   <Import Project="$(SolutionDir)src\common.nugetversions.props" />
   <ItemGroup>
-    <ClCompile Include="ConsoleTSF.cpp" />
-    <ClCompile Include="contsf.cpp" />
-    <ClCompile Include="TfCatUtil.cpp" />
-    <ClCompile Include="TfConvArea.cpp" />
-    <ClCompile Include="TfDispAttr.cpp" />
-    <ClCompile Include="TfEditSession.cpp" />
-    <ClCompile Include="TfTxtevCb.cpp" />
+    <ClCompile Include="Handle.cpp" />
+    <ClCompile Include="Implementation.cpp" />
     <ClCompile Include="precomp.cpp">
       <PrecompiledHeader>Create</PrecompiledHeader>
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
-    <ClInclude Include="ConsoleTSF.h" />
-    <ClInclude Include="debug.h" />
-    <ClInclude Include="globals.h" />
+    <ClInclude Include="Handle.h" />
+    <ClInclude Include="Implementation.h" />
     <ClInclude Include="precomp.h" />
-    <ClInclude Include="TfCatUtil.h" />
-    <ClInclude Include="TfConvArea.h" />
-    <ClInclude Include="TfCtxtComp.h" />
-    <ClInclude Include="TfDispAttr.h" />
-    <ClInclude Include="TfEditSession.h" />
   </ItemGroup>
   <!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
   <Import Project="$(SolutionDir)src\common.build.post.props" />
   <Import Project="$(SolutionDir)src\common.nugetversions.targets" />
-</Project>
+</Project>
\ No newline at end of file
diff --git a/src/tsf/tsf.vcxproj.filters b/src/tsf/tsf.vcxproj.filters
index 3b4f7739ed4..21b300cc00f 100644
--- a/src/tsf/tsf.vcxproj.filters
+++ b/src/tsf/tsf.vcxproj.filters
@@ -15,58 +15,29 @@
     </Filter>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="ConsoleTSF.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
-    <ClCompile Include="contsf.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
-    <ClCompile Include="TfCatUtil.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
-    <ClCompile Include="TfConvArea.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
-    <ClCompile Include="TfDispAttr.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
-    <ClCompile Include="TfEditSession.cpp">
+    <ClCompile Include="precomp.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="TfTxtevCb.cpp">
+    <ClCompile Include="Handle.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="precomp.cpp">
+    <ClCompile Include="Implementation.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
-    <ClInclude Include="ConsoleTSF.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
-    <ClInclude Include="debug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
-    <ClInclude Include="globals.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="precomp.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="TfCatUtil.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
-    <ClInclude Include="TfConvArea.h">
+    <ClInclude Include="Handle.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="TfCtxtComp.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
-    <ClInclude Include="TfDispAttr.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
-    <ClInclude Include="TfEditSession.h">
+    <ClInclude Include="Implementation.h">
       <Filter>Header Files</Filter>
     </ClInclude>
   </ItemGroup>
-</Project>
+  <ItemGroup>
+    <Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
+    <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
+  </ItemGroup>
+</Project>
\ No newline at end of file


Test patch: