Add support for per-profile tab colors (#7162)

This PR adds support for per-profile tab colors, in accordance with
#7134. This adds a single `tabColor` property, that when set, specifies
the background color for profile's tab. This color can be overridden by
the color picker, and clearing the color with the color picker will
revert to this default color set for the tab.

* Full theming is covered in #3327 & #5772 

Validation: Played with setting this color, both on launch and via
hot-reload

Specified in #7134
Closes #1337
This commit is contained in:
Mike Griese 2020-08-07 18:07:42 -05:00 committed by GitHub
parent 60b44c856e
commit 4e0f31337d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 415 additions and 153 deletions

View file

@ -289,11 +289,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);

View file

@ -226,7 +226,7 @@ void CascadiaSettings::_ResolveDefaultProfile()
{
const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() };
auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) };
auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
auto defaultProfileGuid{ til::coalesce_value(maybeParsedDefaultProfile, GUID{}) };
GlobalSettings().DefaultProfile(defaultProfileGuid);
}
@ -565,7 +565,7 @@ GUID CascadiaSettings::_GetProfileForArgs(const NewTerminalArgs& newTerminalArgs
profileByName = _GetProfileGuidByName(newTerminalArgs.Profile());
}
return Utils::CoalesceOptionals(profileByName, profileByIndex, _globals.DefaultProfile());
return til::coalesce_value(profileByName, profileByIndex, _globals.DefaultProfile());
}
// Method Description:

View file

@ -53,6 +53,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)
@ -232,6 +233,12 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
terminalSettings.AntialiasingMode(_antialiasingMode);
if (_tabColor)
{
winrt::Windows::Foundation::IReference<uint32_t> colorRef{ _tabColor.value() };
terminalSettings.TabColor(colorRef);
}
return terminalSettings;
}
@ -405,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

View file

@ -117,6 +117,7 @@ private:
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;

View file

@ -54,6 +54,7 @@ namespace winrt::TerminalApp::implementation
});
_UpdateTitle();
_RecalculateAndApplyTabColor();
}
// Method Description:
@ -435,6 +436,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:
@ -479,6 +490,7 @@ namespace winrt::TerminalApp::implementation
if (tab && sender != tab->_activePane)
{
tab->_UpdateActivePane(sender);
tab->_RecalculateAndApplyTabColor();
}
});
}
@ -529,14 +541,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();
}
});
@ -706,79 +718,57 @@ 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();
}
// 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
//
// 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::coalesce(_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)
{
auto weakThis{ get_weak() };
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis, color]() {
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))
{
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
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);
});
_runtimeTabColor.emplace(color);
_RecalculateAndApplyTabColor();
}
// Method Description:
// Clear the custom color of the tab, if any
// the background color
// - 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::ResetTabColor()
void Tab::_RecalculateAndApplyTabColor()
{
auto weakThis{ get_weak() };
@ -788,34 +778,121 @@ namespace winrt::TerminalApp::implementation
return;
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)
std::optional<winrt::Windows::UI::Color> currentColor = tab->GetTabColor();
if (currentColor.has_value())
{
auto key = winrt::box_value(keyString);
if (tab->_tabViewItem.Resources().HasKey(key))
{
tab->_tabViewItem.Resources().Remove(key);
}
tab->_ApplyTabColor(currentColor.value());
}
else
{
tab->_ClearTabBackgroundColor();
}
tab->_RefreshVisualState();
tab->_tabColor.reset();
tab->_colorCleared();
});
}
// Method Description:
// - 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::ResetRuntimeTabColor()
{
_runtimeTabColor.reset();
_RecalculateAndApplyTabColor();
}
// 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"
};
// 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))
{
_tabViewItem.Resources().Remove(key);
}
}
_RefreshVisualState();
_colorCleared();
}
// Method Description:
// - Display the tab color picker at the location of the TabViewItem for this tab.
// Arguments:

View file

@ -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;
};
}

View file

@ -1818,17 +1818,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();
}

View file

@ -54,6 +54,8 @@ namespace winrt::TerminalApp::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);

View file

@ -82,6 +82,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);
@ -2051,6 +2054,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)
{
@ -2821,6 +2828,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.

View file

@ -107,6 +107,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);
@ -118,6 +120,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:
@ -219,6 +222,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();

View file

@ -70,5 +70,8 @@ namespace Microsoft.Terminal.TerminalControl
void ResetFontSize();
void ToggleRetroEffect();
Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
}
}

View file

@ -34,6 +34,8 @@ namespace Microsoft.Terminal.TerminalControl
String WordDelimiters;
Boolean ForceVTInput;
Windows.Foundation.IReference<UInt32> TabColor;
};
}

View file

@ -147,6 +147,19 @@ void Terminal::UpdateSettings(ICoreSettings settings)
_terminalInput->ForceDisableWin32InputMode(settings.ForceVTInput());
if (settings.TabColor() == nullptr)
{
_tabColor = std::nullopt;
}
else
{
_tabColor = til::color(settings.TabColor().Value() | 0xff000000);
}
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
// size is smaller than where the mutable viewport currently is, we'll want
@ -913,6 +926,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);
@ -969,3 +987,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;
}

View file

@ -168,6 +168,7 @@ public:
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;
@ -176,6 +177,8 @@ public:
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
@ -199,12 +202,14 @@ private:
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;
std::optional<std::wstring> _title;
std::wstring _startingTitle;
std::optional<til::color> _tabColor;
std::array<COLORREF, XTERM_COLOR_TABLE_SIZE> _colorTable;
COLORREF _defaultFg;

View file

@ -4,3 +4,4 @@
#pragma once
#include <LibraryIncludes.h>
#include "winrt/Windows.Foundation.h"

View file

@ -9,8 +9,6 @@
// output, and then flush the output of the VtEngine straight to the Terminal.
#include "pch.h"
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "../../types/inc/Viewport.hpp"
#include "../../types/inc/convert.hpp"

View file

@ -6,6 +6,7 @@
#include "DefaultSettings.h"
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include "../inc/cppwinrt_utils.h"
using namespace winrt::Microsoft::Terminal::TerminalControl;
@ -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;

View file

@ -17,31 +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.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>

View file

@ -22,7 +22,6 @@ unit testing projects in the codebase without a bunch of overhead.
#define VERIFY_SUCCESS_NTSTATUS(x) VERIFY_IS_TRUE(NT_SUCCESS(x))
#include "precomp.h"
#include "../host/globals.h"
#include "../host/inputReadHandleData.h"
#include "../buffer/out/CharRow.hpp"

View file

@ -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"
{

61
src/inc/til/coalesce.h Normal file
View file

@ -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 coalesce_value<T...>()
template<typename T>
T coalesce_value(const T& base)
{
return base;
}
// Method Description:
// - Base case provided to throw an assertion if you call coalesce_value(opt, opt, opt)
template<typename T>
T coalesce_value(const std::optional<T>& base)
{
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 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(coalesce_value(std::forward<Ts>(t2)...));
}
// Method Description:
// - Base case provided to handle the last argument to coalesce_value<T...>()
template<typename T>
std::optional<T> coalesce(const std::optional<T>& base)
{
return base;
}
// Method Description:
// - Base case provided to handle the last argument to coalesce_value<T...>(..., nullopt)
template<typename T>
std::optional<T> coalesce(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> coalesce(const std::optional<T>& t1, Ts&&... t2)
{
return t1.has_value() ? t1 : coalesce(std::forward<Ts>(t2)...);
}
}

View file

@ -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::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::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::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::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::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::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::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());
}

View file

@ -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>
</Project>

View file

@ -96,32 +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)...));
}
}