New Tab Menu Customization (#13763)
Implements an initial version of #1571 as it has been specified, the only big thing missing now is the possibility to add actions, which depends on #6899. Further upcoming spec tracked in #12584 Implemented according to [instructions by @zadjii-msft]. Mostly relatively straightforward, but some notable details: - In accordance with the spec, the counting/indexing of profiles is based on their index in the json (so the index of the profile, not of the entry in the menu). - Resolving a profile name to an actual profile is done in a similar fashion as how currently the `DefaultProfile` field is populated: the `CascadiaSettings` constructor now has an extra `_resolve` function that will iterate over all entries and resolve the names to instances; this same function will compute all profile sets (the set of all profiles from source "x", and the set of all remaining profiles) - ~Fun~ fact: I spent two whole afternoons and evenings trying to add 2 classes (which turned out to be a trivial `.vcxproj` error), and then managed to finish the entire rest of it in another afternoon and evening... ## Validation Steps Performed A lot of manual testing; as mentioned above I was not able to run any tests so I could not add them for now. However, the logic is not too tricky so it should be relatively safe. Closes #1571 [instructions by @zadjii-msft]: https://github.com/microsoft/terminal/issues/1571#issuecomment-1184851000
This commit is contained in:
parent
a5f9c85c39
commit
2668273616
|
@ -81,6 +81,7 @@ Thysell
|
|||
Walisch
|
||||
WDX
|
||||
Wellons
|
||||
Westerman
|
||||
Wirt
|
||||
Wojciech
|
||||
zadjii
|
||||
|
|
|
@ -251,6 +251,7 @@ conattrs
|
|||
conbufferout
|
||||
concfg
|
||||
conclnt
|
||||
concretizations
|
||||
conddkrefs
|
||||
condrv
|
||||
conechokey
|
||||
|
@ -2112,6 +2113,7 @@ webpage
|
|||
websites
|
||||
websockets
|
||||
wekyb
|
||||
WEOF
|
||||
wex
|
||||
wextest
|
||||
wextestclass
|
||||
|
|
|
@ -561,6 +561,160 @@
|
|||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NewTabMenuEntryType": {
|
||||
"enum": [
|
||||
"source",
|
||||
"profile",
|
||||
"folder",
|
||||
"separator",
|
||||
"remainingProfiles",
|
||||
"matchProfiles"
|
||||
]
|
||||
},
|
||||
"NewTabMenuEntry": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "The type of menu entry",
|
||||
"$ref": "#/$defs/NewTabMenuEntryType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FolderEntryInlining": {
|
||||
"enum": [
|
||||
"never",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"FolderEntry": {
|
||||
"description": "A folder entry in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "folder"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The name of the folder to show in the menu"
|
||||
},
|
||||
"icon": {
|
||||
"$ref": "#/$defs/Icon"
|
||||
},
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"description": "The entries to put inside this folder",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
}
|
||||
},
|
||||
"inline": {
|
||||
"description": "When set to auto and the folder only has a single entry, the entry will show directly and no folder will be rendered",
|
||||
"default": "never",
|
||||
"$ref": "#/$defs/FolderEntryInlining"
|
||||
},
|
||||
"allowEmpty": {
|
||||
"description": "Whether to render a folder without entries, or to hide it",
|
||||
"default": "false",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SeparatorEntry": {
|
||||
"description": "A separator in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "separator"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ProfileEntry": {
|
||||
"description": "A profile in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "profile"
|
||||
},
|
||||
"profile": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The name or GUID of the profile to show in this entry"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RemainingProfilesEntry": {
|
||||
"description": "The set of profiles that are not yet explicitly included in another entry, such as the profile or source entries. This entry can be used at most one time!",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "remainingProfiles"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"MatchProfilesEntry": {
|
||||
"description": "A set of profiles all matching the given name, source, or command line, to show in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "matchProfiles"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The name of the profiles to match"
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The source of the profiles to match"
|
||||
},
|
||||
"commandline": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The command line of the profiles to match"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SwitchToAdjacentTabArgs": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
@ -1568,6 +1722,29 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"NewTabMenu": {
|
||||
"description": "Defines the order and structure of the 'new tab' menu. It can consist of e.g. profiles, folders, and separators.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/$defs/FolderEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/SeparatorEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/ProfileEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/MatchProfilesEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/RemainingProfilesEntry"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
|
@ -1946,6 +2123,9 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"newTabMenu": {
|
||||
"$ref": "#/$defs/NewTabMenu"
|
||||
},
|
||||
"language": {
|
||||
"default": "",
|
||||
"description": "Sets an override for the app's preferred language, expressed as a BCP-47 language tag like en-US.",
|
||||
|
|
|
@ -52,6 +52,7 @@ static const std::array settingsLoadWarningsLabels {
|
|||
USES_RESOURCE(L"FailedToParseStartupActions"),
|
||||
USES_RESOURCE(L"FailedToParseSubCommands"),
|
||||
USES_RESOURCE(L"UnknownTheme"),
|
||||
USES_RESOURCE(L"DuplicateRemainingProfilesEntry"),
|
||||
};
|
||||
static const std::array settingsLoadErrorsLabels {
|
||||
USES_RESOURCE(L"NoProfilesText"),
|
||||
|
|
|
@ -232,19 +232,19 @@
|
|||
<value>Warnings were found while parsing your keybindings:</value>
|
||||
</data>
|
||||
<data name="TooManyKeysForChord" xml:space="preserve">
|
||||
<value>• Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
|
||||
<comment>{Locked="\"keys\"","•"} This glyph is a bullet, used in a bulleted list.</comment>
|
||||
<value>• Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
|
||||
<comment>{Locked="\"keys\"","•"} This glyph is a bullet, used in a bulleted list.</comment>
|
||||
</data>
|
||||
<data name="FailedToParseSubCommands" xml:space="preserve">
|
||||
<value>• Failed to parse all subcommands of nested command.</value>
|
||||
<value>• Failed to parse all subcommands of nested command.</value>
|
||||
</data>
|
||||
<data name="MissingRequiredParameter" xml:space="preserve">
|
||||
<value>• Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
|
||||
<comment>{Locked="•"} This glyph is a bullet, used in a bulleted list.</comment>
|
||||
<value>• Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
|
||||
<comment>{Locked="•"} This glyph is a bullet, used in a bulleted list.</comment>
|
||||
</data>
|
||||
<data name="UnknownTheme" xml:space="preserve">
|
||||
<value>• The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
|
||||
<comment>{Locked="•"} This glyph is a bullet, used in a bulleted list.</comment>
|
||||
<value>• The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
|
||||
<comment>{Locked="•"} This glyph is a bullet, used in a bulleted list.</comment>
|
||||
</data>
|
||||
<data name="LegacyGlobalsProperty" xml:space="preserve">
|
||||
<value>The "globals" property is deprecated - your settings might need updating. </value>
|
||||
|
@ -752,6 +752,10 @@
|
|||
<value>Suggestions found: {0}</value>
|
||||
<comment>{0} will be replaced with a number.</comment>
|
||||
</data>
|
||||
<data name="DuplicateRemainingProfilesEntry" xml:space="preserve">
|
||||
<value>The "newTabMenu" field contains more than one entry of type "remainingProfiles". Only the first one will be considered.</value>
|
||||
<comment>{Locked="newTabMenu"} {Locked="remainingProfiles"}</comment>
|
||||
</data>
|
||||
<data name="AboutToolTip" xml:space="preserve">
|
||||
<value>Open a dialog containing product information</value>
|
||||
</data>
|
||||
|
@ -788,4 +792,7 @@
|
|||
<data name="TabCloseToolTip" xml:space="preserve">
|
||||
<value>Close this tab</value>
|
||||
</data>
|
||||
<data name="NewTabMenuFolderEmpty" xml:space="preserve">
|
||||
<value>Empty...</value>
|
||||
</data>
|
||||
</root>
|
|
@ -824,79 +824,14 @@ namespace winrt::TerminalApp::implementation
|
|||
auto newTabFlyout = WUX::Controls::MenuFlyout{};
|
||||
newTabFlyout.Placement(WUX::Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedLeft);
|
||||
|
||||
auto actionMap = _settings.ActionMap();
|
||||
const auto defaultProfileGuid = _settings.GlobalSettings().DefaultProfile();
|
||||
// the number of profiles should not change in the loop for this to work
|
||||
const auto profileCount = gsl::narrow_cast<int>(_settings.ActiveProfiles().Size());
|
||||
for (auto profileIndex = 0; profileIndex < profileCount; profileIndex++)
|
||||
// Create profile entries from the NewTabMenu configuration using a
|
||||
// recursive helper function. This returns a std::vector of FlyoutItemBases,
|
||||
// that we then add to our Flyout.
|
||||
auto entries = _settings.GlobalSettings().NewTabMenu();
|
||||
auto items = _CreateNewTabFlyoutItems(entries);
|
||||
for (const auto& item : items)
|
||||
{
|
||||
const auto profile = _settings.ActiveProfiles().GetAt(profileIndex);
|
||||
auto profileMenuItem = WUX::Controls::MenuFlyoutItem{};
|
||||
|
||||
// Add the keyboard shortcuts based on the number of profiles defined
|
||||
// Look for a keychord that is bound to the equivalent
|
||||
// NewTab(ProfileIndex=N) action
|
||||
NewTerminalArgs newTerminalArgs{ profileIndex };
|
||||
NewTabArgs newTabArgs{ newTerminalArgs };
|
||||
auto profileKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) };
|
||||
|
||||
// make sure we find one to display
|
||||
if (profileKeyChord)
|
||||
{
|
||||
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
|
||||
}
|
||||
|
||||
auto profileName = profile.Name();
|
||||
profileMenuItem.Text(profileName);
|
||||
|
||||
// If there's an icon set for this profile, set it as the icon for
|
||||
// this flyout item.
|
||||
if (!profile.Icon().empty())
|
||||
{
|
||||
auto icon = IconPathConverter::IconWUX(profile.Icon());
|
||||
Automation::AutomationProperties::SetAccessibilityView(icon, Automation::Peers::AccessibilityView::Raw);
|
||||
profileMenuItem.Icon(icon);
|
||||
}
|
||||
|
||||
if (profile.Guid() == defaultProfileGuid)
|
||||
{
|
||||
// Contrast the default profile with others in font weight.
|
||||
profileMenuItem.FontWeight(FontWeights::Bold());
|
||||
}
|
||||
|
||||
auto newTabRun = WUX::Documents::Run();
|
||||
newTabRun.Text(RS_(L"NewTabRun/Text"));
|
||||
auto newPaneRun = WUX::Documents::Run();
|
||||
newPaneRun.Text(RS_(L"NewPaneRun/Text"));
|
||||
newPaneRun.FontStyle(FontStyle::Italic);
|
||||
auto newWindowRun = WUX::Documents::Run();
|
||||
newWindowRun.Text(RS_(L"NewWindowRun/Text"));
|
||||
newWindowRun.FontStyle(FontStyle::Italic);
|
||||
auto elevatedRun = WUX::Documents::Run();
|
||||
elevatedRun.Text(RS_(L"ElevatedRun/Text"));
|
||||
elevatedRun.FontStyle(FontStyle::Italic);
|
||||
|
||||
auto textBlock = WUX::Controls::TextBlock{};
|
||||
textBlock.Inlines().Append(newTabRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(newPaneRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(newWindowRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(elevatedRun);
|
||||
|
||||
auto toolTip = WUX::Controls::ToolTip{};
|
||||
toolTip.Content(textBlock);
|
||||
WUX::Controls::ToolTipService::SetToolTip(profileMenuItem, toolTip);
|
||||
|
||||
profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
NewTerminalArgs newTerminalArgs{ profileIndex };
|
||||
page->_OpenNewTerminalViaDropdown(newTerminalArgs);
|
||||
}
|
||||
});
|
||||
newTabFlyout.Items().Append(profileMenuItem);
|
||||
newTabFlyout.Items().Append(item);
|
||||
}
|
||||
|
||||
// add menu separator
|
||||
|
@ -932,6 +867,7 @@ namespace winrt::TerminalApp::implementation
|
|||
settingsItem.Click({ this, &TerminalPage::_SettingsButtonOnClick });
|
||||
newTabFlyout.Items().Append(settingsItem);
|
||||
|
||||
auto actionMap = _settings.ActionMap();
|
||||
const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) };
|
||||
if (settingsKeyChord)
|
||||
{
|
||||
|
@ -991,6 +927,215 @@ namespace winrt::TerminalApp::implementation
|
|||
_newTabButton.Flyout(newTabFlyout);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - For a given list of tab menu entries, this method will create the corresponding
|
||||
// list of flyout items. This is a recursive method that calls itself when it comes
|
||||
// across a folder entry.
|
||||
std::vector<WUX::Controls::MenuFlyoutItemBase> TerminalPage::_CreateNewTabFlyoutItems(IVector<NewTabMenuEntry> entries)
|
||||
{
|
||||
std::vector<WUX::Controls::MenuFlyoutItemBase> items;
|
||||
|
||||
if (entries == nullptr || entries.Size() == 0)
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
if (entry == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (entry.Type())
|
||||
{
|
||||
case NewTabMenuEntryType::Separator:
|
||||
{
|
||||
items.push_back(WUX::Controls::MenuFlyoutSeparator{});
|
||||
break;
|
||||
}
|
||||
// A folder has a custom name and icon, and has a number of entries that require
|
||||
// us to call this method recursively.
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
const auto folderEntry = entry.as<FolderEntry>();
|
||||
const auto folderEntries = folderEntry.Entries();
|
||||
|
||||
// If the folder is empty, we should skip the entry if AllowEmpty is false, or
|
||||
// when the folder should inline.
|
||||
// The IsEmpty check includes semantics for nested (empty) folders
|
||||
if (folderEntries.Size() == 0 && (!folderEntry.AllowEmpty() || folderEntry.Inlining() == FolderEntryInlining::Auto))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Recursively generate flyout items
|
||||
auto folderEntryItems = _CreateNewTabFlyoutItems(folderEntries);
|
||||
|
||||
// If the folder should auto-inline and there is only one item, do so.
|
||||
if (folderEntry.Inlining() == FolderEntryInlining::Auto && folderEntries.Size() == 1)
|
||||
{
|
||||
for (auto const& folderEntryItem : folderEntryItems)
|
||||
{
|
||||
items.push_back(folderEntryItem);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, create a flyout
|
||||
auto folderItem = WUX::Controls::MenuFlyoutSubItem{};
|
||||
folderItem.Text(folderEntry.Name());
|
||||
|
||||
auto icon = _CreateNewTabFlyoutIcon(folderEntry.Icon());
|
||||
folderItem.Icon(icon);
|
||||
|
||||
for (const auto& folderEntryItem : folderEntryItems)
|
||||
{
|
||||
folderItem.Items().Append(folderEntryItem);
|
||||
}
|
||||
|
||||
// If the folder is empty, and by now we know we set AllowEmpty to true,
|
||||
// create a placeholder item here
|
||||
if (folderEntries.Size() == 0)
|
||||
{
|
||||
auto placeholder = WUX::Controls::MenuFlyoutItem{};
|
||||
placeholder.Text(RS_(L"NewTabMenuFolderEmpty"));
|
||||
placeholder.IsEnabled(false);
|
||||
|
||||
folderItem.Items().Append(placeholder);
|
||||
}
|
||||
|
||||
items.push_back(folderItem);
|
||||
break;
|
||||
}
|
||||
// Any "collection entry" will simply make us add each profile in the collection
|
||||
// separately. This collection is stored as a map <int, Profile>, so the correct
|
||||
// profile index is already known.
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
const auto remainingProfilesEntry = entry.as<ProfileCollectionEntry>();
|
||||
if (remainingProfilesEntry.Profiles() == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto&& [profileIndex, remainingProfile] : remainingProfilesEntry.Profiles())
|
||||
{
|
||||
items.push_back(_CreateNewTabFlyoutProfile(remainingProfile, profileIndex));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// A single profile, the profile index is also given in the entry
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
const auto profileEntry = entry.as<ProfileEntry>();
|
||||
if (profileEntry.Profile() == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex());
|
||||
items.push_back(profileItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method creates a flyout menu item for a given profile with the given index.
|
||||
// It makes sure to set the correct icon, keybinding, and click-action.
|
||||
WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutProfile(const Profile profile, int profileIndex)
|
||||
{
|
||||
auto profileMenuItem = WUX::Controls::MenuFlyoutItem{};
|
||||
|
||||
// Add the keyboard shortcuts based on the number of profiles defined
|
||||
// Look for a keychord that is bound to the equivalent
|
||||
// NewTab(ProfileIndex=N) action
|
||||
NewTerminalArgs newTerminalArgs{ profileIndex };
|
||||
NewTabArgs newTabArgs{ newTerminalArgs };
|
||||
auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) };
|
||||
|
||||
// make sure we find one to display
|
||||
if (profileKeyChord)
|
||||
{
|
||||
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
|
||||
}
|
||||
|
||||
auto profileName = profile.Name();
|
||||
profileMenuItem.Text(profileName);
|
||||
|
||||
// If there's an icon set for this profile, set it as the icon for
|
||||
// this flyout item
|
||||
if (!profile.Icon().empty())
|
||||
{
|
||||
const auto icon = _CreateNewTabFlyoutIcon(profile.Icon());
|
||||
profileMenuItem.Icon(icon);
|
||||
}
|
||||
|
||||
if (profile.Guid() == _settings.GlobalSettings().DefaultProfile())
|
||||
{
|
||||
// Contrast the default profile with others in font weight.
|
||||
profileMenuItem.FontWeight(FontWeights::Bold());
|
||||
}
|
||||
|
||||
auto newTabRun = WUX::Documents::Run();
|
||||
newTabRun.Text(RS_(L"NewTabRun/Text"));
|
||||
auto newPaneRun = WUX::Documents::Run();
|
||||
newPaneRun.Text(RS_(L"NewPaneRun/Text"));
|
||||
newPaneRun.FontStyle(FontStyle::Italic);
|
||||
auto newWindowRun = WUX::Documents::Run();
|
||||
newWindowRun.Text(RS_(L"NewWindowRun/Text"));
|
||||
newWindowRun.FontStyle(FontStyle::Italic);
|
||||
auto elevatedRun = WUX::Documents::Run();
|
||||
elevatedRun.Text(RS_(L"ElevatedRun/Text"));
|
||||
elevatedRun.FontStyle(FontStyle::Italic);
|
||||
|
||||
auto textBlock = WUX::Controls::TextBlock{};
|
||||
textBlock.Inlines().Append(newTabRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(newPaneRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(newWindowRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(elevatedRun);
|
||||
|
||||
auto toolTip = WUX::Controls::ToolTip{};
|
||||
toolTip.Content(textBlock);
|
||||
WUX::Controls::ToolTipService::SetToolTip(profileMenuItem, toolTip);
|
||||
|
||||
profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
NewTerminalArgs newTerminalArgs{ profileIndex };
|
||||
page->_OpenNewTerminalViaDropdown(newTerminalArgs);
|
||||
}
|
||||
});
|
||||
|
||||
return profileMenuItem;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper method to create an IconElement that can be passed to MenuFlyoutItems and
|
||||
// MenuFlyoutSubItems
|
||||
IconElement TerminalPage::_CreateNewTabFlyoutIcon(const winrt::hstring& iconSource)
|
||||
{
|
||||
if (iconSource.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto icon = IconPathConverter::IconWUX(iconSource);
|
||||
Automation::AutomationProperties::SetAccessibilityView(icon, Automation::Peers::AccessibilityView::Raw);
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// Called when the openNewTabDropdown keybinding is used.
|
||||
// Shows the dropdown flyout.
|
||||
|
|
|
@ -240,6 +240,10 @@ namespace winrt::TerminalApp::implementation
|
|||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowLargePasteWarningDialog();
|
||||
|
||||
void _CreateNewTabFlyout();
|
||||
std::vector<winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase> _CreateNewTabFlyoutItems(winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::NewTabMenuEntry> entries);
|
||||
winrt::Windows::UI::Xaml::Controls::IconElement _CreateNewTabFlyoutIcon(const winrt::hstring& icon);
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex);
|
||||
|
||||
void _OpenNewTabDropdown();
|
||||
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
|
||||
|
|
|
@ -153,6 +153,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
void _refreshDefaultTerminals();
|
||||
|
||||
void _resolveDefaultProfile() const;
|
||||
void _resolveNewTabMenuProfiles() const;
|
||||
void _resolveNewTabMenuProfilesSet(const winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> entries, winrt::Windows::Foundation::Collections::IMap<int, Model::Profile>& remainingProfiles, Model::RemainingProfilesEntry& remainingProfilesEntry) const;
|
||||
|
||||
void _validateSettings();
|
||||
void _validateAllSchemesExist();
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
#include "DefaultTerminal.h"
|
||||
#include "FileUtils.h"
|
||||
|
||||
#include "ProfileEntry.h"
|
||||
#include "FolderEntry.h"
|
||||
#include "MatchProfilesEntry.h"
|
||||
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Windows::ApplicationModel::AppExtensions;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
|
@ -1104,6 +1108,7 @@ CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) :
|
|||
_warnings = winrt::single_threaded_vector(std::move(warnings));
|
||||
|
||||
_resolveDefaultProfile();
|
||||
_resolveNewTabMenuProfiles();
|
||||
_validateSettings();
|
||||
}
|
||||
|
||||
|
@ -1282,3 +1287,162 @@ void CascadiaSettings::_resolveDefaultProfile() const
|
|||
// Use the first profile as the new default.
|
||||
GlobalSettings().DefaultProfile(_allProfiles.GetAt(0).Guid());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Iterates through the "newTabMenu" entries and for ProfileEntries resolves the "profile"
|
||||
// fields, which can be a profile name, to a GUID and stores it back.
|
||||
// - It finds any "source" entries and finds all profiles generated by that source
|
||||
// - Lastly, it finds any "remainingProfiles" entries and stores which profiles they
|
||||
// represent (those that were not resolved before). It adds a warning when
|
||||
// multiple of these entries are found.
|
||||
void CascadiaSettings::_resolveNewTabMenuProfiles() const
|
||||
{
|
||||
Model::RemainingProfilesEntry remainingProfilesEntry = nullptr;
|
||||
|
||||
// The TerminalPage needs to know which profile has which profile ID. To prevent
|
||||
// continuous lookups in the _activeProfiles vector, we create a map <int, Profile>
|
||||
// to store these indices in-flight.
|
||||
auto remainingProfilesMap = std::map<int, Model::Profile>{};
|
||||
auto activeProfileCount = gsl::narrow_cast<int>(_activeProfiles.Size());
|
||||
for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++)
|
||||
{
|
||||
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
|
||||
}
|
||||
|
||||
// We keep track of the "remaining profiles" - those that have not yet been resolved
|
||||
// in either a "profile" or "source" entry. They will possibly be assigned to a
|
||||
// "remainingProfiles" entry
|
||||
auto remainingProfiles = single_threaded_map(std::move(remainingProfilesMap));
|
||||
|
||||
// We call a recursive helper function to process the entries
|
||||
auto entries = _globals->NewTabMenu();
|
||||
_resolveNewTabMenuProfilesSet(entries, remainingProfiles, remainingProfilesEntry);
|
||||
|
||||
// If a "remainingProfiles" entry has been found, assign to it the remaining profiles
|
||||
if (remainingProfilesEntry != nullptr)
|
||||
{
|
||||
remainingProfilesEntry.Profiles(remainingProfiles);
|
||||
}
|
||||
|
||||
// If the configuration does not have a "newTabMenu" field, GlobalAppSettings
|
||||
// will return a default value containing just a "remainingProfiles" entry. However,
|
||||
// this value is regenerated on every "get" operation, so the effect of setting
|
||||
// the remaining profiles above will be undone. So only in the case that no custom
|
||||
// value is present in GlobalAppSettings, we will store the modified default value.
|
||||
if (!_globals->HasNewTabMenu())
|
||||
{
|
||||
_globals->NewTabMenu(entries);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function that processes a set of tab menu entries and resolves any profile names
|
||||
// or source fields as necessary - see function above for a more detailed explanation.
|
||||
void CascadiaSettings::_resolveNewTabMenuProfilesSet(const IVector<Model::NewTabMenuEntry> entries, IMap<int, Model::Profile>& remainingProfilesMap, Model::RemainingProfilesEntry& remainingProfilesEntry) const
|
||||
{
|
||||
if (entries == nullptr || entries.Size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
if (entry == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (entry.Type())
|
||||
{
|
||||
// For a simple profile entry, the "profile" field can either be a name or a GUID. We
|
||||
// use the GetProfileByName function to resolve this name to a profile instance, then
|
||||
// find the index of that profile, and store this information in the entry.
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
// We need to access the unresolved profile name, a field that is not exposed
|
||||
// in the projected class. So, we need to first obtain our implementation struct
|
||||
// instance, to access this field.
|
||||
const auto profileEntry{ winrt::get_self<implementation::ProfileEntry>(entry.as<Model::ProfileEntry>()) };
|
||||
|
||||
// Find the profile by name
|
||||
const auto profile = GetProfileByName(profileEntry->ProfileName());
|
||||
|
||||
// If not found, or if the profile is hidden, skip it
|
||||
if (profile == nullptr || profile.Hidden())
|
||||
{
|
||||
profileEntry->Profile(nullptr); // override "default" profile
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the index of the resulting profile and store the result in the entry
|
||||
uint32_t profileIndex;
|
||||
_activeProfiles.IndexOf(profile, profileIndex);
|
||||
|
||||
profileEntry->Profile(profile);
|
||||
profileEntry->ProfileIndex(profileIndex);
|
||||
|
||||
// Remove from remaining profiles list (map)
|
||||
remainingProfilesMap.TryRemove(profileIndex);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// For a remainingProfiles entry, we store it in the variable that is passed back to our caller,
|
||||
// except when that one has already been set (so we found a second/third/...) instance, which will
|
||||
// trigger a warning. We then ignore this entry.
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
{
|
||||
if (remainingProfilesEntry != nullptr)
|
||||
{
|
||||
_warnings.Append(SettingsLoadWarnings::DuplicateRemainingProfilesEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
remainingProfilesEntry = entry.as<Model::RemainingProfilesEntry>();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// For a folder, we simply call this method recursively
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
// We need to access the unfiltered entry list, a field that is not exposed
|
||||
// in the projected class. So, we need to first obtain our implementation struct
|
||||
// instance, to access this field.
|
||||
const auto folderEntry{ winrt::get_self<implementation::FolderEntry>(entry.as<Model::FolderEntry>()) };
|
||||
|
||||
auto folderEntries = folderEntry->RawEntries();
|
||||
_resolveNewTabMenuProfilesSet(folderEntries, remainingProfilesMap, remainingProfilesEntry);
|
||||
break;
|
||||
}
|
||||
|
||||
// For a "matchProfiles" entry, we iterate through the list of all profiles and
|
||||
// find all those matching: generated by the same source, having the same name, or
|
||||
// having the same commandline. This can be expanded with regex support in the future.
|
||||
// We make sure that none of the matches are included in the "remaining profiles" section.
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
// We need to access the matching function, which is not exposed in the projected class.
|
||||
// So, we need to first obtain our implementation struct instance, to access this field.
|
||||
const auto matchEntry{ winrt::get_self<implementation::MatchProfilesEntry>(entry.as<Model::MatchProfilesEntry>()) };
|
||||
|
||||
matchEntry->Profiles(single_threaded_map<int, Model::Profile>());
|
||||
|
||||
auto activeProfileCount = gsl::narrow_cast<int>(_activeProfiles.Size());
|
||||
for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++)
|
||||
{
|
||||
const auto profile = _activeProfiles.GetAt(profileIndex);
|
||||
|
||||
// On a match, we store it in the entry and remove it from the remaining list
|
||||
if (matchEntry->MatchesProfile(profile))
|
||||
{
|
||||
matchEntry->Profiles().Insert(profileIndex, profile);
|
||||
remainingProfilesMap.TryRemove(profileIndex);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "FolderEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
|
||||
#include "FolderEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view IconKey{ "icon" };
|
||||
static constexpr std::string_view EntriesKey{ "entries" };
|
||||
static constexpr std::string_view InliningKey{ "inline" };
|
||||
static constexpr std::string_view AllowEmptyKey{ "allowEmpty" };
|
||||
|
||||
FolderEntry::FolderEntry() noexcept :
|
||||
FolderEntry{ winrt::hstring{} }
|
||||
{
|
||||
}
|
||||
|
||||
FolderEntry::FolderEntry(const winrt::hstring& name) noexcept :
|
||||
FolderEntryT<FolderEntry, NewTabMenuEntry>(NewTabMenuEntryType::Folder),
|
||||
_Name{ name }
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value FolderEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, NameKey, _Name);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
JsonUtils::SetValueForKey(json, EntriesKey, _Entries);
|
||||
JsonUtils::SetValueForKey(json, InliningKey, _Inlining);
|
||||
JsonUtils::SetValueForKey(json, AllowEmptyKey, _AllowEmpty);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> FolderEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<FolderEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
JsonUtils::GetValueForKey(json, EntriesKey, entry->_Entries);
|
||||
JsonUtils::GetValueForKey(json, InliningKey, entry->_Inlining);
|
||||
JsonUtils::GetValueForKey(json, AllowEmptyKey, entry->_AllowEmpty);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
// A FolderEntry should only expose the entries to actually render to WinRT,
|
||||
// to keep the logic for collapsing/expanding more centralised.
|
||||
using NewTabMenuEntryModel = winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntry;
|
||||
IVector<NewTabMenuEntryModel> FolderEntry::Entries() const
|
||||
{
|
||||
// We filter the full list of entries from JSON to just include the
|
||||
// non-empty ones.
|
||||
IVector<NewTabMenuEntryModel> result{ winrt::single_threaded_vector<NewTabMenuEntryModel>() };
|
||||
|
||||
for (const auto& entry : _Entries)
|
||||
{
|
||||
if (entry == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (entry.Type())
|
||||
{
|
||||
case NewTabMenuEntryType::Invalid:
|
||||
continue;
|
||||
|
||||
// A profile is filtered out if it is not valid, so if it was not resolved
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
const auto profileEntry = entry.as<ProfileEntry>();
|
||||
if (profileEntry.Profile() == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Any profile collection is filtered out if there are no results
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
const auto profileCollectionEntry = entry.as<ProfileCollectionEntry>();
|
||||
if (profileCollectionEntry.Profiles().Size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// A folder is filtered out if it has an effective size of 0 (calling
|
||||
// this filtering method recursively), and if it is not allowed to be
|
||||
// empty, or if it should auto-inline.
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
const auto folderEntry = entry.as<Model::FolderEntry>();
|
||||
if (folderEntry.Entries().Size() == 0 && (!folderEntry.AllowEmpty() || folderEntry.Inlining() == FolderEntryInlining::Auto))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.Append(entry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- FolderEntry.h
|
||||
|
||||
Abstract:
|
||||
- A folder entry in the "new tab" dropdown menu,
|
||||
|
||||
Author(s):
|
||||
- Floris Westerman - August 2022
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "pch.h"
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "FolderEntry.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct FolderEntry : FolderEntryT<FolderEntry, NewTabMenuEntry>
|
||||
{
|
||||
public:
|
||||
FolderEntry() noexcept;
|
||||
explicit FolderEntry(const winrt::hstring& name) noexcept;
|
||||
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
// In JSON, we can set arbitrarily many profiles or nested profiles, that might not all
|
||||
// be rendered; for example, when a profile entry is invalid, or when a folder is empty.
|
||||
// Therefore, we will store the JSON entries list internally, and then expose only the
|
||||
// entries to be rendered to WinRT.
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> Entries() const;
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> RawEntries() const
|
||||
{
|
||||
return _Entries;
|
||||
};
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, Name);
|
||||
WINRT_PROPERTY(winrt::hstring, Icon);
|
||||
WINRT_PROPERTY(FolderEntryInlining, Inlining, FolderEntryInlining::Never);
|
||||
WINRT_PROPERTY(bool, AllowEmpty, false);
|
||||
|
||||
private:
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> _Entries{};
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(FolderEntry);
|
||||
}
|
|
@ -23,6 +23,8 @@ Author(s):
|
|||
#include "Command.h"
|
||||
#include "ColorScheme.h"
|
||||
#include "Theme.h"
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "RemainingProfilesEntry.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace SettingsModelLocalTests
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import "Theme.idl";
|
||||
import "ColorScheme.idl";
|
||||
import "ActionMap.idl";
|
||||
import "NewTabMenuEntry.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
|
@ -94,6 +95,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
INHERITABLE_SETTING(Boolean, AlwaysShowNotificationIcon);
|
||||
INHERITABLE_SETTING(IVector<String>, DisabledProfileSources);
|
||||
INHERITABLE_SETTING(Boolean, ShowAdminShield);
|
||||
INHERITABLE_SETTING(IVector<NewTabMenuEntry>, NewTabMenu);
|
||||
INHERITABLE_SETTING(Boolean, EnableColorSelection);
|
||||
|
||||
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
|
||||
|
|
|
@ -61,7 +61,8 @@ Author(s):
|
|||
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, EnableColorSelection, "experimental.enableColorSelection", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} }))
|
||||
|
||||
#define MTSM_PROFILE_SETTINGS(X) \
|
||||
X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "MatchProfilesEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include "MatchProfilesEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view CommandlineKey{ "commandline" };
|
||||
static constexpr std::string_view SourceKey{ "source" };
|
||||
|
||||
MatchProfilesEntry::MatchProfilesEntry() noexcept :
|
||||
MatchProfilesEntryT<MatchProfilesEntry, ProfileCollectionEntry>(NewTabMenuEntryType::MatchProfiles)
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value MatchProfilesEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, NameKey, _Name);
|
||||
JsonUtils::SetValueForKey(json, CommandlineKey, _Commandline);
|
||||
JsonUtils::SetValueForKey(json, SourceKey, _Source);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> MatchProfilesEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<MatchProfilesEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
||||
JsonUtils::GetValueForKey(json, CommandlineKey, entry->_Commandline);
|
||||
JsonUtils::GetValueForKey(json, SourceKey, entry->_Source);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
bool MatchProfilesEntry::MatchesProfile(const Model::Profile& profile)
|
||||
{
|
||||
// We use an optional here instead of a simple bool directly, since there is no
|
||||
// sensible default value for the desired semantics: the first property we want
|
||||
// to match on should always be applied (so one would set "true" as a default),
|
||||
// but if none of the properties are set, the default return value should be false
|
||||
// since this entry type is expected to behave like a positive match/whitelist.
|
||||
//
|
||||
// The easiest way to deal with this neatly is to use an optional, then for any
|
||||
// property to match we consider a null value to be "true", and for the return
|
||||
// value of the function we consider the null value to be "false".
|
||||
auto isMatching = std::optional<bool>{};
|
||||
|
||||
if (!_Name.empty())
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Name == profile.Name() };
|
||||
}
|
||||
|
||||
if (!_Source.empty())
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Source == profile.Source() };
|
||||
}
|
||||
|
||||
if (!_Commandline.empty())
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Commandline == profile.Commandline() };
|
||||
}
|
||||
|
||||
return isMatching.value_or(false);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- MatchProfilesEntry.h
|
||||
|
||||
Abstract:
|
||||
- An entry in the "new tab" dropdown menu that represents a collection
|
||||
of profiles that match a specified name, source, or command line.
|
||||
|
||||
Author(s):
|
||||
- Floris Westerman - November 2022
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "ProfileCollectionEntry.h"
|
||||
#include "MatchProfilesEntry.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct MatchProfilesEntry : MatchProfilesEntryT<MatchProfilesEntry, ProfileCollectionEntry>
|
||||
{
|
||||
public:
|
||||
MatchProfilesEntry() noexcept;
|
||||
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
bool MatchesProfile(const Model::Profile& profile);
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, Name);
|
||||
WINRT_PROPERTY(winrt::hstring, Commandline);
|
||||
WINRT_PROPERTY(winrt::hstring, Source);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(MatchProfilesEntry);
|
||||
}
|
|
@ -21,6 +21,27 @@
|
|||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ActionArgsMagic.h" />
|
||||
<ClInclude Include="NewTabMenuEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SeparatorEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FolderEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ProfileEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="RemainingProfilesEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ProfileCollectionEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MatchProfilesEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VisualStudioGenerator.h" />
|
||||
<ClInclude Include="DefaultTerminal.h">
|
||||
<DependentUpon>DefaultTerminal.idl</DependentUpon>
|
||||
|
@ -163,6 +184,27 @@
|
|||
<ClCompile Include="EnumMappings.cpp">
|
||||
<DependentUpon>EnumMappings.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NewTabMenuEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SeparatorEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FolderEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProfileEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProfileCollectionEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RemainingProfilesEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MatchProfilesEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VsDevCmdGenerator.cpp" />
|
||||
<ClCompile Include="VsDevShellGenerator.cpp" />
|
||||
<ClCompile Include="VsSetupConfiguration.cpp" />
|
||||
|
@ -182,6 +224,7 @@
|
|||
<Midl Include="ApplicationState.idl" />
|
||||
<Midl Include="CascadiaSettings.idl" />
|
||||
<Midl Include="ColorScheme.idl" />
|
||||
<Midl Include="NewTabMenuEntry.idl" />
|
||||
<Midl Include="Theme.idl" />
|
||||
<Midl Include="Command.idl" />
|
||||
<Midl Include="DefaultTerminal.idl" />
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ActionArgsMagic.h" />
|
||||
<ClInclude Include="NewTabMenuEntry.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="ActionArgs.idl" />
|
||||
|
@ -119,6 +120,7 @@
|
|||
<Midl Include="FontConfig.idl" />
|
||||
<Midl Include="DefaultTerminal.idl" />
|
||||
<Midl Include="ApplicationState.idl" />
|
||||
<Midl Include="NewTabMenuEntry.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include "SeparatorEntry.h"
|
||||
#include "FolderEntry.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "RemainingProfilesEntry.h"
|
||||
#include "MatchProfilesEntry.h"
|
||||
|
||||
#include "NewTabMenuEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
using NewTabMenuEntryType = winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType;
|
||||
|
||||
static constexpr std::string_view TypeKey{ "type" };
|
||||
|
||||
NewTabMenuEntry::NewTabMenuEntry(const NewTabMenuEntryType type) noexcept :
|
||||
_Type{ type }
|
||||
{
|
||||
}
|
||||
|
||||
// This method will be overridden by the subclasses, which will then call this
|
||||
// parent implementation for a "base" json object.
|
||||
Json::Value NewTabMenuEntry::ToJson() const
|
||||
{
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
JsonUtils::SetValueForKey(json, TypeKey, _Type);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
// Deserialize the JSON object based on the given type. We use the map from above for that.
|
||||
winrt::com_ptr<NewTabMenuEntry> NewTabMenuEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
const auto type = JsonUtils::GetValueForKey<NewTabMenuEntryType>(json, TypeKey);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NewTabMenuEntryType::Separator:
|
||||
return SeparatorEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Folder:
|
||||
return FolderEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Profile:
|
||||
return ProfileEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
return RemainingProfilesEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
return MatchProfilesEntry::FromJson(json);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- NewTabMenuEntry.h
|
||||
|
||||
Abstract:
|
||||
- An entry in the "new tab" dropdown menu. These entries exist in a few varieties,
|
||||
such as separators, folders, or profile links.
|
||||
|
||||
Author(s):
|
||||
- Floris Westerman - August 2022
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "NewTabMenuEntry.g.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct NewTabMenuEntry : NewTabMenuEntryT<NewTabMenuEntry>
|
||||
{
|
||||
public:
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
virtual Json::Value ToJson() const;
|
||||
|
||||
WINRT_PROPERTY(NewTabMenuEntryType, Type, NewTabMenuEntryType::Invalid);
|
||||
|
||||
// We have a protected/hidden constructor so consumers cannot instantiate
|
||||
// this base class directly and need to go through either FromJson
|
||||
// or one of the subclasses.
|
||||
protected:
|
||||
explicit NewTabMenuEntry(const NewTabMenuEntryType type) noexcept;
|
||||
};
|
||||
}
|
||||
|
||||
namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
||||
{
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<NewTabMenuEntry>
|
||||
{
|
||||
NewTabMenuEntry FromJson(const Json::Value& json)
|
||||
{
|
||||
const auto entry = implementation::NewTabMenuEntry::FromJson(json);
|
||||
if (entry == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return *entry;
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json) const
|
||||
{
|
||||
return json.isObject();
|
||||
}
|
||||
|
||||
Json::Value ToJson(const NewTabMenuEntry& val)
|
||||
{
|
||||
return winrt::get_self<implementation::NewTabMenuEntry>(val)->ToJson();
|
||||
}
|
||||
|
||||
std::string TypeDescription() const
|
||||
{
|
||||
return "NewTabMenuEntry";
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "Profile.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
enum NewTabMenuEntryType
|
||||
{
|
||||
Invalid = 0,
|
||||
Profile,
|
||||
Separator,
|
||||
Folder,
|
||||
RemainingProfiles,
|
||||
MatchProfiles
|
||||
};
|
||||
|
||||
[default_interface] unsealed runtimeclass NewTabMenuEntry
|
||||
{
|
||||
NewTabMenuEntryType Type;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass SeparatorEntry : NewTabMenuEntry
|
||||
{
|
||||
SeparatorEntry();
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ProfileEntry : NewTabMenuEntry
|
||||
{
|
||||
ProfileEntry();
|
||||
ProfileEntry(String profile);
|
||||
|
||||
Profile Profile;
|
||||
Int32 ProfileIndex;
|
||||
}
|
||||
|
||||
enum FolderEntryInlining
|
||||
{
|
||||
Never = 0,
|
||||
Auto
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass FolderEntry : NewTabMenuEntry
|
||||
{
|
||||
FolderEntry();
|
||||
FolderEntry(String name);
|
||||
|
||||
String Name;
|
||||
String Icon;
|
||||
FolderEntryInlining Inlining;
|
||||
Boolean AllowEmpty;
|
||||
|
||||
IVector<NewTabMenuEntry> Entries();
|
||||
}
|
||||
|
||||
[default_interface] unsealed runtimeclass ProfileCollectionEntry : NewTabMenuEntry
|
||||
{
|
||||
IMap<Int32, Profile> Profiles;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass RemainingProfilesEntry : ProfileCollectionEntry
|
||||
{
|
||||
RemainingProfilesEntry();
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass MatchProfilesEntry : ProfileCollectionEntry
|
||||
{
|
||||
MatchProfilesEntry();
|
||||
|
||||
String Name;
|
||||
String Commandline;
|
||||
String Source;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ProfileCollectionEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include "ProfileCollectionEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
ProfileCollectionEntry::ProfileCollectionEntry(const NewTabMenuEntryType type) noexcept :
|
||||
ProfileCollectionEntryT<ProfileCollectionEntry, NewTabMenuEntry>(type)
|
||||
{
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- SeparatorEntry.h
|
||||
|
||||
Abstract:
|
||||
- An entry in the "new tab" dropdown menu that represents some collection
|
||||
of profiles. This is an abstract class that has concretizations like
|
||||
"all profiles from a source" or "all remaining profiles"
|
||||
|
||||
Author(s):
|
||||
- Floris Westerman - August 2022
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "ProfileCollectionEntry.g.h"
|
||||
#include "Profile.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct ProfileCollectionEntry : ProfileCollectionEntryT<ProfileCollectionEntry, NewTabMenuEntry>
|
||||
{
|
||||
public:
|
||||
// Since a comma does not work very nicely in a macro and we need one
|
||||
// for our map definition, we use a macro te define a comma.
|
||||
#define COMMA ,
|
||||
WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IMap<int COMMA Model::Profile>, Profiles);
|
||||
#undef COMMA
|
||||
|
||||
// We have a protected/hidden constructor so consumers cannot instantiate
|
||||
// this class directly and need to go through one of the subclasses.
|
||||
protected:
|
||||
explicit ProfileCollectionEntry(const NewTabMenuEntryType type) noexcept;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include "ProfileEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
static constexpr std::string_view ProfileKey{ "profile" };
|
||||
|
||||
ProfileEntry::ProfileEntry() noexcept :
|
||||
ProfileEntry{ winrt::hstring{} }
|
||||
{
|
||||
}
|
||||
|
||||
ProfileEntry::ProfileEntry(const winrt::hstring& profile) noexcept :
|
||||
ProfileEntryT<ProfileEntry, NewTabMenuEntry>(NewTabMenuEntryType::Profile),
|
||||
_ProfileName{ profile }
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value ProfileEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
// We will now return a profile reference to the JSON representation. Logic is
|
||||
// as follows:
|
||||
// - When Profile is null, this is typically because an existing profile menu entry
|
||||
// in the JSON config is invalid (nonexistent or hidden profile). Then, we store
|
||||
// the original profile string value as read from JSON, to improve portability
|
||||
// of the settings file and limit modifications to the JSON.
|
||||
// - Otherwise, we always store the GUID of the profile. This will effectively convert
|
||||
// all name-matched profiles from the settings file to GUIDs. This might be unexpected
|
||||
// to some users, but is less error-prone and will continue to work when profile
|
||||
// names are changed.
|
||||
if (_Profile == nullptr)
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, ProfileKey, _ProfileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, ProfileKey, _Profile.Guid());
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> ProfileEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<ProfileEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, ProfileKey, entry->_ProfileName);
|
||||
|
||||
return entry;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- FolderEntry.h
|
||||
|
||||
Abstract:
|
||||
- A profile entry in the "new tab" dropdown menu, referring to
|
||||
a single profile.
|
||||
|
||||
Author(s):
|
||||
- Floris Westerman - August 2022
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "ProfileEntry.g.h"
|
||||
|
||||
#include "Profile.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct ProfileEntry : ProfileEntryT<ProfileEntry, NewTabMenuEntry>
|
||||
{
|
||||
public:
|
||||
ProfileEntry() noexcept;
|
||||
explicit ProfileEntry(const winrt::hstring& profile) noexcept;
|
||||
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
// In JSON, only a profile name (guid or string) can be set;
|
||||
// but the consumers of this class would like to have direct access
|
||||
// to the appropriate Model::Profile. Therefore, we have a read-only
|
||||
// property ProfileName that corresponds to the JSON value, and
|
||||
// then CascadiaSettings::_resolveNewTabMenuProfiles() will populate
|
||||
// the Profile and ProfileIndex properties appropriately
|
||||
winrt::hstring ProfileName() const noexcept { return _ProfileName; };
|
||||
|
||||
WINRT_PROPERTY(Model::Profile, Profile);
|
||||
WINRT_PROPERTY(int, ProfileIndex);
|
||||
|
||||
private:
|
||||
winrt::hstring _ProfileName;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ProfileEntry);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "RemainingProfilesEntry.h"
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include "RemainingProfilesEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
RemainingProfilesEntry::RemainingProfilesEntry() noexcept :
|
||||
RemainingProfilesEntryT<RemainingProfilesEntry, ProfileCollectionEntry>(NewTabMenuEntryType::RemainingProfiles)
|
||||
{
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> RemainingProfilesEntry::FromJson(const Json::Value&)
|
||||
{
|
||||
return winrt::make_self<RemainingProfilesEntry>();
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- SeparatorEntry.h
|
||||
|
||||
Abstract:
|
||||
- An entry in the "new tab" dropdown menu that represents all profiles
|
||||
that were not included explicitly elsewhere
|
||||
|
||||
Author(s):
|
||||
- Floris Westerman - August 2022
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "ProfileCollectionEntry.h"
|
||||
#include "RemainingProfilesEntry.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct RemainingProfilesEntry : RemainingProfilesEntryT<RemainingProfilesEntry, ProfileCollectionEntry>
|
||||
{
|
||||
public:
|
||||
RemainingProfilesEntry() noexcept;
|
||||
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(RemainingProfilesEntry);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "SeparatorEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include "SeparatorEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
SeparatorEntry::SeparatorEntry() noexcept :
|
||||
SeparatorEntryT<SeparatorEntry, NewTabMenuEntry>(NewTabMenuEntryType::Separator)
|
||||
{
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> SeparatorEntry::FromJson(const Json::Value&)
|
||||
{
|
||||
return winrt::make_self<SeparatorEntry>();
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- SeparatorEntry.h
|
||||
|
||||
Abstract:
|
||||
- A separator entry in the "new tab" dropdown menu
|
||||
|
||||
Author(s):
|
||||
- Floris Westerman - August 2022
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "SeparatorEntry.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct SeparatorEntry : SeparatorEntryT<SeparatorEntry, NewTabMenuEntry>
|
||||
{
|
||||
public:
|
||||
SeparatorEntry() noexcept;
|
||||
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(SeparatorEntry);
|
||||
}
|
|
@ -651,6 +651,27 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ScrollToMarkDirection)
|
|||
};
|
||||
};
|
||||
|
||||
// Possible NewTabMenuEntryType values
|
||||
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType)
|
||||
{
|
||||
JSON_MAPPINGS(5) = {
|
||||
pair_type{ "profile", ValueType::Profile },
|
||||
pair_type{ "separator", ValueType::Separator },
|
||||
pair_type{ "folder", ValueType::Folder },
|
||||
pair_type{ "remainingProfiles", ValueType::RemainingProfiles },
|
||||
pair_type{ "matchProfiles", ValueType::MatchProfiles },
|
||||
};
|
||||
};
|
||||
|
||||
// Possible FolderEntryInlining values
|
||||
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FolderEntryInlining)
|
||||
{
|
||||
JSON_MAPPINGS(2) = {
|
||||
pair_type{ "never", ValueType::Never },
|
||||
pair_type{ "auto", ValueType::Auto },
|
||||
};
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winrt::Microsoft::Terminal::Control::SelectionColor>
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
FailedToParseStartupActions,
|
||||
FailedToParseSubCommands,
|
||||
UnknownTheme,
|
||||
DuplicateRemainingProfilesEntry,
|
||||
WARNINGS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder.
|
||||
};
|
||||
|
||||
|
|
|
@ -38,6 +38,25 @@
|
|||
<ClInclude Include="../Profile.h" />
|
||||
<ClInclude Include="../TerminalWarnings.h" />
|
||||
<ClInclude Include="../IconPathConverter.h" />
|
||||
<ClInclude Include="../NewTabMenuEntry.h" />
|
||||
<ClInclude Include="../SeparatorEntry.h">
|
||||
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../FolderEntry.h">
|
||||
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../ProfileEntry.h">
|
||||
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../RemainingProfilesEntry.h">
|
||||
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../ProfileCollectionEntry.h">
|
||||
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../MatchProfilesEntry.h">
|
||||
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
<!-- Don't put source files in here - put them in the lib's vcxproj instead! -->
|
||||
|
|
Loading…
Reference in New Issue