From 9d771f5aa6340b1ce0032cd2723e9442e76d40c7 Mon Sep 17 00:00:00 2001
From: Mike Griese <migrie@microsoft.com>
Date: Mon, 3 Aug 2020 10:46:00 -0500
Subject: [PATCH 1/7] This works to initialize the tabs with the current color,
 but doesn't hot-reload the color

---
 .../TerminalApp/AppActionHandlers.cpp         |   4 +-
 src/cascadia/TerminalApp/Profile.cpp          |  10 +
 src/cascadia/TerminalApp/Profile.h            |   1 +
 src/cascadia/TerminalApp/Tab.cpp              | 202 +++++++++++-------
 src/cascadia/TerminalApp/Tab.h                |  11 +-
 src/cascadia/TerminalApp/TerminalPage.cpp     |  20 +-
 src/cascadia/TerminalControl/TermControl.cpp  |   6 +
 src/cascadia/TerminalControl/TermControl.h    |   2 +
 src/cascadia/TerminalControl/TermControl.idl  |   2 +
 src/cascadia/TerminalCore/Terminal.cpp        |  14 ++
 src/cascadia/TerminalCore/Terminal.hpp        |   3 +
 src/cascadia/TerminalCore/pch.h               |   1 +
 .../TerminalSettings/ICoreSettings.idl        |   2 +
 .../TerminalSettings/terminalsettings.h       |   2 +
 src/types/inc/utils.hpp                       |  24 +++
 15 files changed, 215 insertions(+), 89 deletions(-)

diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp
index 63a2743d80b..edc9c642000 100644
--- a/src/cascadia/TerminalApp/AppActionHandlers.cpp
+++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp
@@ -290,11 +290,11 @@ namespace winrt::TerminalApp::implementation
         {
             if (tabColor.has_value())
             {
-                activeTab->SetTabColor(tabColor.value());
+                activeTab->SetRuntimeTabColor(tabColor.value());
             }
             else
             {
-                activeTab->ResetTabColor();
+                activeTab->ResetRuntimeTabColor();
             }
         }
         args.Handled(true);
diff --git a/src/cascadia/TerminalApp/Profile.cpp b/src/cascadia/TerminalApp/Profile.cpp
index 7cd80a65b11..7d85745bb19 100644
--- a/src/cascadia/TerminalApp/Profile.cpp
+++ b/src/cascadia/TerminalApp/Profile.cpp
@@ -52,6 +52,7 @@ static constexpr std::string_view BackgroundImageStretchModeKey{ "backgroundImag
 static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" };
 static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" };
 static constexpr std::string_view AntialiasingModeKey{ "antialiasingMode" };
+static constexpr std::string_view TabColorKey{ "tabColor" };
 
 Profile::Profile() :
     Profile(std::nullopt)
@@ -231,6 +232,13 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
 
     terminalSettings.AntialiasingMode(_antialiasingMode);
 
+    if (_tabColor)
+    {
+        uint32_t c = _tabColor.value();
+        winrt::Windows::Foundation::IReference<uint32_t> cr{ c };
+        terminalSettings.TabColor(cr);
+    }
+
     return terminalSettings;
 }
 
@@ -404,6 +412,8 @@ void Profile::LayerJson(const Json::Value& json)
     JsonUtils::GetValueForKey(json, BackgroundImageAlignmentKey, _backgroundImageAlignment);
     JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _retroTerminalEffect);
     JsonUtils::GetValueForKey(json, AntialiasingModeKey, _antialiasingMode);
+
+    JsonUtils::GetValueForKey(json, TabColorKey, _tabColor);
 }
 
 void Profile::SetFontFace(std::wstring fontFace) noexcept
diff --git a/src/cascadia/TerminalApp/Profile.h b/src/cascadia/TerminalApp/Profile.h
index 17edad98bb6..c1d11cc068c 100644
--- a/src/cascadia/TerminalApp/Profile.h
+++ b/src/cascadia/TerminalApp/Profile.h
@@ -117,6 +117,7 @@ class TerminalApp::Profile final
     std::optional<til::color> _selectionBackground;
     std::optional<til::color> _cursorColor;
     std::optional<std::wstring> _tabTitle;
+    std::optional<til::color> _tabColor;
     bool _suppressApplicationTitle;
     int32_t _historySize;
     bool _snapOnInput;
diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp
index 51e54be09ee..bd559f02079 100644
--- a/src/cascadia/TerminalApp/Tab.cpp
+++ b/src/cascadia/TerminalApp/Tab.cpp
@@ -8,6 +8,7 @@
 #include "Tab.g.cpp"
 #include "Utils.h"
 #include "ColorHelper.h"
+#include "../../types/inc/utils.hpp"
 
 using namespace winrt;
 using namespace winrt::Windows::UI::Xaml;
@@ -15,6 +16,7 @@ using namespace winrt::Windows::UI::Core;
 using namespace winrt::Microsoft::Terminal::Settings;
 using namespace winrt::Microsoft::Terminal::TerminalControl;
 using namespace winrt::Windows::System;
+using namespace ::Microsoft::Console;
 
 namespace winrt
 {
@@ -55,6 +57,7 @@ namespace winrt::TerminalApp::implementation
         });
 
         _UpdateTitle();
+        _RecalculateAndApplyTabColor();
     }
 
     // Method Description:
@@ -480,6 +483,7 @@ namespace winrt::TerminalApp::implementation
             if (tab && sender != tab->_activePane)
             {
                 tab->_UpdateActivePane(sender);
+                tab->_RecalculateAndApplyTabColor();
             }
         });
     }
@@ -530,14 +534,14 @@ namespace winrt::TerminalApp::implementation
         _tabColorPickup.ColorSelected([weakThis](auto newTabColor) {
             if (auto tab{ weakThis.get() })
             {
-                tab->SetTabColor(newTabColor);
+                tab->SetRuntimeTabColor(newTabColor);
             }
         });
 
         _tabColorPickup.ColorCleared([weakThis]() {
             if (auto tab{ weakThis.get() })
             {
-                tab->ResetTabColor();
+                tab->ResetRuntimeTabColor();
             }
         });
 
@@ -707,114 +711,164 @@ namespace winrt::TerminalApp::implementation
     // - The tab's color, if any
     std::optional<winrt::Windows::UI::Color> Tab::GetTabColor()
     {
-        return _tabColor;
+        const auto currControlColor{ GetActiveTerminalControl().TabColor() };
+        std::optional<winrt::Windows::UI::Color> controlTabColor;
+        if (currControlColor != nullptr)
+        {
+            controlTabColor = currControlColor.Value();
+        }
+        return Utils::CoalesceOptionalsOrNot(_runtimeTabColor,
+                                             controlTabColor,
+                                             _themeTabColor,
+                                             std::optional<Windows::UI::Color>(std::nullopt));
     }
 
     // Method Description:
-    // - Sets the tab background color to the color chosen by the user
+    // - Sets the runtime tab background color to the color chosen by the user
     // - Sets the tab foreground color depending on the luminance of
     // the background color
     // Arguments:
-    // - color: the shiny color the user picked for their tab
+    // - color: the color the user picked for their tab
     // Return Value:
     // - <none>
-    void Tab::SetTabColor(const winrt::Windows::UI::Color& color)
+    void Tab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color)
+    {
+        _runtimeTabColor.emplace(color);
+        _RecalculateAndApplyTabColor();
+    }
+
+    // Method Description:
+    // - This function dispatches a function to the UI thread to recalculate
+    //   what this tab's current background color should be. If a color is set,
+    //   it will apply the given color to the tab's background. Otherwise, it
+    //   will clear the tab's background color.
+    // Arguments:
+    // - <none>
+    // Return Value:
+    // - <none>
+    void Tab::_RecalculateAndApplyTabColor()
     {
         auto weakThis{ get_weak() };
 
-        _tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis, color]() {
+        _tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
             auto ptrTab = weakThis.get();
             if (!ptrTab)
                 return;
 
             auto tab{ ptrTab };
-            Media::SolidColorBrush selectedTabBrush{};
-            Media::SolidColorBrush deselectedTabBrush{};
-            Media::SolidColorBrush fontBrush{};
-            Media::SolidColorBrush hoverTabBrush{};
-            // calculate the luminance of the current color and select a font
-            // color based on that
-            // see https://www.w3.org/TR/WCAG20/#relativeluminancedef
-            if (TerminalApp::ColorHelper::IsBrightColor(color))
+
+            std::optional<winrt::Windows::UI::Color> currentColor = tab->GetTabColor();
+            if (currentColor.has_value())
             {
-                fontBrush.Color(winrt::Windows::UI::Colors::Black());
+                tab->_ApplyTabColor(currentColor.value());
             }
             else
             {
-                fontBrush.Color(winrt::Windows::UI::Colors::White());
+                tab->_ClearTabBackgroundColor();
             }
-
-            hoverTabBrush.Color(TerminalApp::ColorHelper::GetAccentColor(color));
-            selectedTabBrush.Color(color);
-
-            // currently if a tab has a custom color, a deselected state is
-            // signified by using the same color with a bit ot transparency
-            auto deselectedTabColor = color;
-            deselectedTabColor.A = 64;
-            deselectedTabBrush.Color(deselectedTabColor);
-
-            // currently if a tab has a custom color, a deselected state is
-            // signified by using the same color with a bit ot transparency
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush);
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush);
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush);
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush);
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush);
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush);
-            tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);
-
-            tab->_RefreshVisualState();
-
-            tab->_tabColor.emplace(color);
-            tab->_colorSelected(color);
         });
     }
 
     // Method Description:
-    // Clear the custom color of the tab, if any
+    // - Applies the given color to the background of this tab's TabViewItem.
+    // - Sets the tab foreground color depending on the luminance of
     // the background color
+    // - This method should only be called on the UI thread.
+    // Arguments:
+    // - color: the color the user picked for their tab
+    // Return Value:
+    // - <none>
+    void Tab::_ApplyTabColor(const winrt::Windows::UI::Color& color)
+    {
+        Media::SolidColorBrush selectedTabBrush{};
+        Media::SolidColorBrush deselectedTabBrush{};
+        Media::SolidColorBrush fontBrush{};
+        Media::SolidColorBrush hoverTabBrush{};
+        // calculate the luminance of the current color and select a font
+        // color based on that
+        // see https://www.w3.org/TR/WCAG20/#relativeluminancedef
+        if (TerminalApp::ColorHelper::IsBrightColor(color))
+        {
+            fontBrush.Color(winrt::Windows::UI::Colors::Black());
+        }
+        else
+        {
+            fontBrush.Color(winrt::Windows::UI::Colors::White());
+        }
+
+        hoverTabBrush.Color(TerminalApp::ColorHelper::GetAccentColor(color));
+        selectedTabBrush.Color(color);
+
+        // currently if a tab has a custom color, a deselected state is
+        // signified by using the same color with a bit ot transparency
+        auto deselectedTabColor = color;
+        deselectedTabColor.A = 64;
+        deselectedTabBrush.Color(deselectedTabColor);
+
+        // currently if a tab has a custom color, a deselected state is
+        // signified by using the same color with a bit ot transparency
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush);
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush);
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush);
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush);
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush);
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush);
+        _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);
+
+        _RefreshVisualState();
+
+        _colorSelected(color);
+    }
+
+    // Method Description:
+    // - Clear the custom runtime color of the tab, if any color is set. This
+    //   will re-apply whatever the tab's base color should be (either the color
+    //   from the control, the theme, or the default tab color.)
     // Arguments:
     // - <none>
     // Return Value:
     // - <none>
-    void Tab::ResetTabColor()
+    void Tab::ResetRuntimeTabColor()
     {
-        auto weakThis{ get_weak() };
+        _runtimeTabColor.reset();
+        _RecalculateAndApplyTabColor();
+    }
 
-        _tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
-            auto ptrTab = weakThis.get();
-            if (!ptrTab)
-                return;
+    // Method Description:
+    // - Clear out any color we've set for the TabViewItem.
+    // - This method should only be called on the UI thread.
+    // Arguments:
+    // - <none>
+    // Return Value:
+    // - <none>
+    void Tab::_ClearTabBackgroundColor()
+    {
+        winrt::hstring keys[] = {
+            L"TabViewItemHeaderBackground",
+            L"TabViewItemHeaderBackgroundSelected",
+            L"TabViewItemHeaderBackgroundPointerOver",
+            L"TabViewItemHeaderForeground",
+            L"TabViewItemHeaderForegroundSelected",
+            L"TabViewItemHeaderForegroundPointerOver",
+            L"TabViewItemHeaderBackgroundPressed",
+            L"TabViewItemHeaderForegroundPressed",
+            L"TabViewButtonForegroundActiveTab"
+        };
 
-            auto tab{ ptrTab };
-            winrt::hstring keys[] = {
-                L"TabViewItemHeaderBackground",
-                L"TabViewItemHeaderBackgroundSelected",
-                L"TabViewItemHeaderBackgroundPointerOver",
-                L"TabViewItemHeaderForeground",
-                L"TabViewItemHeaderForegroundSelected",
-                L"TabViewItemHeaderForegroundPointerOver",
-                L"TabViewItemHeaderBackgroundPressed",
-                L"TabViewItemHeaderForegroundPressed",
-                L"TabViewButtonForegroundActiveTab"
-            };
-
-            // simply clear any of the colors in the tab's dict
-            for (auto keyString : keys)
+        // simply clear any of the colors in the tab's dict
+        for (auto keyString : keys)
+        {
+            auto key = winrt::box_value(keyString);
+            if (_tabViewItem.Resources().HasKey(key))
             {
-                auto key = winrt::box_value(keyString);
-                if (tab->_tabViewItem.Resources().HasKey(key))
-                {
-                    tab->_tabViewItem.Resources().Remove(key);
-                }
+                _tabViewItem.Resources().Remove(key);
             }
+        }
 
-            tab->_RefreshVisualState();
-            tab->_tabColor.reset();
-            tab->_colorCleared();
-        });
+        _RefreshVisualState();
+        _colorCleared();
     }
 
     // Method Description:
diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h
index a6752fe6bbe..3269d9bba6b 100644
--- a/src/cascadia/TerminalApp/Tab.h
+++ b/src/cascadia/TerminalApp/Tab.h
@@ -57,8 +57,8 @@ namespace winrt::TerminalApp::implementation
 
         std::optional<winrt::Windows::UI::Color> GetTabColor();
 
-        void SetTabColor(const winrt::Windows::UI::Color& color);
-        void ResetTabColor();
+        void SetRuntimeTabColor(const winrt::Windows::UI::Color& color);
+        void ResetRuntimeTabColor();
         void ActivateColorPicker();
 
         WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
@@ -75,7 +75,8 @@ namespace winrt::TerminalApp::implementation
         std::shared_ptr<Pane> _activePane{ nullptr };
         winrt::hstring _lastIconPath{};
         winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{};
-        std::optional<winrt::Windows::UI::Color> _tabColor{};
+        std::optional<winrt::Windows::UI::Color> _themeTabColor{};
+        std::optional<winrt::Windows::UI::Color> _runtimeTabColor{};
 
         bool _focused{ false };
         winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr };
@@ -102,6 +103,10 @@ namespace winrt::TerminalApp::implementation
         winrt::fire_and_forget _UpdateTitle();
         void _ConstructTabRenameBox(const winrt::hstring& tabText);
 
+        void _RecalculateAndApplyTabColor();
+        void _ApplyTabColor(const winrt::Windows::UI::Color& color);
+        void _ClearTabBackgroundColor();
+
         friend class ::TerminalAppLocalTests::TabTests;
     };
 }
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index fd613d8a82e..dbeec9885a6 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -1818,16 +1818,16 @@ namespace winrt::TerminalApp::implementation
                 // Raise an event that our title changed
                 _titleChangeHandlers(*this, tab->GetActiveTitle());
 
-                // Raise an event that our titlebar color changed
-                std::optional<Windows::UI::Color> color = tab->GetTabColor();
-                if (color.has_value())
-                {
-                    _SetNonClientAreaColors(color.value());
-                }
-                else
-                {
-                    _ClearNonClientAreaColors();
-                }
+                // // Raise an event that our titlebar color changed
+                // std::optional<Windows::UI::Color> color = tab->GetTabColor();
+                // if (color.has_value())
+                // {
+                //     _SetNonClientAreaColors(color.value());
+                // }
+                // else
+                // {
+                //     _ClearNonClientAreaColors();
+                // }
             }
             CATCH_LOG();
         }
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index 784275fa324..31b2c5874d0 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -2827,6 +2827,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
         _renderer->ResetErrorStateAndResume();
     }
 
+    Windows::Foundation::IReference<winrt::Windows::UI::Color> TermControl::TabColor() noexcept
+    {
+        auto coreColor = _terminal->GetTabColor();
+        return coreColor.has_value() ? Windows::Foundation::IReference<winrt::Windows::UI::Color>(coreColor.value()) : nullptr;
+    }
+
     // -------------------------------- WinRT Events ---------------------------------
     // Winrt events need a method for adding a callback to the event and removing the callback.
     // These macros will define them both for you.
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index 783ca85d967..b1244b8ed2f 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -109,6 +109,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
                                                                const winrt::hstring& padding,
                                                                const uint32_t dpi);
 
+        Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() noexcept;
+
         // clang-format off
         // -------------------------------- WinRT Events ---------------------------------
         DECLARE_EVENT(TitleChanged,             _titleChangedHandlers,              TerminalControl::TitleChangedEventArgs);
diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl
index d5454e50578..5cebfac26e1 100644
--- a/src/cascadia/TerminalControl/TermControl.idl
+++ b/src/cascadia/TerminalControl/TermControl.idl
@@ -70,5 +70,7 @@ namespace Microsoft.Terminal.TerminalControl
         void ResetFontSize();
 
         void ToggleRetroEffect();
+
+        Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
     }
 }
diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp
index e7719507099..9355609999a 100644
--- a/src/cascadia/TerminalCore/Terminal.cpp
+++ b/src/cascadia/TerminalCore/Terminal.cpp
@@ -147,6 +147,15 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
 
     _terminalInput->ForceDisableWin32InputMode(settings.ForceVTInput());
 
+    if (settings.TabColor() == nullptr)
+    {
+        _tabColor = std::nullopt;
+    }
+    else
+    {
+        _tabColor = til::color(settings.TabColor().Value() | 0xff000000);
+    }
+
     // TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we
     // have a smaller scrollback. We should do this carefully - if the new buffer
     // size is smaller than where the mutable viewport currently is, we'll want
@@ -956,3 +965,8 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept
     const auto& cursor = _buffer->GetCursor();
     return cursor.IsBlinkingAllowed();
 }
+
+const std::optional<til::color> Terminal::GetTabColor() const noexcept
+{
+    return _tabColor;
+}
diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp
index ac8d59d3e02..ade99237496 100644
--- a/src/cascadia/TerminalCore/Terminal.hpp
+++ b/src/cascadia/TerminalCore/Terminal.hpp
@@ -176,6 +176,8 @@ class Microsoft::Terminal::Core::Terminal final :
     void SetCursorOn(const bool isOn);
     bool IsCursorBlinkingAllowed() const noexcept;
 
+    const std::optional<til::color> GetTabColor() const noexcept;
+
 #pragma region TextSelection
     // These methods are defined in TerminalSelection.cpp
     enum class SelectionExpansionMode
@@ -205,6 +207,7 @@ class Microsoft::Terminal::Core::Terminal final :
 
     std::optional<std::wstring> _title;
     std::wstring _startingTitle;
+    std::optional<til::color> _tabColor;
 
     std::array<COLORREF, XTERM_COLOR_TABLE_SIZE> _colorTable;
     COLORREF _defaultFg;
diff --git a/src/cascadia/TerminalCore/pch.h b/src/cascadia/TerminalCore/pch.h
index e03399daf91..58fe68df1da 100644
--- a/src/cascadia/TerminalCore/pch.h
+++ b/src/cascadia/TerminalCore/pch.h
@@ -4,3 +4,4 @@
 #pragma once
 
 #include <LibraryIncludes.h>
+#include "winrt/Windows.Foundation.h"
diff --git a/src/cascadia/TerminalSettings/ICoreSettings.idl b/src/cascadia/TerminalSettings/ICoreSettings.idl
index 3bc4834592d..8e3360ae9c8 100644
--- a/src/cascadia/TerminalSettings/ICoreSettings.idl
+++ b/src/cascadia/TerminalSettings/ICoreSettings.idl
@@ -34,6 +34,8 @@ namespace Microsoft.Terminal.Settings
         String WordDelimiters;
 
         Boolean ForceVTInput;
+
+        Windows.Foundation.IReference<UInt32> TabColor;
     };
 
 }
diff --git a/src/cascadia/TerminalSettings/terminalsettings.h b/src/cascadia/TerminalSettings/terminalsettings.h
index 1bd1aad98ae..8e82c0bfeae 100644
--- a/src/cascadia/TerminalSettings/terminalsettings.h
+++ b/src/cascadia/TerminalSettings/terminalsettings.h
@@ -54,6 +54,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
         GETSET_PROPERTY(hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS);
         GETSET_PROPERTY(bool, CopyOnSelect, false);
 
+        GETSET_PROPERTY(Windows::Foundation::IReference<uint32_t>, TabColor, nullptr);
+
         // ------------------------ End of Core Settings -----------------------
 
         GETSET_PROPERTY(hstring, ProfileName);
diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp
index 5e22a25127e..f9bcf52b7fe 100644
--- a/src/types/inc/utils.hpp
+++ b/src/types/inc/utils.hpp
@@ -124,4 +124,28 @@ namespace Microsoft::Console::Utils
         // few more times.
         return t1.value_or(CoalesceOptionals(std::forward<Ts>(t2)...));
     }
+
+    // Method Description:
+    // - Base case provided to handle the last argument to CoalesceOptionals<T...>()
+    template<typename T>
+    std::optional<T> CoalesceOptionalsOrNot(const std::optional<T>& base)
+    {
+        return base;
+    }
+
+    // Method Description:
+    // - Base case provided to handle the last argument to CoalesceOptionals<T...>(..., nullopt)
+    template<typename T>
+    std::optional<T> CoalesceOptionalsOrNot(const std::nullopt_t& base)
+    {
+        return base;
+    }
+
+    // Method Description:
+    // - Returns the value from the first populated optional, or the last one (if none of the previous had a value)
+    template<typename T, typename... Ts>
+    std::optional<T> CoalesceOptionalsOrNot(const std::optional<T>& t1, Ts&&... t2)
+    {
+        return t1.has_value() ? t1 : CoalesceOptionalsOrNot(std::forward<Ts>(t2)...);
+    }
 }

From fb20323d3393352efcf862b7824381160bca1097 Mon Sep 17 00:00:00 2001
From: Mike Griese <migrie@microsoft.com>
Date: Mon, 3 Aug 2020 11:26:17 -0500
Subject: [PATCH 2/7] damn hot-reloading is slick as heck

---
 src/cascadia/TerminalApp/Tab.cpp             | 10 ++++++++++
 src/cascadia/TerminalControl/TermControl.cpp |  7 +++++++
 src/cascadia/TerminalControl/TermControl.h   |  2 ++
 src/cascadia/TerminalControl/TermControl.idl |  1 +
 src/cascadia/TerminalCore/Terminal.cpp       |  6 ++++++
 src/cascadia/TerminalCore/Terminal.hpp       |  2 ++
 6 files changed, 28 insertions(+)

diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp
index bd559f02079..84113809157 100644
--- a/src/cascadia/TerminalApp/Tab.cpp
+++ b/src/cascadia/TerminalApp/Tab.cpp
@@ -439,6 +439,16 @@ namespace winrt::TerminalApp::implementation
                 _rootPane->Relayout();
             }
         });
+
+        control.TabColorChanged([weakThis](auto&&, auto&&) {
+            if (auto tab{ weakThis.get() })
+            {
+                // The control's tabColor changed, but it is not necessarily the
+                // active control in this tab. We'll just recalculate the
+                // current color anyways.
+                tab->_RecalculateAndApplyTabColor();
+            }
+        });
     }
 
     // Method Description:
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index 31b2c5874d0..8c917fe4fb6 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -88,6 +88,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
         auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
         _terminal->SetTitleChangedCallback(pfnTitleChanged);
 
+        auto pfnTabColorChanged = std::bind(&TermControl::_TerminalTabColorChanged, this, std::placeholders::_1);
+        _terminal->SetTabColorChangedCallback(pfnTabColorChanged);
+
         auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
         _terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
 
@@ -2057,6 +2060,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
     {
         _titleChangedHandlers(winrt::hstring{ wstr });
     }
+    void TermControl::_TerminalTabColorChanged(const std::optional<til::color> /*color*/)
+    {
+        _TabColorChangedHandlers(*this, nullptr);
+    }
 
     void TermControl::_CopyToClipboard(const std::wstring_view& wstr)
     {
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index b1244b8ed2f..7cc8b0c26aa 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -122,6 +122,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
 
         TYPED_EVENT(ConnectionStateChanged, TerminalControl::TermControl, IInspectable);
         TYPED_EVENT(Initialized, TerminalControl::TermControl, Windows::UI::Xaml::RoutedEventArgs);
+        TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
         // clang-format on
 
     private:
@@ -223,6 +224,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
         void _DoResizeUnderLock(const double newWidth, const double newHeight);
         void _RefreshSizeUnderLock();
         void _TerminalTitleChanged(const std::wstring_view& wstr);
+        void _TerminalTabColorChanged(const std::optional<til::color> color);
         void _CopyToClipboard(const std::wstring_view& wstr);
         void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
         void _TerminalCursorPositionChanged();
diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl
index 5cebfac26e1..dd301b119ef 100644
--- a/src/cascadia/TerminalControl/TermControl.idl
+++ b/src/cascadia/TerminalControl/TermControl.idl
@@ -72,5 +72,6 @@ namespace Microsoft.Terminal.TerminalControl
         void ToggleRetroEffect();
 
         Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
+        event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
     }
 }
diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp
index 9355609999a..59de8004755 100644
--- a/src/cascadia/TerminalCore/Terminal.cpp
+++ b/src/cascadia/TerminalCore/Terminal.cpp
@@ -155,6 +155,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
     {
         _tabColor = til::color(settings.TabColor().Value() | 0xff000000);
     }
+    _pfnTabColorChanged(_tabColor);
 
     // TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we
     // have a smaller scrollback. We should do this carefully - if the new buffer
@@ -909,6 +910,11 @@ void Terminal::SetTitleChangedCallback(std::function<void(const std::wstring_vie
     _pfnTitleChanged.swap(pfn);
 }
 
+void Terminal::SetTabColorChangedCallback(std::function<void(const std::optional<til::color>)> pfn) noexcept
+{
+    _pfnTabColorChanged.swap(pfn);
+}
+
 void Terminal::SetCopyToClipboardCallback(std::function<void(const std::wstring_view&)> pfn) noexcept
 {
     _pfnCopyToClipboard.swap(pfn);
diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp
index ade99237496..7d93c1b2277 100644
--- a/src/cascadia/TerminalCore/Terminal.hpp
+++ b/src/cascadia/TerminalCore/Terminal.hpp
@@ -168,6 +168,7 @@ class Microsoft::Terminal::Core::Terminal final :
 
     void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
     void SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
+    void SetTabColorChangedCallback(std::function<void(const std::optional<til::color>)> pfn) noexcept;
     void SetCopyToClipboardCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
     void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
     void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
@@ -201,6 +202,7 @@ class Microsoft::Terminal::Core::Terminal final :
     std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
     std::function<void(const COLORREF)> _pfnBackgroundColorChanged;
     std::function<void()> _pfnCursorPositionChanged;
+    std::function<void(const std::optional<til::color>)> _pfnTabColorChanged;
 
     std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
     std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput;

From 694938702233ce1b40f0930d52d35783cd5d315a Mon Sep 17 00:00:00 2001
From: Mike Griese <migrie@microsoft.com>
Date: Mon, 3 Aug 2020 11:44:48 -0500
Subject: [PATCH 3/7] nits from things I forgot before submitting the PR

---
 src/cascadia/TerminalApp/Profile.cpp      |  5 ++---
 src/cascadia/TerminalApp/Tab.cpp          | 15 +++++++++++++++
 src/cascadia/TerminalApp/TerminalPage.cpp | 11 -----------
 3 files changed, 17 insertions(+), 14 deletions(-)

diff --git a/src/cascadia/TerminalApp/Profile.cpp b/src/cascadia/TerminalApp/Profile.cpp
index 7d85745bb19..fd439739449 100644
--- a/src/cascadia/TerminalApp/Profile.cpp
+++ b/src/cascadia/TerminalApp/Profile.cpp
@@ -234,9 +234,8 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
 
     if (_tabColor)
     {
-        uint32_t c = _tabColor.value();
-        winrt::Windows::Foundation::IReference<uint32_t> cr{ c };
-        terminalSettings.TabColor(cr);
+        winrt::Windows::Foundation::IReference<uint32_t> colorRef{ _tabColor.value() };
+        terminalSettings.TabColor(colorRef);
     }
 
     return terminalSettings;
diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp
index 84113809157..efc7684ff2e 100644
--- a/src/cascadia/TerminalApp/Tab.cpp
+++ b/src/cascadia/TerminalApp/Tab.cpp
@@ -727,6 +727,21 @@ namespace winrt::TerminalApp::implementation
         {
             controlTabColor = currControlColor.Value();
         }
+
+        // A Tab's color will be the result of layering a variety of sources,
+        // from the bottom up:
+        //
+        // Color                |             | Set by
+        // -------------------- | --          | --
+        // Runtime Color        | _optional_  | Color Picker / `setTabColor` action
+        // Control Tab Color    | _optional_  | Profile's `tabColor`, or a color set by VT
+        // Theme Tab Background | _optional_  | `tab.backgroundColor` in the theme
+        // Tab Default Color    | **default** | TabView in XAML
+        //
+        // CoalesceOptionalsOrNot will get us the first of these values that's
+        // actually set, with nullopt being our sentinel for "use the default
+        // tabview color" (and clear out any colors we've set).
+
         return Utils::CoalesceOptionalsOrNot(_runtimeTabColor,
                                              controlTabColor,
                                              _themeTabColor,
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index dbeec9885a6..dcc551f1fed 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -1817,17 +1817,6 @@ namespace winrt::TerminalApp::implementation
 
                 // Raise an event that our title changed
                 _titleChangeHandlers(*this, tab->GetActiveTitle());
-
-                // // Raise an event that our titlebar color changed
-                // std::optional<Windows::UI::Color> color = tab->GetTabColor();
-                // if (color.has_value())
-                // {
-                //     _SetNonClientAreaColors(color.value());
-                // }
-                // else
-                // {
-                //     _ClearNonClientAreaColors();
-                // }
             }
             CATCH_LOG();
         }

From daf7e63bfdf1fc0773ce20f8533641f72d7c5ee9 Mon Sep 17 00:00:00 2001
From: Mike Griese <migrie@microsoft.com>
Date: Mon, 3 Aug 2020 16:05:25 -0500
Subject: [PATCH 4/7] fix the compilation of the unittests

---
 src/cascadia/UnitTests_TerminalCore/MockTermSettings.h | 3 +++
 src/cascadia/UnitTests_TerminalCore/precomp.h          | 1 +
 2 files changed, 4 insertions(+)

diff --git a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h
index 9d91488075c..1082d25dfe9 100644
--- a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h
+++ b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h
@@ -6,6 +6,7 @@
 #include "DefaultSettings.h"
 
 #include "winrt/Microsoft.Terminal.Settings.h"
+#include "../inc/cppwinrt_utils.h"
 
 using namespace winrt::Microsoft::Terminal::Settings;
 
@@ -63,6 +64,8 @@ namespace TerminalCoreUnitTests
         // other unimplemented methods
         void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) {}
 
+        GETSET_PROPERTY(winrt::Windows::Foundation::IReference<uint32_t>, TabColor, nullptr);
+
     private:
         int32_t _historySize;
         int32_t _initialRows;
diff --git a/src/cascadia/UnitTests_TerminalCore/precomp.h b/src/cascadia/UnitTests_TerminalCore/precomp.h
index 53dbb752420..7e6feac953f 100644
--- a/src/cascadia/UnitTests_TerminalCore/precomp.h
+++ b/src/cascadia/UnitTests_TerminalCore/precomp.h
@@ -45,3 +45,4 @@ Author(s):
 #define CON_USERPRIVAPI_INDIRECT
 #define CON_DPIAPI_INDIRECT
 #endif
+#include "winrt/Windows.Foundation.h"

From 969c807e93bbb32e11175ca3c229ca6537e37d20 Mon Sep 17 00:00:00 2001
From: Mike Griese <migrie@microsoft.com>
Date: Tue, 4 Aug 2020 12:23:09 -0500
Subject: [PATCH 5/7] all that just for a _test to pass_?

---
 src/cascadia/TerminalCore/Terminal.cpp        |  5 +-
 src/cascadia/UnitTests_TerminalCore/precomp.h | 54 +++++++++++--------
 2 files changed, 35 insertions(+), 24 deletions(-)

diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp
index d92dfb8a911..17bb24876e9 100644
--- a/src/cascadia/TerminalCore/Terminal.cpp
+++ b/src/cascadia/TerminalCore/Terminal.cpp
@@ -155,7 +155,10 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
     {
         _tabColor = til::color(settings.TabColor().Value() | 0xff000000);
     }
-    _pfnTabColorChanged(_tabColor);
+    if (_pfnTabColorChanged)
+    {
+        _pfnTabColorChanged(_tabColor);
+    }
 
     // TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we
     // have a smaller scrollback. We should do this carefully - if the new buffer
diff --git a/src/cascadia/UnitTests_TerminalCore/precomp.h b/src/cascadia/UnitTests_TerminalCore/precomp.h
index 7e6feac953f..2a55dfc7ffc 100644
--- a/src/cascadia/UnitTests_TerminalCore/precomp.h
+++ b/src/cascadia/UnitTests_TerminalCore/precomp.h
@@ -17,32 +17,40 @@ Author(s):
 
 #pragma once
 
-// <Conhost includes>
-// This header and define are needed so that the console host code can build in
-// this test binary.
-
-// Block minwindef.h min/max macros to prevent <algorithm> conflict
-#define NOMINMAX
-
-// This includes a lot of common headers needed by both the host and the propsheet
-// including: windows.h, winuser, ntstatus, assert, and the DDK
-#include "HostAndPropsheetIncludes.h"
-// </Conhost Includes>
-
+#define BLOCK_TIL
 // This includes support libraries from the CRT, STL, WIL, and GSL
 #include "LibraryIncludes.h"
-
-#ifdef BUILDING_INSIDE_WINIDE
-#define DbgRaiseAssertionFailure() __int2c()
+// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
+//      SDK definition of this function, so the only fix is to undef it.
+// from WinBase.h
+// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime
+#ifdef GetCurrentTime
+#undef GetCurrentTime
 #endif
 
-#include <ShellScalingApi.h>
+#include <wil/cppwinrt.h>
+#include <unknwn.h>
+#include <hstring.h>
 
-// Comment to build against the private SDK.
-#define CON_BUILD_PUBLIC
+#include <WexTestClass.h>
+#include "consoletaeftemplates.hpp"
 
-#ifdef CON_BUILD_PUBLIC
-#define CON_USERPRIVAPI_INDIRECT
-#define CON_DPIAPI_INDIRECT
-#endif
-#include "winrt/Windows.Foundation.h"
+#include <winrt/Windows.system.h>
+#include <winrt/Windows.Foundation.h>
+#include <winrt/Windows.Foundation.Collections.h>
+
+// Manually include til after we include Windows.Foundation to give it winrt superpowers
+#include "til.h"
+
+// <Conhost includes>
+// These are needed because the roundtrip tests included in this library also
+// re-use some conhost code that depends on these.
+
+#include "conddkrefs.h"
+// From ntdef.h, but that can't be included or it'll fight over PROBE_ALIGNMENT and other such arch specific defs
+typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
+/*lint -save -e624 */ // Don't complain about different typedefs.
+typedef NTSTATUS* PNTSTATUS;
+/*lint -restore */ // Resume checking for different typedefs.
+#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
+// </Conhost Includes>

From 0b8433320b4a8f6a7e1957c5b108767f25ee9131 Mon Sep 17 00:00:00 2001
From: Mike Griese <migrie@microsoft.com>
Date: Tue, 4 Aug 2020 15:04:39 -0500
Subject: [PATCH 6/7] Eh let's just move this to it's own file now, that's
 easier

---
 src/cascadia/TerminalApp/CascadiaSettings.cpp |  4 +-
 src/cascadia/TerminalApp/Tab.cpp              | 10 +--
 src/inc/til.h                                 |  1 +
 src/inc/til/coalesce.h                        | 61 ++++++++++++++
 src/til/ut_til/CoalesceTests.cpp              | 84 +++++++++++++++++++
 src/til/ut_til/til.unit.tests.vcxproj         |  3 +-
 src/types/inc/utils.hpp                       | 52 ------------
 7 files changed, 154 insertions(+), 61 deletions(-)
 create mode 100644 src/inc/til/coalesce.h
 create mode 100644 src/til/ut_til/CoalesceTests.cpp

diff --git a/src/cascadia/TerminalApp/CascadiaSettings.cpp b/src/cascadia/TerminalApp/CascadiaSettings.cpp
index 96e947e952d..8ebe69428ff 100644
--- a/src/cascadia/TerminalApp/CascadiaSettings.cpp
+++ b/src/cascadia/TerminalApp/CascadiaSettings.cpp
@@ -227,7 +227,7 @@ void CascadiaSettings::_ResolveDefaultProfile()
 {
     const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() };
     auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) };
-    auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
+    auto defaultProfileGuid{ til::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
     GlobalSettings().DefaultProfile(defaultProfileGuid);
 }
 
@@ -566,7 +566,7 @@ GUID CascadiaSettings::_GetProfileForArgs(const NewTerminalArgs& newTerminalArgs
         profileByName = _GetProfileGuidByName(newTerminalArgs.Profile());
     }
 
-    return Utils::CoalesceOptionals(profileByName, profileByIndex, _globals.DefaultProfile());
+    return til::CoalesceOptionals(profileByName, profileByIndex, _globals.DefaultProfile());
 }
 
 // Method Description:
diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp
index efc7684ff2e..89b83156598 100644
--- a/src/cascadia/TerminalApp/Tab.cpp
+++ b/src/cascadia/TerminalApp/Tab.cpp
@@ -8,7 +8,6 @@
 #include "Tab.g.cpp"
 #include "Utils.h"
 #include "ColorHelper.h"
-#include "../../types/inc/utils.hpp"
 
 using namespace winrt;
 using namespace winrt::Windows::UI::Xaml;
@@ -16,7 +15,6 @@ using namespace winrt::Windows::UI::Core;
 using namespace winrt::Microsoft::Terminal::Settings;
 using namespace winrt::Microsoft::Terminal::TerminalControl;
 using namespace winrt::Windows::System;
-using namespace ::Microsoft::Console;
 
 namespace winrt
 {
@@ -742,10 +740,10 @@ namespace winrt::TerminalApp::implementation
         // actually set, with nullopt being our sentinel for "use the default
         // tabview color" (and clear out any colors we've set).
 
-        return Utils::CoalesceOptionalsOrNot(_runtimeTabColor,
-                                             controlTabColor,
-                                             _themeTabColor,
-                                             std::optional<Windows::UI::Color>(std::nullopt));
+        return til::CoalesceOptionalsOrNot(_runtimeTabColor,
+                                           controlTabColor,
+                                           _themeTabColor,
+                                           std::optional<Windows::UI::Color>(std::nullopt));
     }
 
     // Method Description:
diff --git a/src/inc/til.h b/src/inc/til.h
index e9504535708..36e10ad0e6f 100644
--- a/src/inc/til.h
+++ b/src/inc/til.h
@@ -16,6 +16,7 @@
 #include "til/bitmap.h"
 #include "til/u8u16convert.h"
 #include "til/spsc.h"
+#include "til/coalesce.h"
 
 namespace til // Terminal Implementation Library. Also: "Today I Learned"
 {
diff --git a/src/inc/til/coalesce.h b/src/inc/til/coalesce.h
new file mode 100644
index 00000000000..46ce7090315
--- /dev/null
+++ b/src/inc/til/coalesce.h
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+namespace til
+{
+    // Method Description:
+    // - Base case provided to handle the last argument to CoalesceOptionals<T...>()
+    template<typename T>
+    T CoalesceOptionals(const T& base)
+    {
+        return base;
+    }
+
+    // Method Description:
+    // - Base case provided to throw an assertion if you call CoalesceOptionals(opt, opt, opt)
+    template<typename T>
+    T CoalesceOptionals(const std::optional<T>& base)
+    {
+        static_assert(false, "CoalesceOptionals must be passed a base non-optional value to be used if all optionals are empty");
+        return T{};
+    }
+
+    // Method Description:
+    // - Returns the value from the first populated optional, or a base value if none were populated.
+    template<typename T, typename... Ts>
+    T CoalesceOptionals(const std::optional<T>& t1, Ts&&... t2)
+    {
+        // Initially, I wanted to check "has_value" and short-circuit out so that we didn't
+        // evaluate value_or for every single optional, but has_value/value emits exception handling
+        // code that value_or doesn't. Less exception handling is cheaper than calling value_or a
+        // few more times.
+        return t1.value_or(CoalesceOptionals(std::forward<Ts>(t2)...));
+    }
+
+    // Method Description:
+    // - Base case provided to handle the last argument to CoalesceOptionals<T...>()
+    template<typename T>
+    std::optional<T> CoalesceOptionalsOrNot(const std::optional<T>& base)
+    {
+        return base;
+    }
+
+    // Method Description:
+    // - Base case provided to handle the last argument to CoalesceOptionals<T...>(..., nullopt)
+    template<typename T>
+    std::optional<T> CoalesceOptionalsOrNot(const std::nullopt_t& base)
+    {
+        return base;
+    }
+
+    // Method Description:
+    // - Returns the value from the first populated optional, or the last one (if none of the previous had a value)
+    template<typename T, typename... Ts>
+    std::optional<T> CoalesceOptionalsOrNot(const std::optional<T>& t1, Ts&&... t2)
+    {
+        return t1.has_value() ? t1 : CoalesceOptionalsOrNot(std::forward<Ts>(t2)...);
+    }
+
+}
diff --git a/src/til/ut_til/CoalesceTests.cpp b/src/til/ut_til/CoalesceTests.cpp
new file mode 100644
index 00000000000..2b74e663cd0
--- /dev/null
+++ b/src/til/ut_til/CoalesceTests.cpp
@@ -0,0 +1,84 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "precomp.h"
+#include "WexTestClass.h"
+
+using namespace WEX::Common;
+using namespace WEX::Logging;
+using namespace WEX::TestExecution;
+
+class CoalesceTests
+{
+    TEST_CLASS(CoalesceTests);
+
+    TEST_METHOD(CoalesceFirstValue);
+    TEST_METHOD(CoalesceMiddleValue);
+    TEST_METHOD(CoalesceDefaultValue);
+
+    TEST_METHOD(CoalesceOrNotFirstValue);
+    TEST_METHOD(CoalesceOrNotMiddleValue);
+    TEST_METHOD(CoalesceOrNotDefaultValue);
+    TEST_METHOD(CoalesceOrNotDefaultIsNullopt);
+};
+
+void CoalesceTests::CoalesceFirstValue()
+{
+    int result = til::CoalesceOptionals(std::optional<int>(1),
+                                        std::optional<int>(2),
+                                        std::optional<int>(3),
+                                        4);
+    VERIFY_ARE_EQUAL(1, result);
+}
+void CoalesceTests::CoalesceMiddleValue()
+{
+    int result = til::CoalesceOptionals(std::optional<int>(std::nullopt),
+                                        std::optional<int>(2),
+                                        std::optional<int>(3),
+                                        4);
+    VERIFY_ARE_EQUAL(2, result);
+}
+void CoalesceTests::CoalesceDefaultValue()
+{
+    int result = til::CoalesceOptionals(std::optional<int>(std::nullopt),
+                                        std::optional<int>(std::nullopt),
+                                        std::optional<int>(std::nullopt),
+                                        4);
+    VERIFY_ARE_EQUAL(4, result);
+}
+
+void CoalesceTests::CoalesceOrNotFirstValue()
+{
+    std::optional<int> result = til::CoalesceOptionalsOrNot(std::optional<int>(1),
+                                                            std::optional<int>(2),
+                                                            std::optional<int>(3),
+                                                            std::optional<int>(4));
+    VERIFY_IS_TRUE(result.has_value());
+    VERIFY_ARE_EQUAL(1, result.value());
+}
+void CoalesceTests::CoalesceOrNotMiddleValue()
+{
+    std::optional<int> result = til::CoalesceOptionalsOrNot(std::optional<int>(std::nullopt),
+                                                            std::optional<int>(2),
+                                                            std::optional<int>(3),
+                                                            std::optional<int>(4));
+    VERIFY_IS_TRUE(result.has_value());
+    VERIFY_ARE_EQUAL(2, result.value());
+}
+void CoalesceTests::CoalesceOrNotDefaultValue()
+{
+    std::optional<int> result = til::CoalesceOptionalsOrNot(std::optional<int>(std::nullopt),
+                                                            std::optional<int>(std::nullopt),
+                                                            std::optional<int>(std::nullopt),
+                                                            std::optional<int>(4));
+    VERIFY_IS_TRUE(result.has_value());
+    VERIFY_ARE_EQUAL(4, result.value());
+}
+void CoalesceTests::CoalesceOrNotDefaultIsNullopt()
+{
+    std::optional<int> result = til::CoalesceOptionalsOrNot(std::optional<int>(std::nullopt),
+                                                            std::optional<int>(std::nullopt),
+                                                            std::optional<int>(std::nullopt),
+                                                            std::optional<int>(std::nullopt));
+    VERIFY_IS_FALSE(result.has_value());
+}
diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj
index 82b7908bb7c..52a07246b81 100644
--- a/src/til/ut_til/til.unit.tests.vcxproj
+++ b/src/til/ut_til/til.unit.tests.vcxproj
@@ -18,6 +18,7 @@
     <ClCompile Include="RectangleTests.cpp" />
     <ClCompile Include="SizeTests.cpp" />
     <ClCompile Include="ColorTests.cpp" />
+    <ClCompile Include="CoalesceTests.cpp" />
     <ClCompile Include="SomeTests.cpp" />
     <ClCompile Include="..\precomp.cpp">
       <PrecompiledHeader>Create</PrecompiledHeader>
@@ -36,4 +37,4 @@
   <!-- 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.build.tests.props" />
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp
index f9bcf52b7fe..05ba610e9d5 100644
--- a/src/types/inc/utils.hpp
+++ b/src/types/inc/utils.hpp
@@ -96,56 +96,4 @@ namespace Microsoft::Console::Utils
 
     GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span<const gsl::byte> name);
 
-    // Method Description:
-    // - Base case provided to handle the last argument to CoalesceOptionals<T...>()
-    template<typename T>
-    T CoalesceOptionals(const T& base)
-    {
-        return base;
-    }
-
-    // Method Description:
-    // - Base case provided to throw an assertion if you call CoalesceOptionals(opt, opt, opt)
-    template<typename T>
-    T CoalesceOptionals(const std::optional<T>& base)
-    {
-        static_assert(false, "CoalesceOptionals must be passed a base non-optional value to be used if all optionals are empty");
-        return T{};
-    }
-
-    // Method Description:
-    // - Returns the value from the first populated optional, or a base value if none were populated.
-    template<typename T, typename... Ts>
-    T CoalesceOptionals(const std::optional<T>& t1, Ts&&... t2)
-    {
-        // Initially, I wanted to check "has_value" and short-circuit out so that we didn't
-        // evaluate value_or for every single optional, but has_value/value emits exception handling
-        // code that value_or doesn't. Less exception handling is cheaper than calling value_or a
-        // few more times.
-        return t1.value_or(CoalesceOptionals(std::forward<Ts>(t2)...));
-    }
-
-    // Method Description:
-    // - Base case provided to handle the last argument to CoalesceOptionals<T...>()
-    template<typename T>
-    std::optional<T> CoalesceOptionalsOrNot(const std::optional<T>& base)
-    {
-        return base;
-    }
-
-    // Method Description:
-    // - Base case provided to handle the last argument to CoalesceOptionals<T...>(..., nullopt)
-    template<typename T>
-    std::optional<T> CoalesceOptionalsOrNot(const std::nullopt_t& base)
-    {
-        return base;
-    }
-
-    // Method Description:
-    // - Returns the value from the first populated optional, or the last one (if none of the previous had a value)
-    template<typename T, typename... Ts>
-    std::optional<T> CoalesceOptionalsOrNot(const std::optional<T>& t1, Ts&&... t2)
-    {
-        return t1.has_value() ? t1 : CoalesceOptionalsOrNot(std::forward<Ts>(t2)...);
-    }
 }

From 0edc1d88fe34cb1a15d0f0217b2368c150fbafd9 Mon Sep 17 00:00:00 2001
From: Mike Griese <migrie@microsoft.com>
Date: Thu, 6 Aug 2020 16:45:49 -0500
Subject: [PATCH 7/7] Rename this to be _simpler_

---
 src/cascadia/TerminalApp/CascadiaSettings.cpp |  4 +-
 src/cascadia/TerminalApp/Tab.cpp              | 10 ++--
 src/inc/til/coalesce.h                        | 26 ++++-----
 src/til/ut_til/CoalesceTests.cpp              | 56 +++++++++----------
 4 files changed, 48 insertions(+), 48 deletions(-)

diff --git a/src/cascadia/TerminalApp/CascadiaSettings.cpp b/src/cascadia/TerminalApp/CascadiaSettings.cpp
index 8ebe69428ff..32c19867c41 100644
--- a/src/cascadia/TerminalApp/CascadiaSettings.cpp
+++ b/src/cascadia/TerminalApp/CascadiaSettings.cpp
@@ -227,7 +227,7 @@ void CascadiaSettings::_ResolveDefaultProfile()
 {
     const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() };
     auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) };
-    auto defaultProfileGuid{ til::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
+    auto defaultProfileGuid{ til::coalesce_value(maybeParsedDefaultProfile, GUID{}) };
     GlobalSettings().DefaultProfile(defaultProfileGuid);
 }
 
@@ -566,7 +566,7 @@ GUID CascadiaSettings::_GetProfileForArgs(const NewTerminalArgs& newTerminalArgs
         profileByName = _GetProfileGuidByName(newTerminalArgs.Profile());
     }
 
-    return til::CoalesceOptionals(profileByName, profileByIndex, _globals.DefaultProfile());
+    return til::coalesce_value(profileByName, profileByIndex, _globals.DefaultProfile());
 }
 
 // Method Description:
diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp
index 89b83156598..0764acedccd 100644
--- a/src/cascadia/TerminalApp/Tab.cpp
+++ b/src/cascadia/TerminalApp/Tab.cpp
@@ -736,14 +736,14 @@ namespace winrt::TerminalApp::implementation
         // Theme Tab Background | _optional_  | `tab.backgroundColor` in the theme
         // Tab Default Color    | **default** | TabView in XAML
         //
-        // CoalesceOptionalsOrNot will get us the first of these values that's
+        // coalesce will get us the first of these values that's
         // actually set, with nullopt being our sentinel for "use the default
         // tabview color" (and clear out any colors we've set).
 
-        return til::CoalesceOptionalsOrNot(_runtimeTabColor,
-                                           controlTabColor,
-                                           _themeTabColor,
-                                           std::optional<Windows::UI::Color>(std::nullopt));
+        return til::coalesce(_runtimeTabColor,
+                             controlTabColor,
+                             _themeTabColor,
+                             std::optional<Windows::UI::Color>(std::nullopt));
     }
 
     // Method Description:
diff --git a/src/inc/til/coalesce.h b/src/inc/til/coalesce.h
index 46ce7090315..24153869a67 100644
--- a/src/inc/til/coalesce.h
+++ b/src/inc/til/coalesce.h
@@ -6,46 +6,46 @@
 namespace til
 {
     // Method Description:
-    // - Base case provided to handle the last argument to CoalesceOptionals<T...>()
+    // - Base case provided to handle the last argument to coalesce_value<T...>()
     template<typename T>
-    T CoalesceOptionals(const T& base)
+    T coalesce_value(const T& base)
     {
         return base;
     }
 
     // Method Description:
-    // - Base case provided to throw an assertion if you call CoalesceOptionals(opt, opt, opt)
+    // - Base case provided to throw an assertion if you call coalesce_value(opt, opt, opt)
     template<typename T>
-    T CoalesceOptionals(const std::optional<T>& base)
+    T coalesce_value(const std::optional<T>& base)
     {
-        static_assert(false, "CoalesceOptionals must be passed a base non-optional value to be used if all optionals are empty");
+        static_assert(false, "coalesce_value must be passed a base non-optional value to be used if all optionals are empty");
         return T{};
     }
 
     // Method Description:
     // - Returns the value from the first populated optional, or a base value if none were populated.
     template<typename T, typename... Ts>
-    T CoalesceOptionals(const std::optional<T>& t1, Ts&&... t2)
+    T coalesce_value(const std::optional<T>& t1, Ts&&... t2)
     {
         // Initially, I wanted to check "has_value" and short-circuit out so that we didn't
         // evaluate value_or for every single optional, but has_value/value emits exception handling
         // code that value_or doesn't. Less exception handling is cheaper than calling value_or a
         // few more times.
-        return t1.value_or(CoalesceOptionals(std::forward<Ts>(t2)...));
+        return t1.value_or(coalesce_value(std::forward<Ts>(t2)...));
     }
 
     // Method Description:
-    // - Base case provided to handle the last argument to CoalesceOptionals<T...>()
+    // - Base case provided to handle the last argument to coalesce_value<T...>()
     template<typename T>
-    std::optional<T> CoalesceOptionalsOrNot(const std::optional<T>& base)
+    std::optional<T> coalesce(const std::optional<T>& base)
     {
         return base;
     }
 
     // Method Description:
-    // - Base case provided to handle the last argument to CoalesceOptionals<T...>(..., nullopt)
+    // - Base case provided to handle the last argument to coalesce_value<T...>(..., nullopt)
     template<typename T>
-    std::optional<T> CoalesceOptionalsOrNot(const std::nullopt_t& base)
+    std::optional<T> coalesce(const std::nullopt_t& base)
     {
         return base;
     }
@@ -53,9 +53,9 @@ namespace til
     // Method Description:
     // - Returns the value from the first populated optional, or the last one (if none of the previous had a value)
     template<typename T, typename... Ts>
-    std::optional<T> CoalesceOptionalsOrNot(const std::optional<T>& t1, Ts&&... t2)
+    std::optional<T> coalesce(const std::optional<T>& t1, Ts&&... t2)
     {
-        return t1.has_value() ? t1 : CoalesceOptionalsOrNot(std::forward<Ts>(t2)...);
+        return t1.has_value() ? t1 : coalesce(std::forward<Ts>(t2)...);
     }
 
 }
diff --git a/src/til/ut_til/CoalesceTests.cpp b/src/til/ut_til/CoalesceTests.cpp
index 2b74e663cd0..76c2e9c31ab 100644
--- a/src/til/ut_til/CoalesceTests.cpp
+++ b/src/til/ut_til/CoalesceTests.cpp
@@ -24,61 +24,61 @@ class CoalesceTests
 
 void CoalesceTests::CoalesceFirstValue()
 {
-    int result = til::CoalesceOptionals(std::optional<int>(1),
-                                        std::optional<int>(2),
-                                        std::optional<int>(3),
-                                        4);
+    int result = til::coalesce_value(std::optional<int>(1),
+                                     std::optional<int>(2),
+                                     std::optional<int>(3),
+                                     4);
     VERIFY_ARE_EQUAL(1, result);
 }
 void CoalesceTests::CoalesceMiddleValue()
 {
-    int result = til::CoalesceOptionals(std::optional<int>(std::nullopt),
-                                        std::optional<int>(2),
-                                        std::optional<int>(3),
-                                        4);
+    int result = til::coalesce_value(std::optional<int>(std::nullopt),
+                                     std::optional<int>(2),
+                                     std::optional<int>(3),
+                                     4);
     VERIFY_ARE_EQUAL(2, result);
 }
 void CoalesceTests::CoalesceDefaultValue()
 {
-    int result = til::CoalesceOptionals(std::optional<int>(std::nullopt),
-                                        std::optional<int>(std::nullopt),
-                                        std::optional<int>(std::nullopt),
-                                        4);
+    int result = til::coalesce_value(std::optional<int>(std::nullopt),
+                                     std::optional<int>(std::nullopt),
+                                     std::optional<int>(std::nullopt),
+                                     4);
     VERIFY_ARE_EQUAL(4, result);
 }
 
 void CoalesceTests::CoalesceOrNotFirstValue()
 {
-    std::optional<int> result = til::CoalesceOptionalsOrNot(std::optional<int>(1),
-                                                            std::optional<int>(2),
-                                                            std::optional<int>(3),
-                                                            std::optional<int>(4));
+    std::optional<int> result = til::coalesce(std::optional<int>(1),
+                                              std::optional<int>(2),
+                                              std::optional<int>(3),
+                                              std::optional<int>(4));
     VERIFY_IS_TRUE(result.has_value());
     VERIFY_ARE_EQUAL(1, result.value());
 }
 void CoalesceTests::CoalesceOrNotMiddleValue()
 {
-    std::optional<int> result = til::CoalesceOptionalsOrNot(std::optional<int>(std::nullopt),
-                                                            std::optional<int>(2),
-                                                            std::optional<int>(3),
-                                                            std::optional<int>(4));
+    std::optional<int> result = til::coalesce(std::optional<int>(std::nullopt),
+                                              std::optional<int>(2),
+                                              std::optional<int>(3),
+                                              std::optional<int>(4));
     VERIFY_IS_TRUE(result.has_value());
     VERIFY_ARE_EQUAL(2, result.value());
 }
 void CoalesceTests::CoalesceOrNotDefaultValue()
 {
-    std::optional<int> result = til::CoalesceOptionalsOrNot(std::optional<int>(std::nullopt),
-                                                            std::optional<int>(std::nullopt),
-                                                            std::optional<int>(std::nullopt),
-                                                            std::optional<int>(4));
+    std::optional<int> result = til::coalesce(std::optional<int>(std::nullopt),
+                                              std::optional<int>(std::nullopt),
+                                              std::optional<int>(std::nullopt),
+                                              std::optional<int>(4));
     VERIFY_IS_TRUE(result.has_value());
     VERIFY_ARE_EQUAL(4, result.value());
 }
 void CoalesceTests::CoalesceOrNotDefaultIsNullopt()
 {
-    std::optional<int> result = til::CoalesceOptionalsOrNot(std::optional<int>(std::nullopt),
-                                                            std::optional<int>(std::nullopt),
-                                                            std::optional<int>(std::nullopt),
-                                                            std::optional<int>(std::nullopt));
+    std::optional<int> result = til::coalesce(std::optional<int>(std::nullopt),
+                                              std::optional<int>(std::nullopt),
+                                              std::optional<int>(std::nullopt),
+                                              std::optional<int>(std::nullopt));
     VERIFY_IS_FALSE(result.has_value());
 }