mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-19 04:31:10 +00:00
Compare commits
1 Commits
main
...
dev/cazamo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7854388a98 |
@@ -1143,13 +1143,6 @@ til::CoordType ROW::GetTrailingColumnAtCharOffset(const ptrdiff_t offset) const
|
||||
return _createCharToColumnMapper(offset).GetTrailingColumnAt(offset);
|
||||
}
|
||||
|
||||
uint16_t ROW::GetCharOffset(til::CoordType col) const noexcept
|
||||
{
|
||||
const auto columns = GetReadableColumnCount();
|
||||
const auto colBeg = clamp(col, 0, columns);
|
||||
return _uncheckedCharOffset(gsl::narrow_cast<size_t>(colBeg));
|
||||
}
|
||||
|
||||
DelimiterClass ROW::DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept
|
||||
{
|
||||
const auto col = _clampedColumn(column);
|
||||
|
||||
@@ -172,7 +172,6 @@ public:
|
||||
std::wstring_view GetText(til::CoordType columnBegin, til::CoordType columnEnd) const noexcept;
|
||||
til::CoordType GetLeadingColumnAtCharOffset(ptrdiff_t offset) const noexcept;
|
||||
til::CoordType GetTrailingColumnAtCharOffset(ptrdiff_t offset) const noexcept;
|
||||
uint16_t GetCharOffset(til::CoordType col) const noexcept;
|
||||
DelimiterClass DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept;
|
||||
|
||||
auto AttrBegin() const noexcept { return _attr.begin(); }
|
||||
|
||||
@@ -308,11 +308,6 @@ namespace TerminalAppLocalTests
|
||||
// TerminalPage and not only create them successfully, but also create a
|
||||
// tab using those settings successfully.
|
||||
|
||||
// - - - IMPORTANT - - -
|
||||
// GH#14623: "closeOnExit": "never" is important for all test profiles. Without
|
||||
// it, the spawned process exits immediately in the UAP test environment,
|
||||
// and the default "automatic" close-on-exit behavior removes the
|
||||
// tab/pane asynchronously, racing against test assertions.
|
||||
static constexpr std::wstring_view settingsJson0{ LR"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
@@ -320,14 +315,12 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
@@ -354,6 +347,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::TryDuplicateBadTab()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
// * Create a tab with a profile with GUID 1
|
||||
// * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// * Try calling _DuplicateFocusedTab on tab 1
|
||||
@@ -368,14 +365,12 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
@@ -387,8 +382,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
@@ -444,6 +438,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::TryDuplicateBadPane()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
// * Create a tab with a profile with GUID 1
|
||||
// * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// * Try calling _SplitPane(Duplicate) on tab 1
|
||||
@@ -458,14 +456,12 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
@@ -477,8 +473,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
@@ -577,29 +572,25 @@ namespace TerminalAppLocalTests
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"tabTitle" : "Profile 0",
|
||||
"historySize": 1,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"tabTitle" : "Profile 1",
|
||||
"historySize": 2,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 2
|
||||
},
|
||||
{
|
||||
"name" : "profile2",
|
||||
"guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
|
||||
"tabTitle" : "Profile 2",
|
||||
"historySize": 3,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 3
|
||||
},
|
||||
{
|
||||
"name" : "profile3",
|
||||
"guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}",
|
||||
"tabTitle" : "Profile 3",
|
||||
"historySize": 4,
|
||||
"closeOnExit": "never"
|
||||
"historySize": 4
|
||||
}
|
||||
],
|
||||
"schemes":
|
||||
@@ -702,6 +693,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
@@ -741,6 +733,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::MoveFocusFromZoomedPane()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Create a second pane");
|
||||
@@ -786,6 +782,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::CloseZoomedPane()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Create a second pane");
|
||||
@@ -841,6 +841,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::SwapPanes()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Setup 4 panes.");
|
||||
@@ -1047,31 +1051,31 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::NextMRUTab()
|
||||
{
|
||||
// This is a test for GH#8025 - we want to make sure that MRU tab
|
||||
// ordering works correctly and that in-order/disabled switching works.
|
||||
//
|
||||
// Note: We test MRU ordering directly rather than going through the
|
||||
// command palette tab switcher, because the palette's anchor key
|
||||
// handling auto-dismisses when no modifier keys are held (which we
|
||||
// can't simulate in the test environment).
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
// This is a test for GH#8025 - we want to make sure that we can do both
|
||||
// in-order and MRU tab traversal, using the tab switcher and with the
|
||||
// tab switcher disabled.
|
||||
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Create Tab[1]");
|
||||
Log::Comment(L"Create a second tab");
|
||||
TestOnUIThread([&page]() {
|
||||
NewTerminalArgs newTerminalArgs{ 1 };
|
||||
page->_OpenNewTab(newTerminalArgs);
|
||||
});
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
|
||||
Log::Comment(L"Create Tab[2]");
|
||||
Log::Comment(L"Create a third tab");
|
||||
TestOnUIThread([&page]() {
|
||||
NewTerminalArgs newTerminalArgs{ 2 };
|
||||
page->_OpenNewTab(newTerminalArgs);
|
||||
});
|
||||
VERIFY_ARE_EQUAL(3u, page->_tabs.Size());
|
||||
|
||||
Log::Comment(L"Create Tab[3]");
|
||||
Log::Comment(L"Create a fourth tab");
|
||||
TestOnUIThread([&page]() {
|
||||
NewTerminalArgs newTerminalArgs{ 3 };
|
||||
page->_OpenNewTab(newTerminalArgs);
|
||||
@@ -1080,89 +1084,99 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify Tab[3] is focused");
|
||||
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one");
|
||||
});
|
||||
|
||||
Log::Comment(L"Select Tab[1]");
|
||||
Log::Comment(L"Select the second tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectTab(1);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
VERIFY_ARE_EQUAL(1u, focusedIndex, L"Verify Tab[1] is focused");
|
||||
VERIFY_ARE_EQUAL(1u, focusedIndex, L"Verify the second tab is the focused one");
|
||||
});
|
||||
|
||||
// MRU order should now be: Tab[1], Tab[3], Tab[2], Tab[0]
|
||||
// Verify the MRU list directly.
|
||||
Log::Comment(L"Verify MRU order: MRU[0]=Tab[1], MRU[1]=Tab[3]");
|
||||
Log::Comment(L"Change the tab switch order to MRU switching");
|
||||
TestOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(4u, page->_mruTabs.Size());
|
||||
uint32_t mruIdx;
|
||||
page->_tabs.IndexOf(page->_mruTabs.GetAt(0), mruIdx);
|
||||
VERIFY_ARE_EQUAL(1u, mruIdx, L"MRU[0] should be Tab[1] (most recent)");
|
||||
page->_tabs.IndexOf(page->_mruTabs.GetAt(1), mruIdx);
|
||||
VERIFY_ARE_EQUAL(3u, mruIdx, L"MRU[1] should be Tab[3] (last tab added)");
|
||||
page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::MostRecentlyUsed);
|
||||
});
|
||||
|
||||
Log::Comment(L"Select MRU[1]=Tab[3] directly");
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the fourth tab");
|
||||
TestOnUIThread([&page]() {
|
||||
// The next MRU tab after Tab[1] is Tab[3]
|
||||
uint32_t nextMruIdx;
|
||||
page->_tabs.IndexOf(page->_mruTabs.GetAt(1), nextMruIdx);
|
||||
page->_SelectTab(nextMruIdx);
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
});
|
||||
|
||||
Log::Comment(L"Sleep to let events propagate");
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Hide the command palette, to confirm the selection");
|
||||
// If you don't do this, the palette will just stay open, and the
|
||||
// next time we call _HandleNextTab, we'll continue traversing the
|
||||
// MRU list, instead of just hoping one entry.
|
||||
page->LoadCommandPalette().Visibility(Visibility::Collapsed);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify Tab[3] is focused");
|
||||
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one");
|
||||
});
|
||||
|
||||
Log::Comment(L"Select MRU[1]=Tab[1] directly");
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the second tab");
|
||||
TestOnUIThread([&page]() {
|
||||
uint32_t nextMruIdx;
|
||||
page->_tabs.IndexOf(page->_mruTabs.GetAt(1), nextMruIdx);
|
||||
page->_SelectTab(nextMruIdx);
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
});
|
||||
|
||||
Log::Comment(L"Sleep to let events propagate");
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Hide the command palette, to confirm the selection");
|
||||
// If you don't do this, the palette will just stay open, and the
|
||||
// next time we call _HandleNextTab, we'll continue traversing the
|
||||
// MRU list, instead of just hoping one entry.
|
||||
page->LoadCommandPalette().Visibility(Visibility::Collapsed);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
VERIFY_ARE_EQUAL(1u, focusedIndex, L"Verify Tab[1] is focused");
|
||||
VERIFY_ARE_EQUAL(1u, focusedIndex, L"Verify the second tab is the focused one");
|
||||
});
|
||||
|
||||
Log::Comment(L"Change the tab switch order to in-order switching");
|
||||
page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::InOrder);
|
||||
|
||||
Log::Comment(L"Switch to the next in-order tab, which is the third tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
VERIFY_ARE_EQUAL(2u, focusedIndex, L"Verify the third tab is the focused one");
|
||||
});
|
||||
|
||||
// The Disabled tab switcher mode uses direct index-based switching
|
||||
// without the command palette, so it works in the test environment.
|
||||
Log::Comment(L"Change the tab switch order to not use the tab switcher (which is in-order always)");
|
||||
page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::Disabled);
|
||||
|
||||
Log::Comment(L"Switch to the next in-order tab: Tab[2]");
|
||||
Log::Comment(L"Switch to the next in-order tab, which is the fourth tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
VERIFY_ARE_EQUAL(2u, focusedIndex, L"Verify Tab[2] is focused");
|
||||
});
|
||||
|
||||
Log::Comment(L"Switch to the next in-order tab: Tab[3]");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify Tab[3] is focused");
|
||||
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one");
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::VerifyCommandPaletteTabSwitcherOrder()
|
||||
{
|
||||
// This is a test for GH#8188 - we want to make sure that the MRU
|
||||
// ordering is correctly maintained as tabs are selected.
|
||||
//
|
||||
// Note: We verify MRU ordering directly rather than going through
|
||||
// the command palette tab switcher, because the palette's anchor key
|
||||
// handling auto-dismisses when no modifier keys are held (which we
|
||||
// can't simulate in the test environment).
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
// This is a test for GH#8188 - we want to make sure that the order of tabs
|
||||
// is preserved in the CommandPalette's TabSwitcher
|
||||
|
||||
auto page = _commonSetup();
|
||||
|
||||
@@ -1175,7 +1189,7 @@ namespace TerminalAppLocalTests
|
||||
});
|
||||
VERIFY_ARE_EQUAL(4u, page->_mruTabs.Size());
|
||||
|
||||
Log::Comment(L"give alphabetical names to all tabs");
|
||||
Log::Comment(L"give alphabetical names to all switch tab actions");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_GetTabImpl(page->_tabs.GetAt(0))->Title(L"a");
|
||||
});
|
||||
@@ -1197,14 +1211,18 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"c", page->_tabs.GetAt(2).Title());
|
||||
VERIFY_ARE_EQUAL(L"d", page->_tabs.GetAt(3).Title());
|
||||
|
||||
// MRU order after creating Tab[0]-Tab[3]: MRU[0]=Tab[3], MRU[3]=Tab[0]
|
||||
VERIFY_ARE_EQUAL(L"d", page->_mruTabs.GetAt(0).Title());
|
||||
VERIFY_ARE_EQUAL(L"c", page->_mruTabs.GetAt(1).Title());
|
||||
VERIFY_ARE_EQUAL(L"b", page->_mruTabs.GetAt(2).Title());
|
||||
VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title());
|
||||
});
|
||||
|
||||
Log::Comment(L"Select Tab[0] through Tab[3] to establish MRU order");
|
||||
Log::Comment(L"Change the tab switch order to MRU switching");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::MostRecentlyUsed);
|
||||
});
|
||||
|
||||
Log::Comment(L"Select the tabs from 0 to 3");
|
||||
RunOnUIThread([&page]() {
|
||||
page->_UpdatedSelectedTab(page->_tabs.GetAt(0));
|
||||
page->_UpdatedSelectedTab(page->_tabs.GetAt(1));
|
||||
@@ -1212,31 +1230,47 @@ namespace TerminalAppLocalTests
|
||||
page->_UpdatedSelectedTab(page->_tabs.GetAt(3));
|
||||
});
|
||||
|
||||
Log::Comment(L"Verify MRU order: MRU[0]='d', MRU[1]='c', MRU[2]='b', MRU[3]='a'");
|
||||
VERIFY_ARE_EQUAL(4u, page->_mruTabs.Size());
|
||||
VERIFY_ARE_EQUAL(L"d", page->_mruTabs.GetAt(0).Title());
|
||||
VERIFY_ARE_EQUAL(L"c", page->_mruTabs.GetAt(1).Title());
|
||||
VERIFY_ARE_EQUAL(L"b", page->_mruTabs.GetAt(2).Title());
|
||||
VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title());
|
||||
|
||||
Log::Comment(L"Select Tab[2]='c' (MRU[1] after 'd')");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectTab(2);
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the third tab");
|
||||
RunOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
// In the course of a single tick, the Command Palette will:
|
||||
// * open
|
||||
// * select the proper tab from the mru's list
|
||||
// * raise an event for _filteredActionsView().SelectionChanged to
|
||||
// immediately preview the new tab
|
||||
// * raise a _SwitchToTabRequestedHandlers event
|
||||
// * then dismiss itself, because we can't fake holing down an
|
||||
// anchor key in the tests
|
||||
});
|
||||
|
||||
Log::Comment(L"Verify MRU order updated: MRU[0]='c', MRU[1]='d', MRU[2]='b', MRU[3]='a'");
|
||||
TestOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(L"c", page->_mruTabs.GetAt(0).Title());
|
||||
VERIFY_ARE_EQUAL(L"d", page->_mruTabs.GetAt(1).Title());
|
||||
VERIFY_ARE_EQUAL(L"b", page->_mruTabs.GetAt(2).Title());
|
||||
VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title());
|
||||
});
|
||||
|
||||
const auto palette = winrt::get_self<winrt::TerminalApp::implementation::CommandPalette>(page->LoadCommandPalette());
|
||||
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::implementation::CommandPaletteMode::TabSwitchMode, palette->_currentMode, L"Verify we are in the tab switcher mode");
|
||||
// At this point, the contents of the command palette's _mruTabs list is
|
||||
// still the _old_ ordering (d, c, b, a). The ordering is only updated
|
||||
// in TerminalPage::_SelectNextTab, but as we saw before, the palette
|
||||
// will also dismiss itself immediately when that's called. So we can't
|
||||
// really inspect the contents of the list in this test, unfortunately.
|
||||
}
|
||||
|
||||
void TabTests::TestWindowRenameSuccessful()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
@@ -1269,6 +1303,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
@@ -1301,6 +1336,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::TestPreviewCommitScheme()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
Log::Comment(L"Preview a color scheme. Make sure it's applied, then committed accordingly");
|
||||
|
||||
auto page = _commonSetup();
|
||||
@@ -1363,6 +1402,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::TestPreviewDismissScheme()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
Log::Comment(L"Preview a color scheme. Make sure it's applied, then dismissed accordingly");
|
||||
|
||||
auto page = _commonSetup();
|
||||
@@ -1411,6 +1454,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::TestPreviewSchemeWhilePreviewing()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
Log::Comment(L"Preview a color scheme, then preview another scheme. ");
|
||||
|
||||
Log::Comment(L"Preview a color scheme. Make sure it's applied, then committed accordingly");
|
||||
@@ -1478,6 +1525,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void TabTests::TestClampSwitchToTab()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True") // GH#19610 tracks re-enabling this test
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
Log::Comment(L"Test that switching to a tab index higher than the number of tabs just clamps to the last tab.");
|
||||
|
||||
auto page = _commonSetup();
|
||||
|
||||
@@ -2906,6 +2906,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do we ever get here (= uninitialized terminal)? If so: How?
|
||||
assert(false);
|
||||
return { 10, 10 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +39,12 @@ winrt::com_ptr<AppearanceConfig> AppearanceConfig::CopyAppearance(const Appearan
|
||||
|
||||
appearance->_DarkColorSchemeName = source->_DarkColorSchemeName;
|
||||
appearance->_LightColorSchemeName = source->_LightColorSchemeName;
|
||||
appearance->_json = source->_json;
|
||||
|
||||
#define APPEARANCE_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
appearance->_##name = source->_##name;
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_COPY)
|
||||
#undef APPEARANCE_SETTINGS_COPY
|
||||
// Complex/mutable settings with backing fields
|
||||
appearance->_PixelShaderPath = source->_PixelShaderPath;
|
||||
appearance->_PixelShaderImagePath = source->_PixelShaderImagePath;
|
||||
appearance->_BackgroundImagePath = source->_BackgroundImagePath;
|
||||
|
||||
return appearance;
|
||||
}
|
||||
@@ -71,14 +72,116 @@ Json::Value AppearanceConfig::ToJson() const
|
||||
}
|
||||
}
|
||||
|
||||
#define APPEARANCE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
// MTSM appearance settings: copy from _json (the source of truth)
|
||||
#define APPEARANCE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
if (_json.isMember(jsonKey) && !_json[jsonKey].isNull()) \
|
||||
{ \
|
||||
json[JsonKey(jsonKey)] = _json[JsonKey(jsonKey)]; \
|
||||
}
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_TO_JSON)
|
||||
#undef APPEARANCE_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings with backing fields
|
||||
JsonUtils::SetValueForKey(json, "experimental.pixelShaderPath", _PixelShaderPath);
|
||||
JsonUtils::SetValueForKey(json, "experimental.pixelShaderImagePath", _PixelShaderImagePath);
|
||||
JsonUtils::SetValueForKey(json, "backgroundImage", _BackgroundImagePath);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool AppearanceConfig::HasSetting(AppearanceSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _APPEARANCE_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_APPEARANCE_SETTINGS(_APPEARANCE_HAS_SETTING)
|
||||
#undef _APPEARANCE_HAS_SETTING
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
return HasForeground();
|
||||
case AppearanceSettingKey::_Background:
|
||||
return HasBackground();
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
return HasSelectionBackground();
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
return HasCursorColor();
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
return HasOpacity();
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
return HasDarkColorSchemeName();
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
return HasLightColorSchemeName();
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
return HasPixelShaderPath();
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
return HasPixelShaderImagePath();
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
return HasBackgroundImagePath();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AppearanceConfig::ClearSetting(AppearanceSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _APPEARANCE_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_APPEARANCE_SETTINGS(_APPEARANCE_CLEAR_SETTING)
|
||||
#undef _APPEARANCE_CLEAR_SETTING
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
ClearForeground();
|
||||
break;
|
||||
case AppearanceSettingKey::_Background:
|
||||
ClearBackground();
|
||||
break;
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
ClearSelectionBackground();
|
||||
break;
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
ClearCursorColor();
|
||||
break;
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
ClearOpacity();
|
||||
break;
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
ClearDarkColorSchemeName();
|
||||
break;
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
ClearLightColorSchemeName();
|
||||
break;
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
ClearPixelShaderPath();
|
||||
break;
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
ClearPixelShaderImagePath();
|
||||
break;
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
ClearBackgroundImagePath();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AppearanceSettingKey> AppearanceConfig::CurrentSettings() const
|
||||
{
|
||||
std::vector<AppearanceSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(AppearanceSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<AppearanceSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Layer values from the given json object on top of the existing properties
|
||||
// of this object. For any keys we're expecting to be able to parse in the
|
||||
@@ -92,6 +195,14 @@ Json::Value AppearanceConfig::ToJson() const
|
||||
// - json: an object which should be a partial serialization of an AppearanceConfig object.
|
||||
void AppearanceConfig::LayerJson(const Json::Value& json)
|
||||
{
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
// AppearanceConfig receives the full profile JSON; we store all keys and
|
||||
// read only appearance-relevant ones from it.
|
||||
for (const auto& key : json.getMemberNames())
|
||||
{
|
||||
_json[key] = json[key];
|
||||
}
|
||||
|
||||
JsonUtils::GetValueForKey(json, ForegroundKey, _Foreground);
|
||||
_logSettingIfSet(ForegroundKey, _Foreground.has_value());
|
||||
|
||||
@@ -125,12 +236,28 @@ void AppearanceConfig::LayerJson(const Json::Value& json)
|
||||
_logSettingSet("colorScheme.light");
|
||||
}
|
||||
|
||||
// Normalize legacy opacity key into canonical
|
||||
if (json.isMember(JsonKey(LegacyAcrylicTransparencyKey)))
|
||||
{
|
||||
_json[JsonKey(OpacityKey)] = json[JsonKey(LegacyAcrylicTransparencyKey)];
|
||||
}
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define APPEARANCE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_LAYER_JSON)
|
||||
#undef APPEARANCE_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings that have backing fields (not JSON-backed)
|
||||
JsonUtils::GetValueForKey(json, "experimental.pixelShaderPath", _PixelShaderPath);
|
||||
_logSettingIfSet("experimental.pixelShaderPath", _PixelShaderPath.has_value());
|
||||
JsonUtils::GetValueForKey(json, "experimental.pixelShaderImagePath", _PixelShaderImagePath);
|
||||
_logSettingIfSet("experimental.pixelShaderImagePath", _PixelShaderImagePath.has_value());
|
||||
JsonUtils::GetValueForKey(json, "backgroundImage", _BackgroundImagePath);
|
||||
_logSettingIfSet("backgroundImage", _BackgroundImagePath.has_value());
|
||||
}
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile AppearanceConfig::SourceProfile()
|
||||
|
||||
@@ -18,6 +18,7 @@ Author(s):
|
||||
|
||||
#include "AppearanceConfig.g.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include "IInheritable.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
@@ -38,6 +39,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
void ResolveMediaResources(const Model::MediaResourceResolver& resolver);
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(AppearanceSettingKey key) const;
|
||||
void ClearSetting(AppearanceSettingKey key);
|
||||
std::vector<AppearanceSettingKey> CurrentSettings() const;
|
||||
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Foreground, nullptr);
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Background, nullptr);
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, SelectionBackground, nullptr);
|
||||
@@ -48,12 +54,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, hstring, LightColorSchemeName, L"Campbell");
|
||||
|
||||
#define APPEARANCE_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, type, name, ##__VA_ARGS__)
|
||||
INHERITABLE_JSON_SETTING(Model::IAppearanceConfig, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_INITIALIZE)
|
||||
#undef APPEARANCE_SETTINGS_INITIALIZE
|
||||
|
||||
// Complex/mutable settings that need backing fields (not JSON-backed)
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, IMediaResource, PixelShaderPath, implementation::MediaResource::Empty());
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, IMediaResource, PixelShaderImagePath, implementation::MediaResource::Empty());
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, IMediaResource, BackgroundImagePath, implementation::MediaResource::Empty());
|
||||
|
||||
private:
|
||||
winrt::weak_ref<Profile> _sourceProfile;
|
||||
|
||||
// Raw JSON for this layer (appearance-relevant keys only).
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
void _logSettingSet(const std::string_view& setting);
|
||||
|
||||
@@ -25,13 +25,12 @@ winrt::com_ptr<FontConfig> FontConfig::CopyFontInfo(const FontConfig* source, wi
|
||||
{
|
||||
auto fontInfo{ winrt::make_self<FontConfig>(std::move(sourceProfile)) };
|
||||
|
||||
#define FONT_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
fontInfo->_##name = source->_##name;
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_COPY)
|
||||
#undef FONT_SETTINGS_COPY
|
||||
fontInfo->_json = source->_json;
|
||||
|
||||
// We cannot simply copy the font axes and features with `fontInfo->_FontAxes = source->_FontAxes;`
|
||||
// since that'll just create a reference; we have to manually copy the values.
|
||||
// Complex/mutable settings with backing fields
|
||||
fontInfo->_FontAxes = source->_FontAxes;
|
||||
fontInfo->_FontFeatures = source->_FontFeatures;
|
||||
// Deep copy font maps (IMap creates references, not copies)
|
||||
static constexpr auto cloneFontMap = [](const IFontFeatureMap& map) {
|
||||
std::map<winrt::hstring, float> fontAxes;
|
||||
for (const auto& [k, v] : map)
|
||||
@@ -56,14 +55,75 @@ Json::Value FontConfig::ToJson() const
|
||||
{
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
#define FONT_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
// MTSM font settings: copy from _json (the source of truth)
|
||||
#define FONT_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
if (_json.isMember(jsonKey) && !_json[jsonKey].isNull()) \
|
||||
{ \
|
||||
json[JsonKey(jsonKey)] = _json[JsonKey(jsonKey)]; \
|
||||
}
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_TO_JSON)
|
||||
#undef FONT_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings with backing fields
|
||||
JsonUtils::SetValueForKey(json, "axes", _FontAxes);
|
||||
JsonUtils::SetValueForKey(json, "features", _FontFeatures);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool FontConfig::HasSetting(FontSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _FONT_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_FONT_SETTINGS(_FONT_HAS_SETTING)
|
||||
#undef _FONT_HAS_SETTING
|
||||
case FontSettingKey::_FontAxes:
|
||||
return HasFontAxes();
|
||||
case FontSettingKey::_FontFeatures:
|
||||
return HasFontFeatures();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FontConfig::ClearSetting(FontSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _FONT_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_FONT_SETTINGS(_FONT_CLEAR_SETTING)
|
||||
#undef _FONT_CLEAR_SETTING
|
||||
case FontSettingKey::_FontAxes:
|
||||
ClearFontAxes();
|
||||
break;
|
||||
case FontSettingKey::_FontFeatures:
|
||||
ClearFontFeatures();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<FontSettingKey> FontConfig::CurrentSettings() const
|
||||
{
|
||||
std::vector<FontSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(FontSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<FontSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Layer values from the given json object on top of the existing properties
|
||||
// of this object. For any keys we're expecting to be able to parse in the
|
||||
@@ -83,25 +143,47 @@ void FontConfig::LayerJson(const Json::Value& json)
|
||||
{
|
||||
// A font object is defined, use that
|
||||
const auto fontInfoJson = json[JsonKey(FontInfoKey)];
|
||||
#define FONT_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(fontInfoJson, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
|
||||
// Merge the font sub-object into stored _json (font-object shape).
|
||||
for (const auto& key : fontInfoJson.getMemberNames())
|
||||
{
|
||||
_json[key] = fontInfoJson[key];
|
||||
}
|
||||
|
||||
// MTSM font settings are now JSON-backed. Values are already in _json.
|
||||
// We only need to log which settings were set.
|
||||
#define FONT_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
_logSettingIfSet(jsonKey, fontInfoJson.isMember(jsonKey) && !fontInfoJson[jsonKey].isNull());
|
||||
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_LAYER_JSON)
|
||||
#undef FONT_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings that have backing fields (not JSON-backed)
|
||||
JsonUtils::GetValueForKey(fontInfoJson, "axes", _FontAxes);
|
||||
_logSettingIfSet("axes", _FontAxes.has_value());
|
||||
JsonUtils::GetValueForKey(fontInfoJson, "features", _FontFeatures);
|
||||
_logSettingIfSet("features", _FontFeatures.has_value());
|
||||
}
|
||||
else
|
||||
{
|
||||
// No font object is defined
|
||||
// No font object is defined — normalize legacy flat keys into font-object shape.
|
||||
if (json.isMember(JsonKey(LegacyFontFaceKey)))
|
||||
{
|
||||
_json["face"] = json[JsonKey(LegacyFontFaceKey)];
|
||||
}
|
||||
if (json.isMember(JsonKey(LegacyFontSizeKey)))
|
||||
{
|
||||
_json["size"] = json[JsonKey(LegacyFontSizeKey)];
|
||||
}
|
||||
if (json.isMember(JsonKey(LegacyFontWeightKey)))
|
||||
{
|
||||
_json["weight"] = json[JsonKey(LegacyFontWeightKey)];
|
||||
}
|
||||
|
||||
// Log settings as if they were a part of the font object
|
||||
JsonUtils::GetValueForKey(json, LegacyFontFaceKey, _FontFace);
|
||||
_logSettingIfSet("face", _FontFace.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, LegacyFontSizeKey, _FontSize);
|
||||
_logSettingIfSet("size", _FontSize.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, LegacyFontWeightKey, _FontWeight);
|
||||
_logSettingIfSet("weight", _FontWeight.has_value());
|
||||
_logSettingIfSet("face", json.isMember(JsonKey(LegacyFontFaceKey)));
|
||||
_logSettingIfSet("size", json.isMember(JsonKey(LegacyFontSizeKey)));
|
||||
_logSettingIfSet("weight", json.isMember(JsonKey(LegacyFontWeightKey)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,16 +194,16 @@ winrt::Microsoft::Terminal::Settings::Model::Profile FontConfig::SourceProfile()
|
||||
|
||||
void FontConfig::_logSettingSet(const std::string_view& setting)
|
||||
{
|
||||
if (setting == "axes" && _FontAxes.has_value())
|
||||
if (setting == "axes" && HasFontAxes())
|
||||
{
|
||||
for (const auto& [mapKey, _] : _FontAxes.value())
|
||||
for (const auto& [mapKey, _] : FontAxes())
|
||||
{
|
||||
_changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, til::u16u8(mapKey)));
|
||||
}
|
||||
}
|
||||
else if (setting == "features" && _FontFeatures.has_value())
|
||||
else if (setting == "features" && HasFontFeatures())
|
||||
{
|
||||
for (const auto& [mapKey, _] : _FontFeatures.value())
|
||||
for (const auto& [mapKey, _] : FontFeatures())
|
||||
{
|
||||
_changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, til::u16u8(mapKey)));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ Author(s):
|
||||
#include "pch.h"
|
||||
#include "FontConfig.g.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "IInheritable.h"
|
||||
#include <DefaultSettings.h>
|
||||
@@ -39,13 +40,26 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
Model::Profile SourceProfile();
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(FontSettingKey key) const;
|
||||
void ClearSetting(FontSettingKey key);
|
||||
std::vector<FontSettingKey> CurrentSettings() const;
|
||||
|
||||
#define FONT_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING(Model::FontConfig, type, name, ##__VA_ARGS__)
|
||||
INHERITABLE_JSON_SETTING(Model::FontConfig, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_INITIALIZE)
|
||||
#undef FONT_SETTINGS_INITIALIZE
|
||||
|
||||
// Complex/mutable settings that need backing fields (not JSON-backed)
|
||||
INHERITABLE_SETTING(Model::FontConfig, IFontAxesMap, FontAxes);
|
||||
INHERITABLE_SETTING(Model::FontConfig, IFontFeatureMap, FontFeatures);
|
||||
|
||||
private:
|
||||
winrt::weak_ref<Profile> _sourceProfile;
|
||||
|
||||
// Raw JSON for this layer (font sub-object shape).
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
void _logSettingSet(const std::string_view& setting);
|
||||
|
||||
@@ -64,11 +64,10 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
globals->_defaultProfile = _defaultProfile;
|
||||
globals->_actionMap = _actionMap->Copy();
|
||||
globals->_keybindingsWarnings = _keybindingsWarnings;
|
||||
globals->_json = _json;
|
||||
|
||||
#define GLOBAL_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
globals->_##name = _##name;
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_COPY)
|
||||
#undef GLOBAL_SETTINGS_COPY
|
||||
// MTSM settings are JSON-backed — they live in _json, which is already deep-copied above.
|
||||
// No per-setting copy needed.
|
||||
|
||||
if (_colorSchemes)
|
||||
{
|
||||
@@ -86,6 +85,8 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
globals->_themes.Insert(kv.Key(), *themeImpl->Copy());
|
||||
}
|
||||
}
|
||||
|
||||
// Complex/mutable settings with backing fields need explicit deep copy
|
||||
if (_NewTabMenu)
|
||||
{
|
||||
globals->_NewTabMenu = winrt::single_threaded_vector<Model::NewTabMenuEntry>();
|
||||
@@ -150,26 +151,66 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::FromJson(const Json::Value&
|
||||
|
||||
void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origin)
|
||||
{
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
for (const auto& key : json.getMemberNames())
|
||||
{
|
||||
_json[key] = json[key];
|
||||
}
|
||||
|
||||
JsonUtils::GetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);
|
||||
|
||||
// GH#8076 - when adding enum values to this key, we also changed it from
|
||||
// "useTabSwitcher" to "tabSwitcherMode". Continue supporting
|
||||
// "useTabSwitcher", but prefer "tabSwitcherMode"
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyUseTabSwitcherModeKey, _TabSwitcherMode) || _fixupsAppliedDuringLoad;
|
||||
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyInputServiceWarningKey, _InputServiceWarning) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyWarnAboutLargePasteKey, _WarnAboutLargePaste) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyWarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyConfirmCloseAllTabsKey, _ConfirmCloseAllTabs) || _fixupsAppliedDuringLoad;
|
||||
// Normalize legacy keys into canonical _json keys for JSON-backed getters.
|
||||
{
|
||||
Model::TabSwitcherMode legacyVal{};
|
||||
if (JsonUtils::GetValueForKey(json, LegacyUseTabSwitcherModeKey, legacyVal))
|
||||
{
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
_json["tabSwitcherMode"] = json[JsonKey(LegacyUseTabSwitcherModeKey)];
|
||||
}
|
||||
}
|
||||
{
|
||||
bool legacyVal{};
|
||||
if (JsonUtils::GetValueForKey(json, LegacyInputServiceWarningKey, legacyVal))
|
||||
{
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
_json["warning.inputService"] = json[JsonKey(LegacyInputServiceWarningKey)];
|
||||
}
|
||||
if (JsonUtils::GetValueForKey(json, LegacyWarnAboutLargePasteKey, legacyVal))
|
||||
{
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
_json["warning.largePaste"] = json[JsonKey(LegacyWarnAboutLargePasteKey)];
|
||||
}
|
||||
if (JsonUtils::GetValueForKey(json, LegacyWarnAboutMultiLinePasteKey, legacyVal))
|
||||
{
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
_json["warning.multiLinePaste"] = json[JsonKey(LegacyWarnAboutMultiLinePasteKey)];
|
||||
}
|
||||
if (JsonUtils::GetValueForKey(json, LegacyConfirmCloseAllTabsKey, legacyVal))
|
||||
{
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
_json["warning.confirmCloseAllTabs"] = json[JsonKey(LegacyConfirmCloseAllTabsKey)];
|
||||
}
|
||||
}
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define GLOBAL_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_LAYER_JSON)
|
||||
#undef GLOBAL_SETTINGS_LAYER_JSON
|
||||
|
||||
// GH#11975 We only want to allow sensible values and prevent crashes, so we are clamping those values
|
||||
// Complex/mutable settings that have backing fields (not JSON-backed)
|
||||
JsonUtils::GetValueForKey(json, "disabledProfileSources", _DisabledProfileSources);
|
||||
_logSettingIfSet("disabledProfileSources", _DisabledProfileSources.has_value());
|
||||
JsonUtils::GetValueForKey(json, "newTabMenu", _NewTabMenu);
|
||||
_logSettingIfSet("newTabMenu", _NewTabMenu.has_value());
|
||||
|
||||
// GH#11975We only want to allow sensible values and prevent crashes, so we are clamping those values
|
||||
// We only want to assign if the value did change through clamping,
|
||||
// otherwise we could end up setting defaults that get persisted
|
||||
if (this->HasInitialCols())
|
||||
@@ -304,47 +345,113 @@ Json::Value GlobalAppSettings::ToJson()
|
||||
// These experimental options should be removed from the settings file if they're at their default value.
|
||||
// This prevents them from sticking around forever, even if the user was just experimenting with them.
|
||||
// One could consider this a workaround for the settings UI right now not having a "reset to default" button for these.
|
||||
if (_GraphicsAPI == Control::GraphicsAPI::Automatic)
|
||||
if (HasGraphicsAPI() && GraphicsAPI() == Control::GraphicsAPI::Automatic)
|
||||
{
|
||||
_GraphicsAPI.reset();
|
||||
ClearGraphicsAPI();
|
||||
}
|
||||
if (_TextMeasurement == Control::TextMeasurement::Graphemes)
|
||||
if (HasTextMeasurement() && TextMeasurement() == Control::TextMeasurement::Graphemes)
|
||||
{
|
||||
_TextMeasurement.reset();
|
||||
ClearTextMeasurement();
|
||||
}
|
||||
if (_AmbiguousWidth == Control::AmbiguousWidth::Narrow)
|
||||
if (HasAmbiguousWidth() && AmbiguousWidth() == Control::AmbiguousWidth::Narrow)
|
||||
{
|
||||
_AmbiguousWidth.reset();
|
||||
ClearAmbiguousWidth();
|
||||
}
|
||||
if (_DefaultInputScope == Control::DefaultInputScope::Default)
|
||||
if (HasDefaultInputScope() && DefaultInputScope() == Control::DefaultInputScope::Default)
|
||||
{
|
||||
_DefaultInputScope.reset();
|
||||
ClearDefaultInputScope();
|
||||
}
|
||||
|
||||
if (_DisablePartialInvalidation == false)
|
||||
if (HasDisablePartialInvalidation() && DisablePartialInvalidation() == false)
|
||||
{
|
||||
_DisablePartialInvalidation.reset();
|
||||
ClearDisablePartialInvalidation();
|
||||
}
|
||||
if (_SoftwareRendering == false)
|
||||
if (HasSoftwareRendering() && SoftwareRendering() == false)
|
||||
{
|
||||
_SoftwareRendering.reset();
|
||||
ClearSoftwareRendering();
|
||||
}
|
||||
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
JsonUtils::SetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);
|
||||
|
||||
#define GLOBAL_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
// MTSM global settings: copy from _json (the source of truth)
|
||||
#define GLOBAL_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
if (_json.isMember(jsonKey) && !_json[jsonKey].isNull()) \
|
||||
{ \
|
||||
json[JsonKey(jsonKey)] = _json[JsonKey(jsonKey)]; \
|
||||
}
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_TO_JSON)
|
||||
#undef GLOBAL_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings with backing fields
|
||||
JsonUtils::SetValueForKey(json, "disabledProfileSources", _DisabledProfileSources);
|
||||
JsonUtils::SetValueForKey(json, "newTabMenu", _NewTabMenu);
|
||||
|
||||
json[JsonKey(ActionsKey)] = _actionMap->ToJson();
|
||||
json[JsonKey(KeybindingsKey)] = _actionMap->KeyBindingsToJson();
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::HasSetting(GlobalSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _GLOBAL_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_GLOBAL_SETTINGS(_GLOBAL_HAS_SETTING)
|
||||
#undef _GLOBAL_HAS_SETTING
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
return HasUnparsedDefaultProfile();
|
||||
case GlobalSettingKey::_DisabledProfileSources:
|
||||
return HasDisabledProfileSources();
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
return HasNewTabMenu();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalAppSettings::ClearSetting(GlobalSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _GLOBAL_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_GLOBAL_SETTINGS(_GLOBAL_CLEAR_SETTING)
|
||||
#undef _GLOBAL_CLEAR_SETTING
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
ClearUnparsedDefaultProfile();
|
||||
break;
|
||||
case GlobalSettingKey::_DisabledProfileSources:
|
||||
ClearDisabledProfileSources();
|
||||
break;
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
ClearNewTabMenu();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<GlobalSettingKey> GlobalAppSettings::CurrentSettings() const
|
||||
{
|
||||
std::vector<GlobalSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(GlobalSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<GlobalSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::FixupsAppliedDuringLoad()
|
||||
{
|
||||
return _fixupsAppliedDuringLoad || _actionMap->FixupsAppliedDuringLoad();
|
||||
@@ -394,9 +501,9 @@ bool GlobalAppSettings::ShouldUsePersistedLayout() const
|
||||
void GlobalAppSettings::ResolveMediaResources(const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
_actionMap->ResolveMediaResourcesWithBasePath(SourceBasePath, resolver);
|
||||
if (_NewTabMenu)
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
if (const auto resolvable{ entry.try_as<IPathlessMediaResourceContainer>() })
|
||||
{
|
||||
@@ -414,11 +521,12 @@ void GlobalAppSettings::_logSettingSet(const std::string_view& setting)
|
||||
{
|
||||
if (setting == "theme")
|
||||
{
|
||||
if (_Theme.has_value())
|
||||
if (HasTheme())
|
||||
{
|
||||
const auto theme = Theme();
|
||||
// ThemePair always has a Dark/Light value,
|
||||
// so we need to check if they were explicitly set
|
||||
if (_Theme->DarkName() == _Theme->LightName())
|
||||
if (theme.DarkName() == theme.LightName())
|
||||
{
|
||||
_changeLog.emplace(setting);
|
||||
}
|
||||
@@ -431,9 +539,9 @@ void GlobalAppSettings::_logSettingSet(const std::string_view& setting)
|
||||
}
|
||||
else if (setting == "newTabMenu")
|
||||
{
|
||||
if (_NewTabMenu.has_value())
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
std::string entryType;
|
||||
switch (entry.Type())
|
||||
@@ -476,7 +584,7 @@ void GlobalAppSettings::UpdateCommandID(const Model::Command& cmd, winrt::hstrin
|
||||
_actionMap->UpdateCommandID(cmd, newID);
|
||||
// newID might have been empty when this function was called, if so actionMap would have generated a new ID, use that
|
||||
newID = cmd.ID();
|
||||
if (_NewTabMenu)
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
// Recursive lambda function to look through all the new tab menu entries and update IDs accordingly
|
||||
std::function<void(const Model::NewTabMenuEntry&)> recursiveEntryIdUpdate;
|
||||
@@ -503,7 +611,7 @@ void GlobalAppSettings::UpdateCommandID(const Model::Command& cmd, winrt::hstrin
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
recursiveEntryIdUpdate(entry);
|
||||
}
|
||||
@@ -515,8 +623,8 @@ void GlobalAppSettings::_logSettingIfSet(const std::string_view& setting, const
|
||||
if (isSet)
|
||||
{
|
||||
// Exclude some false positives from userDefaults.json
|
||||
const bool settingCopyFormattingToDefault = til::equals_insensitive_ascii(setting, "copyFormatting") && _CopyFormatting.has_value() && _CopyFormatting.value() == static_cast<Control::CopyFormat>(0);
|
||||
const bool settingNTMToDefault = til::equals_insensitive_ascii(setting, "newTabMenu") && _NewTabMenu.has_value() && _NewTabMenu->Size() == 1 && _NewTabMenu->GetAt(0).Type() == NewTabMenuEntryType::RemainingProfiles;
|
||||
const bool settingCopyFormattingToDefault = til::equals_insensitive_ascii(setting, "copyFormatting") && HasCopyFormatting() && CopyFormatting() == static_cast<Control::CopyFormat>(0);
|
||||
const bool settingNTMToDefault = til::equals_insensitive_ascii(setting, "newTabMenu") && HasNewTabMenu() && NewTabMenu().Size() == 1 && NewTabMenu().GetAt(0).Type() == NewTabMenuEntryType::RemainingProfiles;
|
||||
if (!settingCopyFormattingToDefault && !settingNTMToDefault)
|
||||
{
|
||||
_logSettingSet(setting);
|
||||
|
||||
@@ -18,6 +18,7 @@ Author(s):
|
||||
#include "GlobalAppSettings.g.h"
|
||||
#include "IInheritable.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
|
||||
#include "ActionMap.h"
|
||||
#include "Command.h"
|
||||
@@ -56,6 +57,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Json::Value ToJson();
|
||||
bool FixupsAppliedDuringLoad();
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(GlobalSettingKey key) const;
|
||||
void ClearSetting(GlobalSettingKey key);
|
||||
std::vector<GlobalSettingKey> CurrentSettings() const;
|
||||
|
||||
const std::vector<SettingsLoadWarnings>& KeybindingsWarnings() const;
|
||||
|
||||
// This DefaultProfile() setter is called by CascadiaSettings,
|
||||
@@ -83,10 +89,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L"");
|
||||
|
||||
#define GLOBAL_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING_WITH_LOGGING(Model::GlobalAppSettings, type, name, jsonKey, ##__VA_ARGS__)
|
||||
INHERITABLE_JSON_SETTING_WITH_LOGGING(Model::GlobalAppSettings, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_INITIALIZE)
|
||||
#undef GLOBAL_SETTINGS_INITIALIZE
|
||||
|
||||
// Complex/mutable settings that need backing fields (not JSON-backed)
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, nullptr);
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} }));
|
||||
|
||||
private:
|
||||
#ifdef NDEBUG
|
||||
static constexpr bool debugFeaturesDefault{ false };
|
||||
@@ -94,6 +104,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static constexpr bool debugFeaturesDefault{ true };
|
||||
#endif
|
||||
|
||||
// Raw JSON for this layer. Populated by LayerJson(), will become the
|
||||
// source of truth for settings once the JSON-backed refactor is complete.
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
winrt::guid _defaultProfile{};
|
||||
bool _fixupsAppliedDuringLoad{ false };
|
||||
bool _legacyReloadEnvironmentVariables{ true };
|
||||
|
||||
@@ -14,6 +14,8 @@ Author(s):
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "JsonUtils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
template<typename T>
|
||||
@@ -265,3 +267,165 @@ public: \
|
||||
_##name = std::optional<type>{ std::nullopt }; \
|
||||
} \
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// JSON-backed variants of the above macros.
|
||||
// These have NO std::optional<T> backing field — _json is the source of truth.
|
||||
// Getters read from _json with type conversion inline.
|
||||
// Setters write to _json via SetValueForKey.
|
||||
// Has/Clear check/modify _json directly.
|
||||
// =============================================================================
|
||||
|
||||
// Shared base for JSON-backed inheritable settings.
|
||||
// No backing field; all state lives in _json.
|
||||
#define _BASE_INHERITABLE_JSON_SETTING(projectedType, type, name, jsonKey, ...) \
|
||||
public: \
|
||||
/* Returns true if the user explicitly set the value, false otherwise*/ \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return _json.isMember(jsonKey) && !_json[jsonKey].isNull(); \
|
||||
} \
|
||||
\
|
||||
projectedType name##OverrideSource() \
|
||||
{ \
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return *source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no source was found*/ \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_json.removeMember(JsonKey(jsonKey)); \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
/* Read value from this layer's _json only (no parent walk) */ \
|
||||
std::optional<type> _get##name##FromThisLayer() const \
|
||||
{ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
type val{ __VA_ARGS__ }; \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::GetValueForKey( \
|
||||
_json, jsonKey, val); \
|
||||
return val; \
|
||||
} \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
std::optional<type> _get##name##Impl() const \
|
||||
{ \
|
||||
/*return value from this layer*/ \
|
||||
if (auto val{ _get##name##FromThisLayer() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto val{ parent->_get##name##Impl() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), std::optional<type>> \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return { get_strong(), _get##name##FromThisLayer() }; \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return { source, source->_get##name##FromThisLayer() }; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return { nullptr, std::nullopt }; \
|
||||
}
|
||||
|
||||
// JSON-backed inheritable setting (no logging).
|
||||
// No backing field; getter reads from _json, setter writes to _json.
|
||||
#define INHERITABLE_JSON_SETTING(projectedType, type, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_JSON_SETTING(projectedType, type, name, jsonKey, __VA_ARGS__) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value in _json */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey( \
|
||||
_json, jsonKey, value); \
|
||||
}
|
||||
|
||||
// JSON-backed inheritable setting with change logging.
|
||||
// No backing field; getter reads from _json, setter writes to _json and logs.
|
||||
#define INHERITABLE_JSON_SETTING_WITH_LOGGING(projectedType, type, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_JSON_SETTING(projectedType, type, name, jsonKey, __VA_ARGS__) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value, log the change, and write to _json */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
const auto existingVal{ _get##name##FromThisLayer() }; \
|
||||
if (!existingVal.has_value() || existingVal.value() != value) \
|
||||
{ \
|
||||
_logSettingSet(jsonKey); \
|
||||
} \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey( \
|
||||
_json, jsonKey, value); \
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ Module Name:
|
||||
Abstract:
|
||||
- Contains most of the settings within Terminal Settings Model (global, profile, font, appearance)
|
||||
- To add a new setting to any one of those classes, simply add it to the respective list below, following the macro format
|
||||
- Also defines SettingKey enums for generic setting access
|
||||
|
||||
Author(s):
|
||||
- Pankaj Bhojwani - October 2021
|
||||
@@ -62,13 +63,11 @@ Author(s):
|
||||
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
|
||||
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
|
||||
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
|
||||
X(bool, ShowAdminShield, "showAdminShield", true) \
|
||||
X(bool, TrimPaste, "trimPaste", true) \
|
||||
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
|
||||
X(bool, EnableShellCompletionMenu, "experimental.enableShellCompletionMenu", false) \
|
||||
X(bool, EnableUnfocusedAcrylic, "compatibility.enableUnfocusedAcrylic", true) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
|
||||
X(bool, AllowHeadless, "compatibility.allowHeadless", false) \
|
||||
X(hstring, SearchWebDefaultQueryUrl, "searchWebDefaultQueryUrl", L"https://www.bing.com/search?q=%22%s%22") \
|
||||
X(bool, ShowTabsFullscreen, "showTabsFullscreen", false)
|
||||
@@ -88,15 +87,12 @@ Author(s):
|
||||
X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \
|
||||
X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \
|
||||
X(hstring, StartingDirectory, "startingDirectory") \
|
||||
X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \
|
||||
X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \
|
||||
X(guid, ConnectionType, "connectionType") \
|
||||
X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \
|
||||
X(hstring, TabTitle, "tabTitle") \
|
||||
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \
|
||||
X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \
|
||||
X(bool, RightClickContextMenu, "rightClickContextMenu", false) \
|
||||
X(Windows::Foundation::Collections::IVector<IMediaResource>, BellSound, "bellSound", nullptr) \
|
||||
X(bool, Elevate, "elevate", false) \
|
||||
X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \
|
||||
X(bool, ShowMarks, "showMarksOnScrollbar", false) \
|
||||
@@ -124,8 +120,6 @@ Author(s):
|
||||
X(hstring, FontFace, "face", DEFAULT_FONT_FACE) \
|
||||
X(float, FontSize, "size", DEFAULT_FONT_SIZE) \
|
||||
X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \
|
||||
X(IFontAxesMap, FontAxes, "axes") \
|
||||
X(IFontFeatureMap, FontFeatures, "features") \
|
||||
X(bool, EnableBuiltinGlyphs, "builtinGlyphs", true) \
|
||||
X(bool, EnableColorGlyphs, "colorGlyphs", true) \
|
||||
X(winrt::hstring, CellWidth, "cellWidth") \
|
||||
@@ -137,10 +131,7 @@ Author(s):
|
||||
X(float, BackgroundImageOpacity, "backgroundImageOpacity", 1.0f) \
|
||||
X(winrt::Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, "backgroundImageStretchMode", winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill) \
|
||||
X(bool, RetroTerminalEffect, "experimental.retroTerminalEffect", false) \
|
||||
X(IMediaResource, PixelShaderPath, "experimental.pixelShaderPath", implementation::MediaResource::Empty()) \
|
||||
X(IMediaResource, PixelShaderImagePath, "experimental.pixelShaderImagePath", implementation::MediaResource::Empty()) \
|
||||
X(ConvergedAlignment, BackgroundImageAlignment, "backgroundImageAlignment", ConvergedAlignment::Horizontal_Center | ConvergedAlignment::Vertical_Center) \
|
||||
X(IMediaResource, BackgroundImagePath, "backgroundImage", implementation::MediaResource::Empty()) \
|
||||
X(Model::IntenseStyle, IntenseTextStyle, "intenseTextStyle", Model::IntenseStyle::Bright) \
|
||||
X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Automatic) \
|
||||
X(bool, UseAcrylic, "useAcrylic", false)
|
||||
@@ -174,3 +165,176 @@ Author(s):
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedBackground, "unfocusedBackground", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::IconStyle, IconStyle, "iconStyle", winrt::Microsoft::Terminal::Settings::Model::IconStyle::Default) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility, ShowCloseButton, "showCloseButton", winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility::Always)
|
||||
|
||||
// SettingKey enums: provide a generic way to reference settings by key.
|
||||
// Generated from the MTSM macros above. Also includes special-cased settings
|
||||
// that are not part of the macro lists (prefixed with _ to avoid name collisions).
|
||||
// SETTINGS_SIZE is a sentinel value that must remain last in each enum.
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
#define _MTSM_ENUM_VALUE(type, name, jsonKey, ...) name,
|
||||
|
||||
enum class ProfileSettingKey : int
|
||||
{
|
||||
MTSM_PROFILE_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased profile settings (not in MTSM_PROFILE_SETTINGS due to
|
||||
// custom JSON parsing, but included here for completeness)
|
||||
_Name,
|
||||
_Guid,
|
||||
_Source,
|
||||
_Hidden,
|
||||
_Padding,
|
||||
_TabColor,
|
||||
// Complex/mutable settings with backing fields
|
||||
_Icon,
|
||||
_EnvironmentVariables,
|
||||
_BellSound,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class GlobalSettingKey : int
|
||||
{
|
||||
MTSM_GLOBAL_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased global settings
|
||||
_UnparsedDefaultProfile,
|
||||
// Complex/mutable settings with backing fields
|
||||
_DisabledProfileSources,
|
||||
_NewTabMenu,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class FontSettingKey : int
|
||||
{
|
||||
MTSM_FONT_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Complex/mutable settings with backing fields
|
||||
_FontAxes,
|
||||
_FontFeatures,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class AppearanceSettingKey : int
|
||||
{
|
||||
MTSM_APPEARANCE_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased appearance settings
|
||||
_Foreground,
|
||||
_Background,
|
||||
_SelectionBackground,
|
||||
_CursorColor,
|
||||
_Opacity,
|
||||
_DarkColorSchemeName,
|
||||
_LightColorSchemeName,
|
||||
// Complex/mutable settings with backing fields
|
||||
_PixelShaderPath,
|
||||
_PixelShaderImagePath,
|
||||
_BackgroundImagePath,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
#undef _MTSM_ENUM_VALUE
|
||||
|
||||
// JSON key lookup: returns the JSON key string for a given SettingKey.
|
||||
// Generated from the same macros to maintain a single source of truth.
|
||||
constexpr std::string_view JsonKeyForSetting(ProfileSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_PROFILE_SETTINGS(_MTSM_KEY_CASE)
|
||||
case ProfileSettingKey::_Name:
|
||||
return "name";
|
||||
case ProfileSettingKey::_Guid:
|
||||
return "guid";
|
||||
case ProfileSettingKey::_Source:
|
||||
return "source";
|
||||
case ProfileSettingKey::_Hidden:
|
||||
return "hidden";
|
||||
case ProfileSettingKey::_Padding:
|
||||
return "padding";
|
||||
case ProfileSettingKey::_TabColor:
|
||||
return "tabColor";
|
||||
case ProfileSettingKey::_Icon:
|
||||
return "icon";
|
||||
case ProfileSettingKey::_EnvironmentVariables:
|
||||
return "environment";
|
||||
case ProfileSettingKey::_BellSound:
|
||||
return "bellSound";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(GlobalSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_GLOBAL_SETTINGS(_MTSM_KEY_CASE)
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
return "defaultProfile";
|
||||
case GlobalSettingKey::_DisabledProfileSources:
|
||||
return "disabledProfileSources";
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
return "newTabMenu";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(FontSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_FONT_SETTINGS(_MTSM_KEY_CASE)
|
||||
case FontSettingKey::_FontAxes:
|
||||
return "axes";
|
||||
case FontSettingKey::_FontFeatures:
|
||||
return "features";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(AppearanceSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_APPEARANCE_SETTINGS(_MTSM_KEY_CASE)
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
return "foreground";
|
||||
case AppearanceSettingKey::_Background:
|
||||
return "background";
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
return "selectionBackground";
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
return "cursorColor";
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
return "opacity";
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
return "colorScheme";
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
return "colorScheme";
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
return "experimental.pixelShaderPath";
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
return "experimental.pixelShaderImagePath";
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
return "backgroundImage";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,15 +115,16 @@ winrt::com_ptr<Profile> Profile::CopySettings() const
|
||||
profile->_Origin = _Origin;
|
||||
profile->_FontInfo = *fontInfo;
|
||||
profile->_DefaultAppearance = *defaultAppearance;
|
||||
profile->_json = _json;
|
||||
|
||||
#define PROFILE_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
profile->_##name = _##name;
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_COPY)
|
||||
#undef PROFILE_SETTINGS_COPY
|
||||
// MTSM settings are JSON-backed — they live in _json, which is already deep-copied above.
|
||||
// No per-setting copy needed.
|
||||
|
||||
// Complex/mutable settings with backing fields need explicit copy
|
||||
profile->_Icon = _Icon;
|
||||
profile->_EnvironmentVariables = _EnvironmentVariables;
|
||||
if (_BellSound)
|
||||
{
|
||||
// BellSound is an IVector<>, so we need to make a new vector pointing at the same objects
|
||||
profile->_BellSound = winrt::single_threaded_vector(wil::to_vector(*_BellSound));
|
||||
}
|
||||
|
||||
@@ -169,6 +170,13 @@ winrt::com_ptr<winrt::Microsoft::Terminal::Settings::Model::implementation::Prof
|
||||
// <none>
|
||||
void Profile::LayerJson(const Json::Value& json)
|
||||
{
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
// This preserves keys from earlier LayerJson calls that aren't in the new JSON.
|
||||
for (const auto& key : json.getMemberNames())
|
||||
{
|
||||
_json[key] = json[key];
|
||||
}
|
||||
|
||||
// Appearance Settings
|
||||
auto defaultAppearanceImpl = winrt::get_self<implementation::AppearanceConfig>(_DefaultAppearance);
|
||||
defaultAppearanceImpl->LayerJson(json);
|
||||
@@ -196,18 +204,38 @@ void Profile::LayerJson(const Json::Value& json)
|
||||
_logSettingIfSet(TabColorKey, _TabColor.has_value());
|
||||
|
||||
// Try to load some legacy keys, to migrate them.
|
||||
// Done _before_ the MTSM_PROFILE_SETTINGS, which have the updated keys.
|
||||
JsonUtils::GetValueForKey(json, LegacyShowMarksKey, _ShowMarks);
|
||||
JsonUtils::GetValueForKey(json, LegacyAutoMarkPromptsKey, _AutoMarkPrompts);
|
||||
JsonUtils::GetValueForKey(json, LegacyRightClickContextMenuKey, _RightClickContextMenu);
|
||||
// Normalize legacy keys into canonical _json keys so JSON-backed getters find them.
|
||||
// Done _before_ the MTSM_PROFILE_SETTINGS logging, which have the updated keys.
|
||||
if (json.isMember(JsonKey(LegacyShowMarksKey)))
|
||||
{
|
||||
_json["showMarksOnScrollbar"] = json[JsonKey(LegacyShowMarksKey)];
|
||||
}
|
||||
if (json.isMember(JsonKey(LegacyAutoMarkPromptsKey)))
|
||||
{
|
||||
_json["autoMarkPrompts"] = json[JsonKey(LegacyAutoMarkPromptsKey)];
|
||||
}
|
||||
if (json.isMember(JsonKey(LegacyRightClickContextMenuKey)))
|
||||
{
|
||||
_json["rightClickContextMenu"] = json[JsonKey(LegacyRightClickContextMenuKey)];
|
||||
}
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define PROFILE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_LAYER_JSON)
|
||||
#undef PROFILE_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings that have backing fields (not JSON-backed)
|
||||
JsonUtils::GetValueForKey(json, "icon", _Icon);
|
||||
_logSettingIfSet("icon", _Icon.has_value());
|
||||
JsonUtils::GetValueForKey(json, "environment", _EnvironmentVariables);
|
||||
_logSettingIfSet("environment", _EnvironmentVariables.has_value());
|
||||
JsonUtils::GetValueForKey(json, "bellSound", _BellSound);
|
||||
_logSettingIfSet("bellSound", _BellSound.has_value());
|
||||
|
||||
if (json.isMember(JsonKey(UnfocusedAppearanceKey)))
|
||||
{
|
||||
auto unfocusedAppearance{ winrt::make_self<implementation::AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
@@ -345,12 +373,21 @@ Json::Value Profile::ToJson() const
|
||||
|
||||
JsonUtils::SetValueForKey(json, TabColorKey, _TabColor);
|
||||
|
||||
#define PROFILE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
// MTSM profile settings: copy from _json (the source of truth)
|
||||
#define PROFILE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
if (_json.isMember(jsonKey) && !_json[jsonKey].isNull()) \
|
||||
{ \
|
||||
json[JsonKey(jsonKey)] = _json[JsonKey(jsonKey)]; \
|
||||
}
|
||||
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_TO_JSON)
|
||||
#undef PROFILE_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings with backing fields
|
||||
JsonUtils::SetValueForKey(json, "icon", _Icon);
|
||||
JsonUtils::SetValueForKey(json, "environment", _EnvironmentVariables);
|
||||
JsonUtils::SetValueForKey(json, "bellSound", _BellSound);
|
||||
|
||||
if (auto fontJSON = winrt::get_self<FontConfig>(_FontInfo)->ToJson(); !fontJSON.empty())
|
||||
{
|
||||
json[JsonKey(FontInfoKey)] = std::move(fontJSON);
|
||||
@@ -364,6 +401,94 @@ Json::Value Profile::ToJson() const
|
||||
return json;
|
||||
}
|
||||
|
||||
bool Profile::HasSetting(ProfileSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _PROFILE_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_PROFILE_SETTINGS(_PROFILE_HAS_SETTING)
|
||||
#undef _PROFILE_HAS_SETTING
|
||||
case ProfileSettingKey::_Name:
|
||||
return HasName();
|
||||
case ProfileSettingKey::_Guid:
|
||||
return HasGuid();
|
||||
case ProfileSettingKey::_Source:
|
||||
return HasSource();
|
||||
case ProfileSettingKey::_Hidden:
|
||||
return HasHidden();
|
||||
case ProfileSettingKey::_Padding:
|
||||
return HasPadding();
|
||||
case ProfileSettingKey::_TabColor:
|
||||
return HasTabColor();
|
||||
case ProfileSettingKey::_Icon:
|
||||
return HasIcon();
|
||||
case ProfileSettingKey::_EnvironmentVariables:
|
||||
return HasEnvironmentVariables();
|
||||
case ProfileSettingKey::_BellSound:
|
||||
return HasBellSound();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::ClearSetting(ProfileSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _PROFILE_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_PROFILE_SETTINGS(_PROFILE_CLEAR_SETTING)
|
||||
#undef _PROFILE_CLEAR_SETTING
|
||||
case ProfileSettingKey::_Name:
|
||||
ClearName();
|
||||
break;
|
||||
case ProfileSettingKey::_Guid:
|
||||
ClearGuid();
|
||||
break;
|
||||
case ProfileSettingKey::_Source:
|
||||
ClearSource();
|
||||
break;
|
||||
case ProfileSettingKey::_Hidden:
|
||||
ClearHidden();
|
||||
break;
|
||||
case ProfileSettingKey::_Padding:
|
||||
ClearPadding();
|
||||
break;
|
||||
case ProfileSettingKey::_TabColor:
|
||||
ClearTabColor();
|
||||
break;
|
||||
case ProfileSettingKey::_Icon:
|
||||
ClearIcon();
|
||||
break;
|
||||
case ProfileSettingKey::_EnvironmentVariables:
|
||||
ClearEnvironmentVariables();
|
||||
break;
|
||||
case ProfileSettingKey::_BellSound:
|
||||
ClearBellSound();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ProfileSettingKey> Profile::CurrentSettings() const
|
||||
{
|
||||
std::vector<ProfileSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(ProfileSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<ProfileSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Given a commandLine like the following:
|
||||
// * "C:\WINDOWS\System32\cmd.exe"
|
||||
// * "pwsh -WorkingDirectory ~"
|
||||
@@ -507,8 +632,8 @@ void Profile::_logSettingIfSet(const std::string_view& setting, const bool isSet
|
||||
const bool isACS = _Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Azure Cloud Shell");
|
||||
const bool isWTDynamicProfile = _Source.has_value() && til::starts_with(*_Source, L"Windows.Terminal");
|
||||
const bool settingHiddenToFalse = til::equals_insensitive_ascii(setting, HiddenKey) && _Hidden.has_value() && _Hidden == false;
|
||||
const bool settingCommandlineToWinPow = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe");
|
||||
const bool settingCommandlineToCmd = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\cmd.exe");
|
||||
const bool settingCommandlineToWinPow = til::equals_insensitive_ascii(setting, "commandline") && HasCommandline() && til::equals_insensitive_ascii(Commandline(), L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe");
|
||||
const bool settingCommandlineToCmd = til::equals_insensitive_ascii(setting, "commandline") && HasCommandline() && til::equals_insensitive_ascii(Commandline(), L"%SystemRoot%\\System32\\cmd.exe");
|
||||
// clang-format off
|
||||
if (!(isWinPow && (settingHiddenToFalse || settingCommandlineToWinPow))
|
||||
&& !(isCmd && (settingHiddenToFalse || settingCommandlineToCmd))
|
||||
@@ -541,7 +666,7 @@ void Profile::ResolveMediaResources(const Model::MediaResourceResolver& resolver
|
||||
auto newIcon{ MediaResource::FromString(icon->Path()) };
|
||||
const std::wstring cmdline{ NormalizeCommandLine(iconSource->Commandline().c_str()) };
|
||||
newIcon.Resolve(cmdline.c_str() /* c_str: give hstring a chance to find the null terminator */);
|
||||
iconSource->_Icon = std::move(newIcon);
|
||||
iconSource->Icon(std::move(newIcon));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ Author(s):
|
||||
#include "MTSMSettings.h"
|
||||
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include <DefaultSettings.h>
|
||||
#include "MediaResourceSupport.h"
|
||||
#include "AppearanceConfig.h"
|
||||
@@ -98,6 +99,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void LayerJson(const Json::Value& json);
|
||||
Json::Value ToJson() const;
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(ProfileSettingKey key) const;
|
||||
void ClearSetting(ProfileSettingKey key);
|
||||
std::vector<ProfileSettingKey> CurrentSettings() const;
|
||||
|
||||
hstring EvaluatedStartingDirectory() const;
|
||||
|
||||
Model::IAppearanceConfig DefaultAppearance();
|
||||
@@ -137,14 +143,23 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
public:
|
||||
#define PROFILE_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING_WITH_LOGGING(Model::Profile, type, name, jsonKey, ##__VA_ARGS__)
|
||||
INHERITABLE_JSON_SETTING_WITH_LOGGING(Model::Profile, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_INITIALIZE)
|
||||
#undef PROFILE_SETTINGS_INITIALIZE
|
||||
|
||||
// Complex/mutable settings that need backing fields (not JSON-backed)
|
||||
INHERITABLE_SETTING(Model::Profile, IMediaResource, Icon, implementation::MediaResource::FromString(L"\uE756"));
|
||||
INHERITABLE_SETTING(Model::Profile, IEnvironmentVariableMap, EnvironmentVariables, nullptr);
|
||||
INHERITABLE_SETTING(Model::Profile, Windows::Foundation::Collections::IVector<IMediaResource>, BellSound, nullptr);
|
||||
|
||||
private:
|
||||
Model::IAppearanceConfig _DefaultAppearance{ winrt::make<AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
Model::FontConfig _FontInfo{ winrt::make<FontConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
|
||||
// Raw JSON for this layer. Populated by LayerJson(), will become the
|
||||
// source of truth for settings once the JSON-backed refactor is complete.
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
static std::wstring EvaluateStartingDirectory(const std::wstring& directory);
|
||||
|
||||
@@ -31,6 +31,11 @@ namespace SettingsModelUnitTests
|
||||
TEST_METHOD(TestGenGuidsForProfiles);
|
||||
TEST_METHOD(TestCorrectOldDefaultShellPaths);
|
||||
TEST_METHOD(ProfileDefaultsProhibitedSettings);
|
||||
|
||||
TEST_METHOD(JsonSyncOnSetAndClear);
|
||||
TEST_METHOD(SettingKeyEnumAndJsonKeyLookup);
|
||||
TEST_METHOD(GenericHasAndClearMatchTypedAPIs);
|
||||
TEST_METHOD(CurrentSettingsReturnsCorrectKeys);
|
||||
};
|
||||
|
||||
void ProfileTests::ProfileGeneratesGuid()
|
||||
@@ -532,4 +537,168 @@ namespace SettingsModelUnitTests
|
||||
VERIFY_ARE_NOT_EQUAL(L"Default Profile Source", allProfiles.GetAt(2).Source());
|
||||
VERIFY_ARE_NOT_EQUAL(L"foo.exe", allProfiles.GetAt(2).Commandline());
|
||||
}
|
||||
|
||||
void ProfileTests::JsonSyncOnSetAndClear()
|
||||
{
|
||||
// Verify that setting a value via the typed setter updates the internal
|
||||
// _json, and clearing it removes the key from _json.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// Verify initial value
|
||||
VERIFY_ARE_EQUAL(1000, profile.HistorySize());
|
||||
VERIFY_IS_TRUE(profile.HasHistorySize());
|
||||
|
||||
// Modify setting; _json should be updated
|
||||
profile.HistorySize(5000);
|
||||
VERIFY_ARE_EQUAL(5000, profile.HistorySize());
|
||||
|
||||
// Verify ToJson reflects the change
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_ARE_EQUAL(5000, json["historySize"].asInt());
|
||||
|
||||
// Clear setting; should fall back to default
|
||||
profile.ClearHistorySize();
|
||||
VERIFY_IS_FALSE(profile.HasHistorySize());
|
||||
// Should now inherit or use default (9001 is the DEFAULT_HISTORY_SIZE)
|
||||
VERIFY_ARE_EQUAL(DEFAULT_HISTORY_SIZE, profile.HistorySize());
|
||||
|
||||
// ToJson should no longer have historySize
|
||||
const auto json2 = profileImpl->ToJson();
|
||||
VERIFY_IS_FALSE(json2.isMember("historySize"));
|
||||
}
|
||||
|
||||
void ProfileTests::SettingKeyEnumAndJsonKeyLookup()
|
||||
{
|
||||
// Verify that the SettingKey enums map to correct JSON keys.
|
||||
using namespace implementation;
|
||||
|
||||
// Spot-check a few profile setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "historySize" }, JsonKeyForSetting(ProfileSettingKey::HistorySize));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "snapOnInput" }, JsonKeyForSetting(ProfileSettingKey::SnapOnInput));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "commandline" }, JsonKeyForSetting(ProfileSettingKey::Commandline));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "tabTitle" }, JsonKeyForSetting(ProfileSettingKey::TabTitle));
|
||||
|
||||
// Special-cased settings
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "name" }, JsonKeyForSetting(ProfileSettingKey::_Name));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "guid" }, JsonKeyForSetting(ProfileSettingKey::_Guid));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "hidden" }, JsonKeyForSetting(ProfileSettingKey::_Hidden));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "padding" }, JsonKeyForSetting(ProfileSettingKey::_Padding));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "tabColor" }, JsonKeyForSetting(ProfileSettingKey::_TabColor));
|
||||
|
||||
// Global setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "initialRows" }, JsonKeyForSetting(GlobalSettingKey::InitialRows));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "alwaysOnTop" }, JsonKeyForSetting(GlobalSettingKey::AlwaysOnTop));
|
||||
|
||||
// Font setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "face" }, JsonKeyForSetting(FontSettingKey::FontFace));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "size" }, JsonKeyForSetting(FontSettingKey::FontSize));
|
||||
|
||||
// SETTINGS_SIZE should be a valid (but large) enum value
|
||||
VERIFY_IS_TRUE(static_cast<int>(ProfileSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(GlobalSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(FontSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(AppearanceSettingKey::SETTINGS_SIZE) > 0);
|
||||
}
|
||||
|
||||
void ProfileTests::GenericHasAndClearMatchTypedAPIs()
|
||||
{
|
||||
// Verify that HasSetting(key) and ClearSetting(key) match the
|
||||
// typed HasXxx() and ClearXxx() methods.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000,
|
||||
"snapOnInput": false
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// HasSetting should match HasXxx for set values
|
||||
VERIFY_ARE_EQUAL(profile.HasHistorySize(), profileImpl->HasSetting(implementation::ProfileSettingKey::HistorySize));
|
||||
VERIFY_ARE_EQUAL(profile.HasSnapOnInput(), profileImpl->HasSetting(implementation::ProfileSettingKey::SnapOnInput));
|
||||
|
||||
// HasSetting should match HasXxx for unset values
|
||||
VERIFY_ARE_EQUAL(profile.HasTabTitle(), profileImpl->HasSetting(implementation::ProfileSettingKey::TabTitle));
|
||||
VERIFY_IS_FALSE(profileImpl->HasSetting(implementation::ProfileSettingKey::TabTitle));
|
||||
|
||||
// ClearSetting should behave like ClearXxx
|
||||
profileImpl->ClearSetting(implementation::ProfileSettingKey::HistorySize);
|
||||
VERIFY_IS_FALSE(profile.HasHistorySize());
|
||||
VERIFY_IS_FALSE(profileImpl->HasSetting(implementation::ProfileSettingKey::HistorySize));
|
||||
}
|
||||
|
||||
void ProfileTests::CurrentSettingsReturnsCorrectKeys()
|
||||
{
|
||||
// Verify that CurrentSettings() returns the keys that are explicitly
|
||||
// set at the current layer.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000,
|
||||
"snapOnInput": false,
|
||||
"tabTitle": "MyTab"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
const auto currentKeys = profileImpl->CurrentSettings();
|
||||
|
||||
// historySize, snapOnInput, and tabTitle should be in the list
|
||||
auto hasHistorySize = false;
|
||||
auto hasSnapOnInput = false;
|
||||
auto hasTabTitle = false;
|
||||
for (const auto& key : currentKeys)
|
||||
{
|
||||
if (key == implementation::ProfileSettingKey::HistorySize)
|
||||
hasHistorySize = true;
|
||||
if (key == implementation::ProfileSettingKey::SnapOnInput)
|
||||
hasSnapOnInput = true;
|
||||
if (key == implementation::ProfileSettingKey::TabTitle)
|
||||
hasTabTitle = true;
|
||||
}
|
||||
VERIFY_IS_TRUE(hasHistorySize, L"historySize should be in CurrentSettings");
|
||||
VERIFY_IS_TRUE(hasSnapOnInput, L"snapOnInput should be in CurrentSettings");
|
||||
VERIFY_IS_TRUE(hasTabTitle, L"tabTitle should be in CurrentSettings");
|
||||
|
||||
// Clear one and verify it's removed
|
||||
profileImpl->ClearSetting(implementation::ProfileSettingKey::TabTitle);
|
||||
const auto updatedKeys = profileImpl->CurrentSettings();
|
||||
auto hasTabTitleAfterClear = false;
|
||||
for (const auto& key : updatedKeys)
|
||||
{
|
||||
if (key == implementation::ProfileSettingKey::TabTitle)
|
||||
hasTabTitleAfterClear = true;
|
||||
}
|
||||
VERIFY_IS_FALSE(hasTabTitleAfterClear, L"tabTitle should NOT be in CurrentSettings after clear");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1061,7 +1061,38 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
||||
ROW* rowBackup = nullptr;
|
||||
if (row == compositionRow)
|
||||
{
|
||||
rowBackup = _PaintBufferOutputComposition(buffer, r, activeComposition);
|
||||
auto& scratch = buffer.GetScratchpadRow();
|
||||
scratch.CopyFrom(r);
|
||||
rowBackup = &scratch;
|
||||
|
||||
std::wstring_view text{ activeComposition.text };
|
||||
RowWriteState state{
|
||||
.columnLimit = r.GetReadableColumnCount(),
|
||||
.columnEnd = _compositionCache->absoluteOrigin.x,
|
||||
};
|
||||
|
||||
size_t off = 0;
|
||||
for (const auto& range : activeComposition.attributes)
|
||||
{
|
||||
const auto len = range.len;
|
||||
auto attr = range.attr;
|
||||
|
||||
// Use the color at the cursor if TSF didn't specify any explicit color.
|
||||
if (attr.GetBackground().IsDefault())
|
||||
{
|
||||
attr.SetBackground(_compositionCache->baseAttribute.GetBackground());
|
||||
}
|
||||
if (attr.GetForeground().IsDefault())
|
||||
{
|
||||
attr.SetForeground(_compositionCache->baseAttribute.GetForeground());
|
||||
}
|
||||
|
||||
state.text = text.substr(off, len);
|
||||
state.columnBegin = state.columnEnd;
|
||||
const_cast<ROW&>(r).ReplaceText(state);
|
||||
const_cast<ROW&>(r).ReplaceAttributes(state.columnBegin, state.columnEnd, attr);
|
||||
off += len;
|
||||
}
|
||||
}
|
||||
const auto restore = wil::scope_exit([&] {
|
||||
if (rowBackup)
|
||||
@@ -1101,107 +1132,6 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
||||
}
|
||||
}
|
||||
|
||||
ROW* Renderer::_PaintBufferOutputComposition(TextBuffer& buffer, const ROW& r, const Composition& activeComposition)
|
||||
{
|
||||
auto& scratch = buffer.GetScratchpadRow();
|
||||
scratch.CopyFrom(r);
|
||||
|
||||
// *Overwrite* the original text with the active composition...
|
||||
til::CoordType compositionEnd = 0;
|
||||
{
|
||||
std::wstring_view text{ activeComposition.text };
|
||||
RowWriteState state{
|
||||
.columnLimit = r.GetReadableColumnCount(),
|
||||
.columnEnd = _compositionCache->absoluteOrigin.x,
|
||||
};
|
||||
|
||||
size_t off = 0;
|
||||
for (const auto& range : activeComposition.attributes)
|
||||
{
|
||||
const auto len = range.len;
|
||||
auto attr = range.attr;
|
||||
|
||||
// Use the color at the cursor if TSF didn't specify any explicit color.
|
||||
if (attr.GetBackground().IsDefault())
|
||||
{
|
||||
attr.SetBackground(_compositionCache->baseAttribute.GetBackground());
|
||||
}
|
||||
if (attr.GetForeground().IsDefault())
|
||||
{
|
||||
attr.SetForeground(_compositionCache->baseAttribute.GetForeground());
|
||||
}
|
||||
|
||||
state.text = text.substr(off, len);
|
||||
state.columnBegin = state.columnEnd;
|
||||
const_cast<ROW&>(r).ReplaceText(state);
|
||||
const_cast<ROW&>(r).ReplaceAttributes(state.columnBegin, state.columnEnd, attr);
|
||||
off += len;
|
||||
}
|
||||
|
||||
compositionEnd = state.columnEnd;
|
||||
}
|
||||
|
||||
// The text we've overwritten may have been crucial to the user,
|
||||
// so copy it back by absorbing available whitespace to the right
|
||||
// and re-inserting the non-whitespace characters instead.
|
||||
const auto compositionWidth = compositionEnd - _compositionCache->absoluteOrigin.x;
|
||||
const auto colLimit = r.GetReadableColumnCount();
|
||||
if (compositionWidth > 0 && compositionEnd < colLimit)
|
||||
{
|
||||
const auto text = scratch.GetText();
|
||||
auto srcCol = _compositionCache->absoluteOrigin.x;
|
||||
auto dstCol = compositionEnd;
|
||||
auto remaining = compositionWidth;
|
||||
size_t i = scratch.GetCharOffset(srcCol);
|
||||
|
||||
while (i < text.size() && dstCol < colLimit)
|
||||
{
|
||||
// Treat whitespace we encounter as a credit towards our composition width.
|
||||
// This loop essentially absorbs the whitespace.
|
||||
while (i < text.size() && til::at(text, i) == L' ' && remaining > 0)
|
||||
{
|
||||
remaining--;
|
||||
srcCol++;
|
||||
i++;
|
||||
}
|
||||
if (remaining <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the end of the non-whitespace span: Our span of text to insert.
|
||||
auto spanEnd = i;
|
||||
while (spanEnd < text.size() && til::at(text, spanEnd) != L' ')
|
||||
{
|
||||
spanEnd++;
|
||||
}
|
||||
|
||||
// Copy the non-whitespace segment from the original text (scratch) back in.
|
||||
RowCopyTextFromState state{
|
||||
.source = scratch,
|
||||
.columnBegin = dstCol,
|
||||
.columnLimit = colLimit,
|
||||
.sourceColumnBegin = srcCol,
|
||||
.sourceColumnLimit = scratch.GetLeadingColumnAtCharOffset(spanEnd),
|
||||
};
|
||||
const_cast<ROW&>(r).CopyTextFrom(state);
|
||||
|
||||
const auto srcBeg = gsl::narrow_cast<uint16_t>(srcCol);
|
||||
const auto srcEnd = gsl::narrow_cast<uint16_t>(state.sourceColumnEnd);
|
||||
const auto attr = scratch.Attributes().slice(srcBeg, srcEnd);
|
||||
const auto dstBeg = gsl::narrow_cast<uint16_t>(dstCol);
|
||||
const auto dstEnd = gsl::narrow_cast<uint16_t>(dstCol + attr.size());
|
||||
const_cast<ROW&>(r).Attributes().replace(dstBeg, dstEnd, attr);
|
||||
|
||||
dstCol = state.columnEnd;
|
||||
srcCol = state.sourceColumnEnd;
|
||||
i = spanEnd;
|
||||
}
|
||||
}
|
||||
|
||||
return &scratch;
|
||||
}
|
||||
|
||||
static bool _IsAllSpaces(const std::wstring_view v)
|
||||
{
|
||||
// first non-space char is not found (is npos)
|
||||
|
||||
@@ -121,7 +121,6 @@ namespace Microsoft::Console::Render
|
||||
void _scheduleRenditionBlink();
|
||||
[[nodiscard]] HRESULT _PaintBackground(_In_ IRenderEngine* const pEngine);
|
||||
void _PaintBufferOutput(_In_ IRenderEngine* const pEngine);
|
||||
ROW* _PaintBufferOutputComposition(TextBuffer& buffer, const ROW& r, const Composition& activeComposition);
|
||||
void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, TextBufferCellIterator it, const til::point target);
|
||||
void _PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngine, const TextAttribute textAttribute, const size_t cchLine, const til::point coordTarget);
|
||||
bool _isHoveredHyperlink(const TextAttribute& textAttribute) const noexcept;
|
||||
|
||||
Reference in New Issue
Block a user