Compare commits

...

94 Commits

Author SHA1 Message Date
Mike Griese
aa27423485 more cleanup 2024-06-01 21:19:33 -05:00
Mike Griese
c80f3f5f38 cleanup 2024-06-01 21:03:58 -05:00
Mike Griese
040ba0f9bd a much cleaner abstraction 2024-06-01 20:44:47 -05:00
Mike Griese
54aaed8dcf smaller refactors 2024-06-01 20:11:15 -05:00
Mike Griese
dc56e983d1 use a cache in actionmap to stash commands as we parse them 2024-06-01 14:37:50 -05:00
Mike Griese
ac18f8147c Merge branch 'dev/migrie/f/just-local-snippets' into dev/migrie/f/local-snippets-on-action-refactor 2024-06-01 13:57:06 -05:00
Mike Griese
4fedf06f7c Merge remote-tracking branch 'origin/main' into dev/migrie/f/local-snippets-on-action-refactor 2024-06-01 13:56:59 -05:00
Mike Griese
c1c483ee36 Actually works in suggestions UI again 2024-06-01 13:52:38 -05:00
Mike Griese
73afe48041 plumb it through. It works
(cherry-picked from 38999195b)
2024-06-01 12:27:21 -05:00
Pankaj Bhojwani
0dff336556 nits, schema 2024-05-31 10:51:36 -07:00
Pankaj Bhojwani
51528e9cfd spaces 2024-05-16 16:59:35 -07:00
Pankaj Bhojwani
40b4aa2c94 works 2024-05-16 16:50:56 -07:00
Pankaj Bhojwani
d4d216ca33 don't need helper anymore 2024-05-08 10:48:33 -07:00
Pankaj Bhojwani
7d00b25fbf spelling 2024-05-08 10:11:16 -07:00
Pankaj Bhojwani
ba375ec2a2 remove keys from command 2024-05-08 10:00:45 -07:00
Pankaj Bhojwani
5e48a45ba2 update add action 2024-05-07 15:56:52 -07:00
Pankaj Bhojwani
14d83b5f5c delete user actions that are identical to inbox actions 2024-05-06 16:50:01 -07:00
Pankaj Bhojwani
3e31bda6f2 generate here instead 2024-05-06 15:29:24 -07:00
Pankaj Bhojwani
4d35c14966 schema conflict 2024-05-03 14:54:09 -07:00
Pankaj Bhojwani
abef25d29c move this to header 2024-05-03 14:51:51 -07:00
Pankaj Bhojwani
7793c5c5bc schema 2024-05-03 13:30:09 -07:00
Pankaj Bhojwani
ccf1cc9e83 nits 2024-05-02 19:06:51 -07:00
Pankaj Bhojwani
ebc03e98f4 another test 2024-05-02 18:44:44 -07:00
Pankaj Bhojwani
80fc299c10 some new tests 2024-05-02 18:11:46 -07:00
Pankaj Bhojwani
02a1e37aae correct GH todo 2024-05-01 18:05:06 -07:00
Pankaj Bhojwani
0480d651cb this is better 2024-05-01 17:54:46 -07:00
Pankaj Bhojwani
193e5733bf fix remaining tests 2024-05-01 17:36:31 -07:00
Pankaj Bhojwani
2b16acd4cf check for name, fix some tests 2024-05-01 14:48:37 -07:00
Pankaj Bhojwani
f35bf206e6 Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/action_refactor 2024-05-01 10:50:02 -07:00
Pankaj Bhojwani
cdb907d94d mark todo 2024-04-30 17:43:25 -07:00
Pankaj Bhojwani
3e601f5b66 better if 2024-04-30 17:36:37 -07:00
Pankaj Bhojwani
c2c75c80ed bandaid temporary fix for name 2024-04-30 17:33:53 -07:00
Pankaj Bhojwani
3c6015d97b remove _getcumulativeactions 2024-04-30 16:45:59 -07:00
Pankaj Bhojwani
45cfcd6eca just add duplicate pane auto to defaults 2024-04-30 15:24:33 -07:00
Pankaj Bhojwani
ca4015f5f9 only one tojson 2024-04-30 15:19:33 -07:00
Pankaj Bhojwani
4c744e6ab3 misc 2024-04-30 14:56:42 -07:00
Pankaj Bhojwani
6437b9f508 fix user defaults file 2024-04-30 14:36:09 -07:00
Pankaj Bhojwani
428821b40c remove _idwasgenerated 2024-04-30 13:04:14 -07:00
Pankaj Bhojwani
3d92f27de7 format 2024-04-30 11:18:12 -07:00
Pankaj Bhojwani
db00b90306 spelling things 2024-04-30 11:14:21 -07:00
Pankaj Bhojwani
e725f1e936 resolve conflict 2024-04-30 11:09:51 -07:00
Pankaj Bhojwani
e62dfa2177 some comments 2024-04-30 11:09:15 -07:00
Pankaj Bhojwani
2b4aeb2b11 don't check for special in standard 2024-04-30 11:04:01 -07:00
Pankaj Bhojwani
2f1d8d2dca update defaults 2024-04-30 10:34:58 -07:00
Pankaj Bhojwani
754bf04ab3 mark gh todo 2024-04-30 10:27:25 -07:00
Pankaj Bhojwani
c51558ff4c unmark these 2024-04-30 10:20:07 -07:00
Pankaj Bhojwani
5a1b822833 reimplement populating all known keybindings 2024-04-29 21:33:33 -07:00
Pankaj Bhojwani
b3e9c267f5 remove check for invalid 2024-04-27 16:22:14 -07:00
Pankaj Bhojwani
936afd6b01 _getactionbyid no longer returns optional 2024-04-27 16:16:25 -07:00
Pankaj Bhojwani
dc874c3b3f rename to special/standard 2024-04-27 15:36:17 -07:00
Pankaj Bhojwani
ae16a5e0e1 started stage 3 2024-04-26 15:43:06 -07:00
Pankaj Bhojwani
3e7ab3861a sui works? 2024-04-26 11:26:10 -07:00
Pankaj Bhojwani
ddfac907f4 Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/action_refactor 2024-04-26 09:56:46 -07:00
Pankaj Bhojwani
f1633e0360 overwritten IDs and overwritten keychords show up properly in the SUI 2024-04-25 21:25:17 -07:00
Pankaj Bhojwani
12a61c595e shows up in sui and all keybindings work 2024-04-25 19:16:27 -07:00
Pankaj Bhojwani
d0938e2a24 ugly way to make sure we fixup 2024-04-24 18:15:37 -07:00
Pankaj Bhojwani
f425746169 remove keysmap 2024-04-24 16:21:22 -07:00
Pankaj Bhojwani
e28d47888c some todos for later 2024-04-24 16:04:39 -07:00
Pankaj Bhojwani
0a3e17eebb edge cases 2024-04-24 15:33:35 -07:00
Pankaj Bhojwani
22ab9363ef works?? 2024-04-24 14:33:01 -07:00
Pankaj Bhojwani
c134402507 about to test stage 1 2024-04-24 11:24:40 -07:00
Pankaj Bhojwani
85933e2231 midpoint 2024-04-23 09:49:27 -07:00
Pankaj Bhojwani
ca3eb87301 rename and comment 2024-04-17 16:58:04 -07:00
Pankaj Bhojwani
5e70911a68 remove 0 2024-04-17 16:49:58 -07:00
Pankaj Bhojwani
360b92e567 fmt_compile, fix test 2024-04-17 16:38:52 -07:00
Pankaj Bhojwani
5ee630ec82 fmt is smart 2024-04-12 15:16:36 -07:00
Pankaj Bhojwani
aa4921268e null check 2024-04-12 15:08:53 -07:00
Pankaj Bhojwani
12f3aa9d06 truncate and hex, debug assert 2024-04-12 15:04:23 -07:00
Pankaj Bhojwani
bdf42c2d9c first round of nits 2024-04-11 16:05:35 -07:00
Pankaj Bhojwani
af2d22f343 defaults conflict 2024-04-11 09:42:42 -07:00
Pankaj Bhojwani
6e293a5ee8 Everytime 2024-04-01 10:26:54 -07:00
Pankaj Bhojwani
dd25ed762f change tests 2024-04-01 10:23:23 -07:00
Pankaj Bhojwani
dca7df50c8 excess line 2024-03-29 13:49:33 -07:00
Pankaj Bhojwani
9fc69721c9 add tests 2024-03-29 13:40:53 -07:00
Pankaj Bhojwani
5c2307c531 fix test 2024-03-28 11:48:51 -07:00
Pankaj Bhojwani
d57c7a1f03 move this 2024-03-27 18:04:12 -07:00
Pankaj Bhojwani
71bf90f295 even better, also get the ID from json 2024-03-27 18:02:17 -07:00
Pankaj Bhojwani
10d1fc8d60 this way is better 2024-03-27 17:30:55 -07:00
Pankaj Bhojwani
44510dce1b move id generation to fixupusersettings 2024-03-27 17:14:21 -07:00
Pankaj Bhojwani
eccd87f303 update comment 2024-03-27 15:38:50 -07:00
Pankaj Bhojwani
6c3253968f string of numbers is unsightly but it works 2024-03-27 15:37:19 -07:00
Pankaj Bhojwani
2093660ac1 line 2024-03-26 11:45:44 -07:00
Pankaj Bhojwani
b43191d2c5 spacing 2024-03-26 11:44:47 -07:00
Pankaj Bhojwani
7c907fed6e nits 2024-03-26 11:43:50 -07:00
Pankaj Bhojwani
db528c94fc generate IDs for user commands 2024-03-26 11:29:12 -07:00
Pankaj Bhojwani
be193b21eb merge origin 2024-03-19 15:08:08 -07:00
Pankaj Bhojwani
2bb1b6c6ad conflict 2024-03-19 12:18:06 -07:00
Pankaj Bhojwani
66fe08f964 default ids 2024-03-06 17:24:39 -08:00
Pankaj Bhojwani
642d0ab2b7 inbox makes more sense 2024-03-05 15:47:49 -08:00
Pankaj Bhojwani
8cc82de489 generated 2024-03-05 15:42:38 -08:00
Pankaj Bhojwani
052d063686 ah one of the tests uses this 2024-03-05 14:36:45 -08:00
Pankaj Bhojwani
8bcbd0bd42 fix tests 2024-03-05 14:20:14 -08:00
Pankaj Bhojwani
9dff28f23d update calls in tests 2024-03-05 13:51:18 -08:00
Pankaj Bhojwani
90627b3ae5 add origin tag 2024-03-05 11:35:26 -08:00
46 changed files with 1448 additions and 942 deletions

28
.wt.json Normal file
View File

@@ -0,0 +1,28 @@
{
"actions":
[
{
"command": { "action": "sendInput", "input": "bx\r" },
"name": "Build projectttttttttttttttttt",
"description": "Build the project in the CWD"
},
{
"command": { "action": "sendInput", "input": "bz\r" },
"name": "Build solution, incremental",
"description": "Just build changes to the solution"
},
{
"command": { "action": "sendInput", "input": "bcz\r" },
"name": "Clean & build solution",
"icon": "\uE8e6",
"description": "Start over. Go get your coffee. "
},
{
"command": { "action": "sendInput", "input": "nuget push -apikey az -source TerminalDependencies %userprofile%\\Downloads" },
"name": "Upload package to nuget feed",
"icon": "\uE898",
"description": "Go download a .nupkg, put it in ~/Downloads, and use this to push to our private feed."
},
]
}

View File

@@ -629,7 +629,8 @@
"folder",
"separator",
"remainingProfiles",
"matchProfiles"
"matchProfiles",
"action"
]
},
"NewTabMenuEntry": {
@@ -781,6 +782,28 @@
}
]
},
"ActionEntry": {
"description": "An action in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "action"
},
"profile": {
"type": "string",
"default": "",
"description": "The ID of the action to show in this entry"
}
}
}
]
},
"SwitchToAdjacentTabArgs": {
"oneOf": [
{
@@ -2054,11 +2077,14 @@
},
{
"$ref": "#/$defs/RemainingProfilesEntry"
},
{
"$ref": "#/$defs/ActionEntry"
}
]
}
},
"Keybinding": {
"FullCommand": {
"additionalProperties": false,
"properties": {
"command": {
@@ -2186,21 +2212,6 @@
}
]
},
"keys": {
"description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key",
"oneOf": [
{
"$ref": "#/$defs/KeyChordSegment"
},
{
"items": {
"$ref": "#/$defs/KeyChordSegment"
},
"minItems": 1,
"type": "array"
}
]
},
"icon": {
"$ref": "#/$defs/Icon"
},
@@ -2235,10 +2246,10 @@
"type": "object",
"properties": {
"command": {
"$ref": "#/$defs/Keybinding/properties/command"
"$ref": "#/$defs/FullCommand/properties/command"
},
"name": {
"$ref": "#/$defs/Keybinding/properties/name"
"$ref": "#/$defs/FullCommand/properties/name"
}
}
},
@@ -2261,6 +2272,44 @@
],
"type": "object"
},
"Keybinding": {
"additionalProperties": false,
"properties": {
"id": {
"description": "The ID of the command this keybinding should execute.",
"type": "string"
},
"keys": {
"description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key",
"oneOf": [
{
"$ref": "#/$defs/KeyChordSegment"
},
{
"items": {
"$ref": "#/$defs/KeyChordSegment"
},
"minItems": 1,
"type": "array"
}
]
}
},
"anyOf": [
{
"required": [
"keys",
"id"
]
},
{
"required": [
"keys"
]
}
],
"type": "object"
},
"Globals": {
"additionalProperties": true,
"description": "Properties that affect the entire window, regardless of the profile settings.",
@@ -2464,12 +2513,12 @@
"actions": {
"description": "Properties are specific to each custom action.",
"items": {
"$ref": "#/$defs/Keybinding"
"$ref": "#/$defs/FullCommand"
},
"type": "array"
},
"keybindings": {
"description": "[deprecated] Use actions instead.",
"description": "A list of keychords bound to action IDs",
"deprecated": true,
"items": {
"$ref": "#/$defs/Keybinding"

View File

@@ -18,11 +18,11 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt::TerminalApp::implementation
{
ActionPaletteItem::ActionPaletteItem(const Microsoft::Terminal::Settings::Model::Command& command) :
ActionPaletteItem::ActionPaletteItem(const Microsoft::Terminal::Settings::Model::Command& command, const winrt::hstring keyChordText) :
_Command(command)
{
Name(command.Name());
KeyChordText(command.KeyChordText());
KeyChordText(keyChordText);
Icon(command.IconPath());
}
}

View File

@@ -11,7 +11,7 @@ namespace winrt::TerminalApp::implementation
struct ActionPaletteItem : ActionPaletteItemT<ActionPaletteItem, PaletteItem>
{
ActionPaletteItem() = default;
ActionPaletteItem(const Microsoft::Terminal::Settings::Model::Command& command);
ActionPaletteItem(const Microsoft::Terminal::Settings::Model::Command& command, const winrt::hstring keyChordText);
WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, nullptr);

View File

@@ -7,7 +7,7 @@ namespace TerminalApp
{
[default_interface] runtimeclass ActionPaletteItem : PaletteItem
{
ActionPaletteItem(Microsoft.Terminal.Settings.Model.Command command);
ActionPaletteItem(Microsoft.Terminal.Settings.Model.Command command, String keyChordText);
Microsoft.Terminal.Settings.Model.Command Command { get; };
}

View File

@@ -1330,66 +1330,77 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<SuggestionsArgs>())
{
const auto source = realArgs.Source();
std::vector<Command> commandsCollection;
Control::CommandHistoryContext context{ nullptr };
winrt::hstring currentCommandline = L"";
_doHandleSuggestions(realArgs);
// If the user wanted to use the current commandline to filter results,
// OR they wanted command history (or some other source that
// requires context from the control)
// then get that here.
const bool shouldGetContext = realArgs.UseCommandline() ||
WI_IsFlagSet(source, SuggestionsSource::CommandHistory);
if (shouldGetContext)
{
if (const auto& control{ _GetActiveControl() })
{
context = control.CommandHistory();
if (context)
{
currentCommandline = context.CurrentCommandline();
}
}
}
// Aggregate all the commands from the different sources that
// the user selected.
// Tasks are all the sendInput commands the user has saved in
// their settings file. Ask the ActionMap for those.
if (WI_IsFlagSet(source, SuggestionsSource::Tasks))
{
const auto tasks = _settings.GlobalSettings().ActionMap().FilterToSendInput(currentCommandline);
for (const auto& t : tasks)
{
commandsCollection.push_back(t);
}
}
// Command History comes from the commands in the buffer,
// assuming the user has enabled shell integration. Get those
// from the active control.
if (WI_IsFlagSet(source, SuggestionsSource::CommandHistory) &&
context != nullptr)
{
const auto recentCommands = Command::HistoryToCommands(context.History(), currentCommandline, false);
for (const auto& t : recentCommands)
{
commandsCollection.push_back(t);
}
}
// Open the palette with all these commands in it.
_OpenSuggestions(_GetActiveControl(),
winrt::single_threaded_vector<Command>(std::move(commandsCollection)),
SuggestionsMode::Palette,
currentCommandline);
args.Handled(true);
}
}
}
winrt::fire_and_forget TerminalPage::_doHandleSuggestions(SuggestionsArgs realArgs)
{
const auto source = realArgs.Source();
std::vector<Command> commandsCollection;
Control::CommandHistoryContext context{ nullptr };
winrt::hstring currentCommandline = L"";
winrt::hstring currentWorkingDirectory = L"";
// If the user wanted to use the current commandline to filter results,
// OR they wanted command history (or some other source that
// requires context from the control)
// then get that here.
const bool shouldGetContext = realArgs.UseCommandline() ||
WI_IsAnyFlagSet(source, SuggestionsSource::CommandHistory);
if (const auto& control{ _GetActiveControl() })
{
currentWorkingDirectory = control.CurrentWorkingDirectory();
if (shouldGetContext)
{
context = control.CommandHistory();
if (context)
{
currentCommandline = context.CurrentCommandline();
}
}
}
// Aggregate all the commands from the different sources that
// the user selected.
// Tasks are all the sendInput commands the user has saved in
// their settings file. Ask the ActionMap for those.
if (WI_IsFlagSet(source, SuggestionsSource::Tasks))
{
const auto tasks = co_await _settings.GlobalSettings().ActionMap().FilterToSnippets(currentCommandline, currentWorkingDirectory);
for (const auto& t : tasks)
{
commandsCollection.push_back(t);
}
}
// Command History comes from the commands in the buffer,
// assuming the user has enabled shell integration. Get those
// from the active control.
if (WI_IsFlagSet(source, SuggestionsSource::CommandHistory) &&
context != nullptr)
{
const auto recentCommands = Command::HistoryToCommands(context.History(), currentCommandline, false);
for (const auto& t : recentCommands)
{
commandsCollection.push_back(t);
}
}
co_await wil::resume_foreground(Dispatcher());
// Open the palette with all these commands in it.
_OpenSuggestions(_GetActiveControl(),
winrt::single_threaded_vector<Command>(std::move(commandsCollection)),
SuggestionsMode::Palette,
currentCommandline);
}
void TerminalPage::_HandleColorSelection(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

View File

@@ -950,21 +950,27 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap)
{
_actionMap = actionMap;
_setCommands();
}
void CommandPalette::SetCommands(const Collections::IVector<Command>& actions)
void CommandPalette::_setCommands()
{
_allCommands.Clear();
for (const auto& action : actions)
if (_actionMap)
{
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
auto filteredCommand{ winrt::make<FilteredCommand>(actionPaletteItem) };
_allCommands.Append(filteredCommand);
}
_allCommands.Clear();
const auto expandedCommands{ _actionMap.ExpandedCommands() };
for (const auto& action : expandedCommands)
{
const auto keyChordText{ KeyChordSerialization::ToString(_actionMap.GetKeyBindingForAction(action.ID())) };
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action, keyChordText) };
auto filteredCommand{ winrt::make<FilteredCommand>(actionPaletteItem) };
_allCommands.Append(filteredCommand);
}
if (Visibility() == Visibility::Visible && _currentMode == CommandPaletteMode::ActionMode)
{
_updateFilteredActions();
if (Visibility() == Visibility::Visible && _currentMode == CommandPaletteMode::ActionMode)
{
_updateFilteredActions();
}
}
}
@@ -1178,7 +1184,8 @@ namespace winrt::TerminalApp::implementation
for (const auto& nameAndCommand : parentCommand.NestedCommands())
{
const auto action = nameAndCommand.Value();
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
// nested commands cannot have keys bound to them, so just pass in the command and no keys
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action, L"") };
auto nestedFilteredCommand{ winrt::make<FilteredCommand>(nestedActionPaletteItem) };
_currentNestedCommands.Append(nestedFilteredCommand);
}

View File

@@ -31,7 +31,6 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> FilteredActions();
void SetCommands(const Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command>& actions);
void SetTabs(const Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase>& tabs, const Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase>& mruTabs);
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
@@ -81,6 +80,8 @@ namespace winrt::TerminalApp::implementation
bool _lastFilterTextWasEmpty{ true };
void _setCommands();
void _filterTextChanged(const Windows::Foundation::IInspectable& sender,
const Windows::UI::Xaml::RoutedEventArgs& args);
void _previewKeyDownHandler(const Windows::Foundation::IInspectable& sender,

View File

@@ -20,8 +20,6 @@ namespace TerminalApp
Windows.Foundation.Collections.IObservableVector<FilteredCommand> FilteredActions { get; };
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
void SetTabs(Windows.Foundation.Collections.IObservableVector<TabBase> tabs, Windows.Foundation.Collections.IObservableVector<TabBase> mruTabs);
void SetActionMap(Microsoft.Terminal.Settings.Model.IActionMapView actionMap);

View File

@@ -508,7 +508,7 @@ namespace winrt::TerminalApp::implementation
automationPeer.RaiseNotificationEvent(
Automation::Peers::AutomationNotificationKind::ItemAdded,
Automation::Peers::AutomationNotificationProcessing::MostRecent,
paletteItem.Name() + L" " + paletteItem.KeyChordText(),
paletteItem.Name(),
L"SuggestionsControlSelectedItemChanged" /* unique name for this notification category */);
}
}
@@ -751,17 +751,13 @@ namespace winrt::TerminalApp::implementation
return _filteredActions;
}
void SuggestionsControl::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap)
{
_actionMap = actionMap;
}
void SuggestionsControl::SetCommands(const Collections::IVector<Command>& actions)
{
_allCommands.Clear();
for (const auto& action : actions)
{
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
// key chords aren't relevant in the suggestions control, so make the palette item with just the command and no keys
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action, L"") };
auto filteredCommand{ winrt::make<FilteredCommand>(actionPaletteItem) };
_allCommands.Append(filteredCommand);
}
@@ -915,7 +911,7 @@ namespace winrt::TerminalApp::implementation
for (const auto& nameAndCommand : parentCommand.NestedCommands())
{
const auto action = nameAndCommand.Value();
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action, L"") };
auto nestedFilteredCommand{ winrt::make<FilteredCommand>(nestedActionPaletteItem) };
_currentNestedCommands.Append(nestedFilteredCommand);
}

View File

@@ -24,7 +24,6 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> FilteredActions();
void SetCommands(const Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command>& actions);
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);

View File

@@ -36,7 +36,6 @@ namespace TerminalApp
SuggestionsMode Mode { get; set; };
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
void SetActionMap(Microsoft.Terminal.Settings.Model.IActionMapView actionMap);
void SelectNextItem(Boolean moveDown);
void Open(SuggestionsMode mode, IVector<Microsoft.Terminal.Settings.Model.Command> commands, String filterText, Windows.Foundation.Point anchor, Windows.Foundation.Size space, Single characterHeight);

View File

@@ -31,7 +31,6 @@
MinHeight="0"
Padding="16,0,12,0"
HorizontalContentAlignment="Stretch"
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}"
AutomationProperties.Name="{x:Bind Item.Name, Mode=OneWay}"
FontSize="12" />
</DataTemplate>

View File

@@ -192,7 +192,8 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TabBase::_UpdateSwitchToTabKeyChord()
{
const auto keyChord = _actionMap ? _actionMap.GetKeyBindingForAction(ShortcutAction::SwitchToTab, SwitchToTabArgs{ _TabViewIndex }) : nullptr;
const auto id = fmt::format(FMT_COMPILE(L"Terminal.SwitchToTab{}"), _TabViewIndex);
const auto keyChord{ _actionMap.GetKeyBindingForAction(id) };
const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L"";
if (_keyChord == keyChordText)

View File

@@ -123,7 +123,6 @@ namespace winrt::TerminalApp::implementation
// to happen before the Settings UI is reloaded and tries to re-read those values.
if (const auto p = CommandPaletteElement())
{
p.SetCommands(_settings.GlobalSettings().ActionMap().ExpandedCommands());
p.SetActionMap(_settings.ActionMap());
}
@@ -826,7 +825,7 @@ namespace winrt::TerminalApp::implementation
newTabFlyout.Items().Append(settingsItem);
auto actionMap = _settings.ActionMap();
const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) };
const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(L"Terminal.OpenSettingsUI") };
if (settingsKeyChord)
{
_SetAcceleratorForMenuItem(settingsItem, settingsKeyChord);
@@ -848,7 +847,7 @@ namespace winrt::TerminalApp::implementation
commandPaletteFlyout.Click({ this, &TerminalPage::_CommandPaletteButtonOnClick });
newTabFlyout.Items().Append(commandPaletteFlyout);
const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction(L"Terminal.ToggleCommandPalette") };
if (commandPaletteKeyChord)
{
_SetAcceleratorForMenuItem(commandPaletteFlyout, commandPaletteKeyChord);
@@ -1005,6 +1004,18 @@ namespace winrt::TerminalApp::implementation
items.push_back(profileItem);
break;
}
case NewTabMenuEntryType::Action:
{
const auto actionEntry = entry.as<ActionEntry>();
const auto actionId = actionEntry.ActionId();
if (_settings.ActionMap().GetActionById(actionId))
{
auto actionItem = _CreateNewTabFlyoutAction(actionId);
items.push_back(actionItem);
}
break;
}
}
}
@@ -1023,7 +1034,8 @@ namespace winrt::TerminalApp::implementation
// NewTab(ProfileIndex=N) action
NewTerminalArgs newTerminalArgs{ profileIndex };
NewTabArgs newTabArgs{ newTerminalArgs };
auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) };
const auto id = fmt::format(FMT_COMPILE(L"Terminal.OpenNewTabProfile{}"), profileIndex);
const auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(id) };
// make sure we find one to display
if (profileKeyChord)
@@ -1094,6 +1106,42 @@ namespace winrt::TerminalApp::implementation
return profileMenuItem;
}
// Method Description:
// - This method creates a flyout menu item for a given action
// It makes sure to set the correct icon, keybinding, and click-action.
WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutAction(const winrt::hstring& actionId)
{
auto actionMenuItem = WUX::Controls::MenuFlyoutItem{};
const auto action{ _settings.ActionMap().GetActionById(actionId) };
const auto actionKeyChord{ _settings.ActionMap().GetKeyBindingForAction(actionId) };
if (actionKeyChord)
{
_SetAcceleratorForMenuItem(actionMenuItem, actionKeyChord);
}
const auto actionName = action.Name();
actionMenuItem.Text(actionName);
// If there's an icon set for this action, set it as the icon for
// this flyout item
const auto& iconPath = action.IconPath();
if (!iconPath.empty())
{
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
actionMenuItem.Icon(icon);
}
actionMenuItem.Click([action, weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
page->_actionDispatch->DoAction(action.ActionAndArgs());
}
});
return actionMenuItem;
}
// Method Description:
// - Helper method to create an IconElement that can be passed to MenuFlyoutItems and
// MenuFlyoutSubItems
@@ -1827,7 +1875,6 @@ namespace winrt::TerminalApp::implementation
{
const auto p = FindName(L"CommandPaletteElement").as<CommandPalette>();
p.SetCommands(_settings.GlobalSettings().ActionMap().ExpandedCommands());
p.SetActionMap(_settings.ActionMap());
// When the visibility of the command palette changes to "collapsed",

View File

@@ -300,6 +300,7 @@ namespace winrt::TerminalApp::implementation
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);
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutAction(const winrt::hstring& actionId);
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs);
@@ -543,6 +544,7 @@ namespace winrt::TerminalApp::implementation
winrt::com_ptr<TerminalTab> _senderOrFocusedTab(const IInspectable& sender);
void _activePaneChanged(winrt::TerminalApp::TerminalTab tab, Windows::Foundation::IInspectable args);
winrt::fire_and_forget _doHandleSuggestions(Microsoft::Terminal::Settings::Model::SuggestionsArgs realArgs);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp

View File

@@ -2256,7 +2256,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
return winrt::hstring{ L"" };
};
const auto currentCommand = _terminal->CurrentCommand();
const auto trimmedCurrentCommand = trimToHstring(currentCommand);
@@ -2279,9 +2278,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto context = winrt::make_self<CommandHistoryContext>(std::move(commands));
context->CurrentCommandline(trimmedCurrentCommand);
return *context;
}
winrt::hstring ControlCore::CurrentWorkingDirectory() const
{
return winrt::hstring{ _terminal->GetWorkingDirectory() };
}
Core::Scheme ControlCore::ColorScheme() const noexcept
{
Core::Scheme s;

View File

@@ -68,6 +68,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
til::property<Windows::Foundation::Collections::IVector<winrt::hstring>> History;
til::property<winrt::hstring> CurrentCommandline;
til::property<winrt::hstring> CurrentWorkingDirectory;
CommandHistoryContext(std::vector<winrt::hstring>&& history)
{
@@ -184,6 +185,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ContextMenuSelectCommand();
void ContextMenuSelectOutput();
winrt::hstring CurrentWorkingDirectory() const;
#pragma endregion
#pragma region ITerminalInput

View File

@@ -62,5 +62,7 @@ namespace Microsoft.Terminal.Control
void SelectOutput(Boolean goUp);
IVector<ScrollMark> ScrollMarks { get; };
String CurrentWorkingDirectory { get; };
};
}

View File

@@ -3555,6 +3555,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return _core.CommandHistory();
}
winrt::hstring TermControl::CurrentWorkingDirectory() const
{
return _core.CurrentWorkingDirectory();
}
Core::Scheme TermControl::ColorScheme() const noexcept
{

View File

@@ -113,6 +113,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SelectCommand(const bool goUp);
void SelectOutput(const bool goUp);
winrt::hstring CurrentWorkingDirectory() const;
#pragma endregion
void ScrollViewport(int viewTop);

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ActionEntry.h"
#include "JsonUtils.h"
#include "ActionEntry.g.cpp"
using namespace Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
static constexpr std::string_view ActionIdKey{ "actionId" };
ActionEntry::ActionEntry() noexcept :
ActionEntryT<ActionEntry, NewTabMenuEntry>(NewTabMenuEntryType::Action)
{
}
Json::Value ActionEntry::ToJson() const
{
auto json = NewTabMenuEntry::ToJson();
JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId);
return json;
}
winrt::com_ptr<NewTabMenuEntry> ActionEntry::FromJson(const Json::Value& json)
{
auto entry = winrt::make_self<ActionEntry>();
JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId);
return entry;
}

View File

@@ -0,0 +1,37 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- ActionEntry.h
Abstract:
- An action entry in the "new tab" dropdown menu
Author(s):
- Pankaj Bhojwani - May 2024
--*/
#pragma once
#include "NewTabMenuEntry.h"
#include "ActionEntry.g.h"
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
struct ActionEntry : ActionEntryT<ActionEntry, NewTabMenuEntry>
{
public:
ActionEntry() noexcept;
Json::Value ToJson() const override;
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
WINRT_PROPERTY(winrt::hstring, ActionId);
};
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
{
BASIC_FACTORY(ActionEntry);
}

File diff suppressed because it is too large Load Diff

View File

@@ -49,6 +49,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
struct ActionMap : ActionMapT<ActionMap>, IInheritable<ActionMap>
{
void _FinalizeInheritance() override;
// views
Windows::Foundation::Collections::IMapView<hstring, Model::ActionAndArgs> AvailableActions();
Windows::Foundation::Collections::IMapView<hstring, Model::Command> NameMap();
@@ -58,20 +60,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// queries
Model::Command GetActionByKeyChord(const Control::KeyChord& keys) const;
Model::Command GetActionById(const winrt::hstring& cmdID) const;
bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const;
Control::KeyChord GetKeyBindingForAction(const ShortcutAction& action) const;
Control::KeyChord GetKeyBindingForAction(const ShortcutAction& action, const IActionArgs& actionArgs) const;
Control::KeyChord GetKeyBindingForAction(winrt::hstring cmdID);
// population
void AddAction(const Model::Command& cmd);
void AddAction(const Model::Command& cmd, const Control::KeyChord& keys);
// JSON
static com_ptr<ActionMap> FromJson(const Json::Value& json, const OriginTag origin = OriginTag::None);
std::vector<SettingsLoadWarnings> LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true);
Json::Value ToJson() const;
Json::Value KeyBindingsToJson() const;
bool FixUpsAppliedDuringLoad() const;
// modification
bool GenerateIDsForActions();
bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys);
void DeleteKeyBinding(const Control::KeyChord& keys);
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
@@ -80,49 +83,54 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
const Windows::Foundation::Collections::IMapView<winrt::hstring, Model::ColorScheme>& schemes);
winrt::Windows::Foundation::Collections::IVector<Model::Command> FilterToSendInput(winrt::hstring currentCommandline);
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Foundation::Collections::IVector<Model::Command>> FilterToSnippets(winrt::hstring currentCommandline, winrt::hstring currentWorkingDirectory);
private:
std::optional<Model::Command> _GetActionByID(const InternalActionID actionID) const;
Model::Command _GetActionByID(const winrt::hstring actionID) const;
std::optional<winrt::hstring> _GetActionIdByKeyChordInternal(const Control::KeyChord& keys) const;
std::optional<Model::Command> _GetActionByKeyChordInternal(const Control::KeyChord& keys) const;
void _RefreshKeyBindingCaches();
void _PopulateAvailableActionsWithStandardCommands(std::unordered_map<hstring, Model::ActionAndArgs>& availableActions, std::unordered_set<InternalActionID>& visitedActionIDs) const;
void _PopulateNameMapWithSpecialCommands(std::unordered_map<hstring, Model::Command>& nameMap) const;
void _PopulateNameMapWithStandardCommands(std::unordered_map<hstring, Model::Command>& nameMap) const;
void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map<Control::KeyChord, Model::Command, KeyChordHash, KeyChordEquality>& keyBindingsMap, std::unordered_set<Control::KeyChord, KeyChordHash, KeyChordEquality>& unboundKeys) const;
std::vector<Model::Command> _GetCumulativeActions() const noexcept;
void _TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& consolidatedCmd);
void _TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd);
void _TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd);
void _PopulateCumulativeKeyMap(std::unordered_map<Control::KeyChord, winrt::hstring, KeyChordHash, KeyChordEquality>& keyBindingsMap);
void _PopulateCumulativeActionMap(std::unordered_map<hstring, Model::Command>& actionMap);
void _recursiveUpdateCommandKeybindingLabels();
void _TryUpdateActionMap(const Model::Command& cmd);
void _TryUpdateKeyChord(const Model::Command& cmd, const Control::KeyChord& keys);
winrt::Windows::Foundation::IAsyncAction _updateLocalSnippetCache(winrt::hstring currentWorkingDirectory);
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionsCache{ nullptr };
Windows::Foundation::Collections::IMap<hstring, Model::Command> _NameMapCache{ nullptr };
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _GlobalHotkeysCache{ nullptr };
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _KeyBindingMapCache{ nullptr };
Windows::Foundation::Collections::IVector<Model::Command> _ExpandedCommandsCache{ nullptr };
std::unordered_map<winrt::hstring, Model::Command> _NestedCommands;
std::vector<Model::Command> _IterableCommands;
std::unordered_map<Control::KeyChord, InternalActionID, KeyChordHash, KeyChordEquality> _KeyMap;
std::unordered_map<InternalActionID, Model::Command> _ActionMap;
// Masking Actions:
// These are actions that were introduced in an ancestor,
// but were edited (or unbound) in the current layer.
// _ActionMap shows a Command with keys that were added in this layer,
// whereas _MaskingActions provides a view that encompasses all of
// the valid associated key chords.
// Maintaining this map allows us to return a valid Command
// in GetKeyBindingForAction.
// Additionally, these commands to not need to be serialized,
// whereas those in _ActionMap do. These actions provide more data
// than is necessary to be serialized.
std::unordered_map<InternalActionID, Model::Command> _MaskingActions;
bool _fixUpsAppliedDuringLoad;
// _KeyMap is the map of key chords -> action IDs defined in this layer
// _ActionMap is the map of action IDs -> commands defined in this layer
// These maps are the ones that we deserialize into when parsing the user json and vice-versa
std::unordered_map<Control::KeyChord, winrt::hstring, KeyChordHash, KeyChordEquality> _KeyMap;
std::unordered_map<winrt::hstring, Model::Command> _ActionMap;
// _CumulativeKeyMapCache is the map of key chords -> action IDs defined in all layers, with child layers overriding parent layers
Windows::Foundation::Collections::IMap<Control::KeyChord, winrt::hstring> _CumulativeKeyMapCache{ nullptr };
// _CumulativeActionMapCache is the map of action IDs -> commands defined in all layers, with child layers overriding parent layers
Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command> _CumulativeActionMapCache{ nullptr };
// _ResolvedKeyActionMapCache is the map of key chords -> commands defined in all layers, with child layers overriding parent layers
// This is effectively a combination of _CumulativeKeyMapCache and _CumulativeActionMapCache and its purpose is so that
// we can give the SUI a view of the key chords and the commands they map to
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _ResolvedKeyActionMapCache{ nullptr };
std::unordered_map<hstring, std::vector<Model::Command>> _cwdLocalSnippetsCache{};
friend class SettingsModelUnitTests::KeyBindingsTests;
friend class SettingsModelUnitTests::DeserializationTests;

View File

@@ -11,9 +11,8 @@ namespace Microsoft.Terminal.Settings.Model
Boolean IsKeyChordExplicitlyUnbound(Microsoft.Terminal.Control.KeyChord keys);
Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys);
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action);
[method_name("GetKeyBindingForActionWithArgs")] Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs);
Command GetActionById(String cmdID);
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID);
Windows.Foundation.Collections.IMapView<String, ActionAndArgs> AvailableActions { get; };
@@ -23,7 +22,7 @@ namespace Microsoft.Terminal.Settings.Model
IVector<Command> ExpandedCommands { get; };
IVector<Command> FilterToSendInput(String CurrentCommandline);
Windows.Foundation.IAsyncOperation<IVector<Command> > FilterToSnippets(String CurrentCommandline, String CurrentWorkingDirectory);
};
[default_interface] runtimeclass ActionMap : IActionMapView

View File

@@ -27,12 +27,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
// Method Description:
// - Deserialize an ActionMap from the array `json`. The json array should contain
// an array of serialized `Command` objects.
// - These actions are added to the `ActionMap`, where we automatically handle
// overwriting and unbinding actions.
// - Deserialize an ActionMap from the array `json`
// - The json array either contains an array of serialized `Command` objects,
// or an array of keybindings
// - The actions are added to _ActionMap and the keybindings are added to _KeyMap
// Arguments:
// - json: an array of Json::Value's to deserialize into our ActionMap.
// - json: an array of Json::Value's to deserialize into our _ActionMap and _KeyMap
// Return value:
// - a list of warnings encountered while deserializing the json
std::vector<SettingsLoadWarnings> ActionMap::LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings)
@@ -43,14 +43,69 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// settings phase, so we'll collect them now.
std::vector<SettingsLoadWarnings> warnings;
for (const auto& cmdJson : json)
for (const auto& jsonBlock : json)
{
if (!cmdJson.isObject())
if (!jsonBlock.isObject())
{
continue;
}
AddAction(*Command::FromJson(cmdJson, warnings, origin, withKeybindings));
// the json block may be 1 of 3 things:
// - the legacy style command block, that has the action, args and keys in it
// - the modern style command block, that has the action, args and an ID
// - the modern style keys block, that has the keys and an ID
// if the block contains a "command" field, it is either a legacy or modern style command block
// and we can call Command::FromJson on it (Command::FromJson can handle parsing both legacy or modern)
// if there is no "command" field, then it is a modern style keys block
// if there are keys, extract them first
Control::KeyChord keys{ nullptr };
if (withKeybindings && jsonBlock.isMember(JsonKey(KeysKey)))
{
const auto keysJson{ jsonBlock[JsonKey(KeysKey)] };
if (keysJson.isArray() && keysJson.size() > 1)
{
warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord);
}
else
{
JsonUtils::GetValueForKey(jsonBlock, KeysKey, keys);
}
}
// Now check if this is a command block
if (jsonBlock.isMember(JsonKey(CommandsKey)) || jsonBlock.isMember(JsonKey(ActionKey)))
{
AddAction(*Command::FromJson(jsonBlock, warnings, origin), keys);
if (jsonBlock.isMember(JsonKey(KeysKey)))
{
// there are keys in this command block meaning this is the legacy style -
// inform the loader that fixups are needed
_fixUpsAppliedDuringLoad = true;
}
if (jsonBlock.isMember(JsonKey(ActionKey)) && !jsonBlock.isMember(JsonKey(IterateOnKey)) && origin == OriginTag::User && !jsonBlock.isMember(JsonKey(IDKey)))
{
// for non-nested non-iterable commands,
// if there's no ID in the command block we will generate one for the user -
// inform the loader that the ID needs to be written into the json
_fixUpsAppliedDuringLoad = true;
}
}
else if (keys)
{
// this is not a command block, so it is a keybinding block
// if the "id" field doesn't exist in the json, then idJson will be an empty string which is fine
winrt::hstring idJson;
JsonUtils::GetValueForKey(jsonBlock, IDKey, idJson);
// any existing keybinding with the same keychord in this layer will get overwritten
_KeyMap.insert_or_assign(keys, idJson);
}
}
return warnings;
@@ -60,23 +115,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
Json::Value actionList{ Json::ValueType::arrayValue };
// Command serializes to an array of JSON objects.
// This is because a Command may have multiple key chords associated with it.
// The name and icon are only serialized in the first object.
// Example:
// { "name": "Custom Copy", "command": "copy", "keys": "ctrl+c" }
// { "command": "copy", "keys": "ctrl+shift+c" }
// { "command": "copy", "keys": "ctrl+ins" }
auto toJson = [&actionList](const Model::Command& cmd) {
const auto cmdImpl{ winrt::get_self<implementation::Command>(cmd) };
const auto& cmdJsonArray{ cmdImpl->ToJson() };
for (const auto& cmdJson : cmdJsonArray)
{
actionList.append(cmdJson);
}
const auto& cmdJson{ cmdImpl->ToJson() };
actionList.append(cmdJson);
};
// Serialize all standard Command objects in the current layer
for (const auto& [_, cmd] : _ActionMap)
{
toJson(cmd);
@@ -96,4 +140,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return actionList;
}
Json::Value ActionMap::KeyBindingsToJson() const
{
Json::Value keybindingsList{ Json::ValueType::arrayValue };
auto toJson = [&keybindingsList](const KeyChord kc, const winrt::hstring cmdID) {
Json::Value keyIDPair{ Json::ValueType::objectValue };
JsonUtils::SetValueForKey(keyIDPair, KeysKey, kc);
JsonUtils::SetValueForKey(keyIDPair, IDKey, cmdID);
keybindingsList.append(keyIDPair);
};
// Serialize all standard keybinding objects in the current layer
for (const auto& [keys, cmdID] : _KeyMap)
{
toJson(keys, cmdID);
}
return keybindingsList;
}
}

View File

@@ -1095,6 +1095,20 @@ void CascadiaSettings::ExportFile(winrt::hstring path, winrt::hstring content)
}
CATCH_LOG();
}
winrt::hstring CascadiaSettings::ReadFile(winrt::hstring path)
{
try
{
auto maybeContents = ReadUTF8FileIfExists({ path.c_str() });
if (maybeContents.has_value())
{
return winrt::hstring{ til::u8u16(*maybeContents) };
}
}
CATCH_LOG();
return L"";
}
void CascadiaSettings::_validateThemeExists()
{

View File

@@ -113,6 +113,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::hstring ApplicationVersion();
static bool IsPortableMode();
static void ExportFile(winrt::hstring path, winrt::hstring content);
static winrt::hstring ReadFile(winrt::hstring path);
CascadiaSettings() noexcept = default;
CascadiaSettings(const winrt::hstring& userJSON, const winrt::hstring& inboxJSON);

View File

@@ -21,6 +21,7 @@ namespace Microsoft.Terminal.Settings.Model
static String ApplicationVersion { get; };
static void ExportFile(String path, String content);
static String ReadFile(String path);
CascadiaSettings(String userJSON, String inboxJSON);

View File

@@ -461,6 +461,7 @@ bool SettingsLoader::FixupUserSettings()
};
auto fixedUp = userSettings.fixupsAppliedDuringLoad;
fixedUp = userSettings.globals->FixUpsAppliedDuringLoad() || fixedUp;
fixedUp = RemapColorSchemeForProfile(userSettings.baseLayerProfile) || fixedUp;
for (const auto& profile : userSettings.profiles)
@@ -504,10 +505,6 @@ bool SettingsLoader::FixupUserSettings()
fixedUp = true;
}
// we need to generate an ID for a command in the user settings if it doesn't already have one
auto actionMap{ winrt::get_self<ActionMap>(userSettings.globals->ActionMap()) };
actionMap->GenerateIDsForActions();
return fixedUp;
}

View File

@@ -20,14 +20,6 @@ namespace winrt
namespace WUX = Windows::UI::Xaml;
}
static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view IDKey{ "id" };
static constexpr std::string_view IconKey{ "icon" };
static constexpr std::string_view ActionKey{ "command" };
static constexpr std::string_view IterateOnKey{ "iterateOn" };
static constexpr std::string_view CommandsKey{ "commands" };
static constexpr std::string_view KeysKey{ "keys" };
static constexpr std::string_view ProfileNameToken{ "${profile.name}" };
static constexpr std::string_view ProfileIconToken{ "${profile.icon}" };
static constexpr std::string_view SchemeNameToken{ "${scheme.name}" };
@@ -43,7 +35,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
command->_Origin = _Origin;
command->_ID = _ID;
command->_ActionAndArgs = *get_self<implementation::ActionAndArgs>(_ActionAndArgs)->Copy();
command->_keyMappings = _keyMappings;
command->_iconPath = _iconPath;
command->_IterateOn = _IterateOn;
@@ -121,7 +112,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return hstring{ _ID };
}
bool Command::GenerateID()
void Command::GenerateID()
{
if (_ActionAndArgs)
{
@@ -129,11 +120,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty())
{
_ID = generatedID;
_IDWasGenerated = true;
return true;
_IdWasGenerated = true;
}
}
return false;
}
bool Command::IdWasGenerated()
{
return _IdWasGenerated;
}
void Command::Name(const hstring& value)
@@ -144,70 +138,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
}
std::vector<Control::KeyChord> Command::KeyMappings() const noexcept
{
return _keyMappings;
}
// Function Description:
// - Add the key chord to the command's list of key mappings.
// - If the key chord was already registered, move it to the back
// of the line, and dispatch a notification that Command::Keys changed.
// Arguments:
// - keys: the new key chord that we are registering this command to
// Return Value:
// - <none>
void Command::RegisterKey(const Control::KeyChord& keys)
{
if (!keys)
{
return;
}
// Remove the KeyChord and add it to the back of the line.
// This makes it so that the main key chord associated with this
// command is updated.
EraseKey(keys);
_keyMappings.push_back(keys);
}
// Function Description:
// - Remove the key chord from the command's list of key mappings.
// Arguments:
// - keys: the key chord that we are unregistering
// Return Value:
// - <none>
void Command::EraseKey(const Control::KeyChord& keys)
{
_keyMappings.erase(std::remove_if(_keyMappings.begin(), _keyMappings.end(), [&keys](const Control::KeyChord& iterKey) {
return keys.Modifiers() == iterKey.Modifiers() && keys.Vkey() == iterKey.Vkey();
}),
_keyMappings.end());
}
// Function Description:
// - Keys is the Command's identifying KeyChord. The command may have multiple keys associated
// with it, but we'll only ever display the most recently added one externally. To do this,
// _keyMappings stores all of the associated key chords, but ensures that the last entry
// is the most recently added one.
// Arguments:
// - <none>
// Return Value:
// - the primary key chord associated with this Command
Control::KeyChord Command::Keys() const noexcept
{
if (_keyMappings.empty())
{
return nullptr;
}
return _keyMappings.back();
}
hstring Command::KeyChordText() const noexcept
{
return KeyChordSerialization::ToString(Keys());
}
hstring Command::IconPath() const noexcept
{
if (_iconPath.has_value())
@@ -281,8 +211,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// - the newly constructed Command object.
winrt::com_ptr<Command> Command::FromJson(const Json::Value& json,
std::vector<SettingsLoadWarnings>& warnings,
const OriginTag origin,
const bool parseKeys)
const OriginTag origin)
{
auto result = winrt::make_self<Command>();
result->_Origin = origin;
@@ -338,26 +267,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// create an "invalid" ActionAndArgs
result->_ActionAndArgs = make<implementation::ActionAndArgs>();
}
if (parseKeys)
{
// GH#4239 - If the user provided more than one key
// chord to a "keys" array, warn the user here.
// TODO: GH#1334 - remove this check.
const auto keysJson{ json[JsonKey(KeysKey)] };
if (keysJson.isArray() && keysJson.size() > 1)
{
warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord);
}
else
{
Control::KeyChord keys{ nullptr };
if (JsonUtils::GetValueForKey(json, KeysKey, keys))
{
result->RegisterKey(keys);
}
}
}
}
// If an iterable command doesn't have a name set, we'll still just
@@ -423,14 +332,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
// Function Description:
// - Serialize the Command into an array of json actions
// - Serialize the Command into a json value
// Arguments:
// - <none>
// Return Value:
// - an array of serialized actions
// - a serialized command
Json::Value Command::ToJson() const
{
Json::Value cmdList{ Json::ValueType::arrayValue };
Json::Value cmdJson{ Json::ValueType::objectValue };
if (_nestedCommand || _IterateOn != ExpandCommandType::None)
{
@@ -438,15 +347,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// For these, we can trust _originalJson to be correct.
// In fact, we _need_ to use it here because we don't actually deserialize `iterateOn`
// until we expand the command.
cmdList.append(_originalJson);
cmdJson = _originalJson;
}
else if (_keyMappings.empty())
else
{
// only write out one command
Json::Value cmdJson{ Json::ValueType::objectValue };
JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath);
JsonUtils::SetValueForKey(cmdJson, NameKey, _name);
if (!_ID.empty() && !_IDWasGenerated)
if (!_ID.empty())
{
JsonUtils::SetValueForKey(cmdJson, IDKey, _ID);
}
@@ -455,38 +362,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs);
}
cmdList.append(cmdJson);
}
else
{
// we'll write out one command per key mapping
for (auto keys{ _keyMappings.begin() }; keys != _keyMappings.end(); ++keys)
{
Json::Value cmdJson{ Json::ValueType::objectValue };
if (keys == _keyMappings.begin())
{
// First iteration also writes icon and name
JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath);
JsonUtils::SetValueForKey(cmdJson, NameKey, _name);
if (!_ID.empty())
{
JsonUtils::SetValueForKey(cmdJson, IDKey, _ID);
}
}
if (_ActionAndArgs)
{
cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs);
}
JsonUtils::SetValueForKey(cmdJson, KeysKey, *keys);
cmdList.append(cmdJson);
}
}
return cmdList;
return cmdJson;
}
// Function Description:
@@ -828,4 +706,33 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return winrt::single_threaded_vector<Model::Command>(std::move(result));
}
IVector<Model::Command> Command::ParseLocalCommands(winrt::hstring localTasksFileContents)
{
auto data = winrt::to_string(localTasksFileContents);
std::string errs;
static std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
Json::Value root;
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
{
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
}
auto result = std::vector<Model::Command>();
if (auto actions{ root[JsonKey("actions")] })
{
std::vector<SettingsLoadWarnings> warnings;
for (const auto& json : actions)
{
auto parsed = Command::FromJson(json, warnings, OriginTag::Generated);
if (parsed->ActionAndArgs().Action() != ShortcutAction::SendInput)
continue;
// commands.Append(*parsed);
result.push_back(*parsed);
}
}
return winrt::single_threaded_vector<Model::Command>(std::move(result));
}
}

View File

@@ -31,6 +31,14 @@ namespace SettingsModelUnitTests
class CommandTests;
};
static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view IDKey{ "id" };
static constexpr std::string_view IconKey{ "icon" };
static constexpr std::string_view ActionKey{ "command" };
static constexpr std::string_view IterateOnKey{ "iterateOn" };
static constexpr std::string_view CommandsKey{ "commands" };
static constexpr std::string_view KeysKey{ "keys" };
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
struct Command : CommandT<Command>
@@ -40,8 +48,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::com_ptr<Command> FromJson(const Json::Value& json,
std::vector<SettingsLoadWarnings>& warnings,
const OriginTag origin,
const bool parseKeys = true);
const OriginTag origin);
static void ExpandCommands(Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command>& commands,
Windows::Foundation::Collections::IVectorView<Model::Profile> profiles,
@@ -62,13 +69,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void Name(const hstring& name);
hstring ID() const noexcept;
bool GenerateID();
Control::KeyChord Keys() const noexcept;
hstring KeyChordText() const noexcept;
std::vector<Control::KeyChord> KeyMappings() const noexcept;
void RegisterKey(const Control::KeyChord& keys);
void EraseKey(const Control::KeyChord& keys);
void GenerateID();
bool IdWasGenerated();
hstring IconPath() const noexcept;
void IconPath(const hstring& val);
@@ -77,6 +79,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static Windows::Foundation::Collections::IVector<Model::Command> HistoryToCommands(Windows::Foundation::Collections::IVector<winrt::hstring> history,
winrt::hstring currentCommandline,
bool directories);
static Windows::Foundation::Collections::IVector<Model::Command> ParseLocalCommands(winrt::hstring localTasksFileContents);
WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);
@@ -85,10 +88,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
private:
Json::Value _originalJson;
Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command> _subcommands{ nullptr };
std::vector<Control::KeyChord> _keyMappings;
std::optional<std::wstring> _name;
std::wstring _ID;
bool _IDWasGenerated{ false };
bool _IdWasGenerated{ false };
std::optional<std::wstring> _iconPath;
bool _nestedCommand{ false };

View File

@@ -38,9 +38,6 @@ namespace Microsoft.Terminal.Settings.Model
String Name { get; };
String ID { get; };
ActionAndArgs ActionAndArgs { get; };
Microsoft.Terminal.Control.KeyChord Keys { get; };
void RegisterKey(Microsoft.Terminal.Control.KeyChord keys);
String KeyChordText { get; };
String IconPath;
@@ -49,6 +46,5 @@ namespace Microsoft.Terminal.Settings.Model
static IVector<Command> ParsePowerShellMenuComplete(String json, Int32 replaceLength);
static IVector<Command> HistoryToCommands(IVector<String> commandHistory, String commandline, Boolean directories);
}
}

View File

@@ -45,6 +45,7 @@ void GlobalAppSettings::_FinalizeInheritance()
}
}
}
_actionMap->_FinalizeInheritance();
}
winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
@@ -155,7 +156,9 @@ void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origi
void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings)
{
static constexpr std::array bindingsKeys{ LegacyKeybindingsKey, ActionsKey };
// we want to do the keybindings map after the actions map so that we overwrite any leftover keybindings
// that might have existed in the first pass, in case the user did a partial update from legacy to modern
static constexpr std::array bindingsKeys{ ActionsKey, LegacyKeybindingsKey };
for (const auto& jsonKey : bindingsKeys)
{
if (auto bindings{ json[JsonKey(jsonKey)] })
@@ -259,9 +262,16 @@ Json::Value GlobalAppSettings::ToJson()
#undef GLOBAL_SETTINGS_TO_JSON
json[JsonKey(ActionsKey)] = _actionMap->ToJson();
json[JsonKey(LegacyKeybindingsKey)] = _actionMap->KeyBindingsToJson();
return json;
}
bool GlobalAppSettings::FixUpsAppliedDuringLoad()
{
return _actionMap->FixUpsAppliedDuringLoad();
}
winrt::Microsoft::Terminal::Settings::Model::Theme GlobalAppSettings::CurrentTheme() noexcept
{
auto requestedTheme = Model::Theme::IsSystemInDarkTheme() ?

View File

@@ -53,6 +53,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true);
Json::Value ToJson();
bool FixUpsAppliedDuringLoad();
const std::vector<SettingsLoadWarnings>& KeybindingsWarnings() const;

View File

@@ -28,6 +28,9 @@
<ClInclude Include="SeparatorEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ActionEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude>
<ClInclude Include="FolderEntry.h">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClInclude>
@@ -185,6 +188,9 @@
<ClCompile Include="SeparatorEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ActionEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile>
<ClCompile Include="FolderEntry.cpp">
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
</ClCompile>

View File

@@ -8,6 +8,7 @@
#include "SeparatorEntry.h"
#include "FolderEntry.h"
#include "ProfileEntry.h"
#include "ActionEntry.h"
#include "RemainingProfilesEntry.h"
#include "MatchProfilesEntry.h"
@@ -52,6 +53,8 @@ winrt::com_ptr<NewTabMenuEntry> NewTabMenuEntry::FromJson(const Json::Value& jso
return RemainingProfilesEntry::FromJson(json);
case NewTabMenuEntryType::MatchProfiles:
return MatchProfilesEntry::FromJson(json);
case NewTabMenuEntryType::Action:
return ActionEntry::FromJson(json);
default:
return nullptr;
}

View File

@@ -12,7 +12,8 @@ namespace Microsoft.Terminal.Settings.Model
Separator,
Folder,
RemainingProfiles,
MatchProfiles
MatchProfiles,
Action
};
[default_interface] unsealed runtimeclass NewTabMenuEntry
@@ -34,6 +35,13 @@ namespace Microsoft.Terminal.Settings.Model
Int32 ProfileIndex;
}
[default_interface] runtimeclass ActionEntry : NewTabMenuEntry
{
ActionEntry();
String ActionId;
}
enum FolderEntryInlining
{
Never = 0,

View File

@@ -501,9 +501,10 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FindMatchDirecti
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::SuggestionsSource)
{
static constexpr std::array<pair_type, 5> mappings = {
static constexpr std::array<pair_type, 6> mappings = {
pair_type{ "none", AllClear },
pair_type{ "tasks", ValueType::Tasks },
pair_type{ "snippets", ValueType::Tasks },
pair_type{ "commandHistory", ValueType::CommandHistory },
pair_type{ "directoryHistory", ValueType::DirectoryHistory },
pair_type{ "all", AllSet },
@@ -678,8 +679,9 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ScrollToMarkDirection)
// Possible NewTabMenuEntryType values
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType)
{
JSON_MAPPINGS(5) = {
JSON_MAPPINGS(6) = {
pair_type{ "profile", ValueType::Profile },
pair_type{ "action", ValueType::Action },
pair_type{ "separator", ValueType::Separator },
pair_type{ "folder", ValueType::Folder },
pair_type{ "remainingProfiles", ValueType::RemainingProfiles },

View File

@@ -422,28 +422,27 @@
],
"actions":
[
// Application-level Keys
{ "command": "closeWindow", "keys": "alt+f4", "id": "Terminal.CloseWindow" },
{ "command": "toggleFullscreen", "keys": "alt+enter", "id": "Terminal.ToggleFullscreen" },
{ "command": "toggleFullscreen", "keys": "f11", "id": "Terminal.ToggleFullscreen" },
// Application-level Commands
{ "command": "closeWindow", "id": "Terminal.CloseWindow" },
{ "command": "toggleFullscreen", "id": "Terminal.ToggleFullscreen" },
{ "command": "toggleFocusMode", "id": "Terminal.ToggleFocusMode" },
{ "command": "toggleAlwaysOnTop", "id": "Terminal.ToggleAlwaysOnTop" },
{ "command": "openNewTabDropdown", "keys": "ctrl+shift+space", "id": "Terminal.OpenNewTabDropdown" },
{ "command": { "action": "openSettings", "target": "settingsUI" }, "keys": "ctrl+,", "id": "Terminal.OpenSettingsUI" },
{ "command": { "action": "openSettings", "target": "settingsFile" }, "keys": "ctrl+shift+,", "id": "Terminal.OpenSettingsFile" },
{ "command": { "action": "openSettings", "target": "defaultsFile" }, "keys": "ctrl+alt+,", "id": "Terminal.OpenDefaultSettingsFile" },
{ "command": "find", "keys": "ctrl+shift+f", "id": "Terminal.FindText" },
{ "command": "openNewTabDropdown", "id": "Terminal.OpenNewTabDropdown" },
{ "command": { "action": "openSettings", "target": "settingsUI" }, "id": "Terminal.OpenSettingsUI" },
{ "command": { "action": "openSettings", "target": "settingsFile" }, "id": "Terminal.OpenSettingsFile" },
{ "command": { "action": "openSettings", "target": "defaultsFile" }, "id": "Terminal.OpenDefaultSettingsFile" },
{ "command": "find", "id": "Terminal.FindText" },
{ "command": { "action": "findMatch", "direction": "next" }, "id": "Terminal.FindNextMatch" },
{ "command": { "action": "findMatch", "direction": "prev" }, "id": "Terminal.FindPrevMatch" },
{ "command": "toggleShaderEffects", "id": "Terminal.ToggleShaderEffects" },
{ "command": "openTabColorPicker", "id": "Terminal.OpenTabColorPicker" },
{ "command": "renameTab", "id": "Terminal.RenameTab" },
{ "command": "openTabRenamer", "id": "Terminal.OpenTabRenamer" },
{ "command": "commandPalette", "keys":"ctrl+shift+p", "id": "Terminal.ToggleCommandPalette" },
{ "command": "commandPalette", "id": "Terminal.ToggleCommandPalette" },
{ "command": "identifyWindow", "id": "Terminal.IdentifyWindow" },
{ "command": "openWindowRenamer", "id": "Terminal.OpenWindowRenamer" },
{ "command": "quakeMode", "keys":"win+sc(41)", "id": "Terminal.QuakeMode" },
{ "command": "openSystemMenu", "keys": "alt+space", "id": "Terminal.OpenSystemMenu" },
{ "command": "quakeMode", "id": "Terminal.QuakeMode" },
{ "command": "openSystemMenu", "id": "Terminal.OpenSystemMenu" },
{ "command": "quit", "id": "Terminal.Quit" },
{ "command": "restoreLastClosed", "id": "Terminal.RestoreLastClosed" },
{ "command": "openAbout", "id": "Terminal.OpenAboutDialog" },
@@ -455,49 +454,50 @@
{ "command": "closeTabsAfter", "id": "Terminal.CloseTabsAfter" },
{ "command": { "action" : "moveTab", "direction": "forward" }, "id": "Terminal.MoveTabForward" },
{ "command": { "action" : "moveTab", "direction": "backward" }, "id": "Terminal.MoveTabBackward" },
{ "command": "newTab", "keys": "ctrl+shift+t", "id": "Terminal.OpenNewTab" },
{ "command": "newWindow", "keys": "ctrl+shift+n", "id": "Terminal.OpenNewWindow" },
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1", "id": "Terminal.OpenNewTabProfile0" },
{ "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2", "id": "Terminal.OpenNewTabProfile1" },
{ "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3", "id": "Terminal.OpenNewTabProfile2" },
{ "command": { "action": "newTab", "index": 3 }, "keys": "ctrl+shift+4", "id": "Terminal.OpenNewTabProfile3" },
{ "command": { "action": "newTab", "index": 4 }, "keys": "ctrl+shift+5", "id": "Terminal.OpenNewTabProfile4" },
{ "command": { "action": "newTab", "index": 5 }, "keys": "ctrl+shift+6", "id": "Terminal.OpenNewTabProfile5" },
{ "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7", "id": "Terminal.OpenNewTabProfile6" },
{ "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8", "id": "Terminal.OpenNewTabProfile7" },
{ "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9", "id": "Terminal.OpenNewTabProfile8" },
{ "command": "duplicateTab", "keys": "ctrl+shift+d", "id": "Terminal.DuplicateTab" },
{ "command": "nextTab", "keys": "ctrl+tab", "id": "Terminal.NextTab" },
{ "command": "prevTab", "keys": "ctrl+shift+tab", "id": "Terminal.PrevTab" },
{ "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1", "id": "Terminal.SwitchToTab0" },
{ "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2", "id": "Terminal.SwitchToTab1" },
{ "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3", "id": "Terminal.SwitchToTab2" },
{ "command": { "action": "switchToTab", "index": 3 }, "keys": "ctrl+alt+4", "id": "Terminal.SwitchToTab3" },
{ "command": { "action": "switchToTab", "index": 4 }, "keys": "ctrl+alt+5", "id": "Terminal.SwitchToTab4" },
{ "command": { "action": "switchToTab", "index": 5 }, "keys": "ctrl+alt+6", "id": "Terminal.SwitchToTab5" },
{ "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7", "id": "Terminal.SwitchToTab6" },
{ "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8", "id": "Terminal.SwitchToTab7" },
{ "command": { "action": "switchToTab", "index": 4294967295 }, "keys": "ctrl+alt+9", "id": "Terminal.SwitchToLastTab" },
{ "command": "newTab", "id": "Terminal.OpenNewTab" },
{ "command": "newWindow", "id": "Terminal.OpenNewWindow" },
{ "command": { "action": "newTab", "index": 0 }, "id": "Terminal.OpenNewTabProfile0" },
{ "command": { "action": "newTab", "index": 1 }, "id": "Terminal.OpenNewTabProfile1" },
{ "command": { "action": "newTab", "index": 2 }, "id": "Terminal.OpenNewTabProfile2" },
{ "command": { "action": "newTab", "index": 3 }, "id": "Terminal.OpenNewTabProfile3" },
{ "command": { "action": "newTab", "index": 4 }, "id": "Terminal.OpenNewTabProfile4" },
{ "command": { "action": "newTab", "index": 5 }, "id": "Terminal.OpenNewTabProfile5" },
{ "command": { "action": "newTab", "index": 6 }, "id": "Terminal.OpenNewTabProfile6" },
{ "command": { "action": "newTab", "index": 7 }, "id": "Terminal.OpenNewTabProfile7" },
{ "command": { "action": "newTab", "index": 8 }, "id": "Terminal.OpenNewTabProfile8" },
{ "command": "duplicateTab", "id": "Terminal.DuplicateTab" },
{ "command": "nextTab", "id": "Terminal.NextTab" },
{ "command": "prevTab", "id": "Terminal.PrevTab" },
{ "command": { "action": "switchToTab", "index": 0 }, "id": "Terminal.SwitchToTab0" },
{ "command": { "action": "switchToTab", "index": 1 }, "id": "Terminal.SwitchToTab1" },
{ "command": { "action": "switchToTab", "index": 2 }, "id": "Terminal.SwitchToTab2" },
{ "command": { "action": "switchToTab", "index": 3 }, "id": "Terminal.SwitchToTab3" },
{ "command": { "action": "switchToTab", "index": 4 }, "id": "Terminal.SwitchToTab4" },
{ "command": { "action": "switchToTab", "index": 5 }, "id": "Terminal.SwitchToTab5" },
{ "command": { "action": "switchToTab", "index": 6 }, "id": "Terminal.SwitchToTab6" },
{ "command": { "action": "switchToTab", "index": 7 }, "id": "Terminal.SwitchToTab7" },
{ "command": { "action": "switchToTab", "index": 4294967295 }, "id": "Terminal.SwitchToLastTab" },
{ "command": { "action": "moveTab", "window": "new" }, "id": "Terminal.MoveTabToNewWindow" },
// Pane Management
{ "command": "closeOtherPanes", "id": "Terminal.CloseOtherPanes" },
{ "command": "closePane", "keys": "ctrl+shift+w", "id": "Terminal.ClosePane" },
{ "command": "closePane", "id": "Terminal.ClosePane" },
{ "command": { "action": "splitPane", "split": "up" }, "id": "Terminal.SplitPaneUp" },
{ "command": { "action": "splitPane", "split": "down" }, "id": "Terminal.SplitPaneDown" },
{ "command": { "action": "splitPane", "split": "left" }, "id": "Terminal.SplitPaneLeft" },
{ "command": { "action": "splitPane", "split": "right" }, "id": "Terminal.SplitPaneRight" },
{ "command": { "action": "splitPane", "splitMode": "duplicate", "split": "down" }, "keys": "alt+shift+-", "id": "Terminal.SplitPaneDuplicateDown" },
{ "command": { "action": "splitPane", "splitMode": "duplicate", "split": "right" }, "keys": "alt+shift+plus", "id": "Terminal.SplitPaneDuplicateRight" },
{ "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down", "id": "Terminal.ResizePaneDown" },
{ "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left", "id": "Terminal.ResizePaneLeft" },
{ "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right", "id": "Terminal.ResizePaneRight" },
{ "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up", "id": "Terminal.ResizePaneUp" },
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down", "id": "Terminal.MoveFocusDown" },
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left", "id": "Terminal.MoveFocusLeft" },
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right", "id": "Terminal.MoveFocusRight" },
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up", "id": "Terminal.MoveFocusUp" },
{ "command": { "action": "moveFocus", "direction": "previous" }, "keys": "ctrl+alt+left", "id": "Terminal.MoveFocusPrevious" },
{ "command": { "action": "splitPane", "splitMode": "duplicate", "split": "down" }, "id": "Terminal.DuplicatePaneDown" },
{ "command": { "action": "splitPane", "splitMode": "duplicate", "split": "right" }, "id": "Terminal.DuplicatePaneRight" },
{ "command": { "action": "splitPane", "splitMode": "duplicate", "split": "auto" }, "id": "Terminal.DuplicatePaneAuto" },
{ "command": { "action": "resizePane", "direction": "down" }, "id": "Terminal.ResizePaneDown" },
{ "command": { "action": "resizePane", "direction": "left" }, "id": "Terminal.ResizePaneLeft" },
{ "command": { "action": "resizePane", "direction": "right" }, "id": "Terminal.ResizePaneRight" },
{ "command": { "action": "resizePane", "direction": "up" }, "id": "Terminal.ResizePaneUp" },
{ "command": { "action": "moveFocus", "direction": "down" }, "id": "Terminal.MoveFocusDown" },
{ "command": { "action": "moveFocus", "direction": "left" }, "id": "Terminal.MoveFocusLeft" },
{ "command": { "action": "moveFocus", "direction": "right" }, "id": "Terminal.MoveFocusRight" },
{ "command": { "action": "moveFocus", "direction": "up" }, "id": "Terminal.MoveFocusUp" },
{ "command": { "action": "moveFocus", "direction": "previous" }, "id": "Terminal.MoveFocusPrevious" },
{ "command": { "action": "moveFocus", "direction": "previousInOrder" }, "id": "Terminal.MoveFocusPreviousInOrder" },
{ "command": { "action": "moveFocus", "direction": "nextInOrder" }, "id": "Terminal.MoveFocusNextInOrder" },
{ "command": { "action": "moveFocus", "direction": "first" }, "id": "Terminal.MoveFocusFirst" },
@@ -530,38 +530,35 @@
{ "command": "restartConnection", "id": "Terminal.RestartConnection" },
// Clipboard Integration
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c", "id": "Terminal.CopySelectedText" },
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+insert", "id": "Terminal.CopySelectedText" },
{ "command": { "action": "copy", "singleLine": false }, "keys": "enter", "id": "Terminal.CopySelectedText" },
{ "command": "paste", "keys": "ctrl+shift+v", "id": "Terminal.PasteFromClipboard" },
{ "command": "paste", "keys": "shift+insert", "id": "Terminal.PasteFromClipboard" },
{ "command": "selectAll", "keys": "ctrl+shift+a", "id": "Terminal.SelectAll" },
{ "command": "markMode", "keys": "ctrl+shift+m", "id": "Terminal.ToggleMarkMode" },
{ "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopySelectedText" },
{ "command": "paste", "id": "Terminal.PasteFromClipboard" },
{ "command": "selectAll", "id": "Terminal.SelectAll" },
{ "command": "markMode", "id": "Terminal.ToggleMarkMode" },
{ "command": "toggleBlockSelection", "id": "Terminal.ToggleBlockSelection" },
{ "command": "switchSelectionEndpoint", "id": "Terminal.SwitchSelectionEndpoint" },
{ "command": "expandSelectionToWord", "id": "Terminal.ExpandSelectionToWord" },
{ "command": "showContextMenu", "keys": "menu", "id": "Terminal.ShowContextMenu" },
{ "command": "showContextMenu", "id": "Terminal.ShowContextMenu" },
// Web Search
{ "command": { "action": "searchWeb" }, "name": { "key": "SearchWebCommandKey" }, "id": "Terminal.SearchWeb" },
// Scrollback
{ "command": "scrollDown", "keys": "ctrl+shift+down", "id": "Terminal.ScrollDown" },
{ "command": "scrollDownPage", "keys": "ctrl+shift+pgdn", "id": "Terminal.ScrollDownPage" },
{ "command": "scrollUp", "keys": "ctrl+shift+up", "id": "Terminal.ScrollUp" },
{ "command": "scrollUpPage", "keys": "ctrl+shift+pgup", "id": "Terminal.ScrollUpPage" },
{ "command": "scrollToTop", "keys": "ctrl+shift+home", "id": "Terminal.ScrollToTop" },
{ "command": "scrollToBottom", "keys": "ctrl+shift+end", "id": "Terminal.ScrollToBottom" },
{ "command": "scrollDown", "id": "Terminal.ScrollDown" },
{ "command": "scrollDownPage", "id": "Terminal.ScrollDownPage" },
{ "command": "scrollUp", "id": "Terminal.ScrollUp" },
{ "command": "scrollUpPage", "id": "Terminal.ScrollUpPage" },
{ "command": "scrollToTop", "id": "Terminal.ScrollToTop" },
{ "command": "scrollToBottom", "id": "Terminal.ScrollToBottom" },
{ "command": { "action": "clearBuffer", "clear": "all" }, "id": "Terminal.ClearBuffer" },
{ "command": "exportBuffer", "id": "Terminal.ExportBuffer" },
// Visual Adjustments
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+plus", "id": "Terminal.IncreaseFontSize" },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+minus", "id": "Terminal.DecreaseFontSize" },
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+numpad_plus", "id": "Terminal.IncreaseFontSize" },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+numpad_minus", "id": "Terminal.DecreaseFontSize" },
{ "command": "resetFontSize", "keys": "ctrl+0", "id": "Terminal.ResetFontSize" },
{ "command": "resetFontSize", "keys": "ctrl+numpad_0", "id": "Terminal.ResetFontSize" },
{ "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Terminal.IncreaseFontSize" },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "id": "Terminal.DecreaseFontSize" },
{ "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Terminal.IncreaseFontSize" },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "id": "Terminal.DecreaseFontSize" },
{ "command": "resetFontSize", "id": "Terminal.ResetFontSize" },
{ "command": "resetFontSize", "id": "Terminal.ResetFontSize" },
// Other commands
{
@@ -626,5 +623,86 @@
{ "command": { "action": "adjustOpacity", "opacity": 100, "relative": false } }
]
}
],
"keybindings": [
// Application-level Keys
{ "keys": "alt+f4", "id": "Terminal.CloseWindow" },
{ "keys": "alt+enter", "id": "Terminal.ToggleFullscreen" },
{ "keys": "f11", "id": "Terminal.ToggleFullscreen" },
{ "keys": "ctrl+shift+space", "id": "Terminal.OpenNewTabDropdown" },
{ "keys": "ctrl+,", "id": "Terminal.OpenSettingsUI" },
{ "keys": "ctrl+shift+,", "id": "Terminal.OpenSettingsFile" },
{ "keys": "ctrl+alt+,", "id": "Terminal.OpenDefaultSettingsFile" },
{ "keys": "ctrl+shift+f", "id": "Terminal.FindText" },
{ "keys":"ctrl+shift+p", "id": "Terminal.ToggleCommandPalette" },
{ "keys":"win+sc(41)", "id": "Terminal.QuakeMode" },
{ "keys": "alt+space", "id": "Terminal.OpenSystemMenu" },
// Tab Management
// "command": "closeTab" is unbound by default.
// The closeTab command closes a tab without confirmation, even if it has multiple panes.
{ "keys": "ctrl+shift+t", "id": "Terminal.OpenNewTab" },
{ "keys": "ctrl+shift+n", "id": "Terminal.OpenNewWindow" },
{ "keys": "ctrl+shift+1", "id": "Terminal.OpenNewTabProfile0" },
{ "keys": "ctrl+shift+2", "id": "Terminal.OpenNewTabProfile1" },
{ "keys": "ctrl+shift+3", "id": "Terminal.OpenNewTabProfile2" },
{ "keys": "ctrl+shift+4", "id": "Terminal.OpenNewTabProfile3" },
{ "keys": "ctrl+shift+5", "id": "Terminal.OpenNewTabProfile4" },
{ "keys": "ctrl+shift+6", "id": "Terminal.OpenNewTabProfile5" },
{ "keys": "ctrl+shift+7", "id": "Terminal.OpenNewTabProfile6" },
{ "keys": "ctrl+shift+8", "id": "Terminal.OpenNewTabProfile7" },
{ "keys": "ctrl+shift+9", "id": "Terminal.OpenNewTabProfile8" },
{ "keys": "ctrl+shift+d", "id": "Terminal.DuplicateTab" },
{ "keys": "ctrl+tab", "id": "Terminal.NextTab" },
{ "keys": "ctrl+shift+tab", "id": "Terminal.PrevTab" },
{ "keys": "ctrl+alt+1", "id": "Terminal.SwitchToTab0" },
{ "keys": "ctrl+alt+2", "id": "Terminal.SwitchToTab1" },
{ "keys": "ctrl+alt+3", "id": "Terminal.SwitchToTab2" },
{ "keys": "ctrl+alt+4", "id": "Terminal.SwitchToTab3" },
{ "keys": "ctrl+alt+5", "id": "Terminal.SwitchToTab4" },
{ "keys": "ctrl+alt+6", "id": "Terminal.SwitchToTab5" },
{ "keys": "ctrl+alt+7", "id": "Terminal.SwitchToTab6" },
{ "keys": "ctrl+alt+8", "id": "Terminal.SwitchToTab7" },
{ "keys": "ctrl+alt+9", "id": "Terminal.SwitchToLastTab" },
// Pane Management
{ "keys": "ctrl+shift+w", "id": "Terminal.ClosePane" },
{ "keys": "alt+shift+-", "id": "Terminal.DuplicatePaneDown" },
{ "keys": "alt+shift+plus", "id": "Terminal.DuplicatePaneRight" },
{ "keys": "alt+shift+down", "id": "Terminal.ResizePaneDown" },
{ "keys": "alt+shift+left", "id": "Terminal.ResizePaneLeft" },
{ "keys": "alt+shift+right", "id": "Terminal.ResizePaneRight" },
{ "keys": "alt+shift+up", "id": "Terminal.ResizePaneUp" },
{ "keys": "alt+down", "id": "Terminal.MoveFocusDown" },
{ "keys": "alt+left", "id": "Terminal.MoveFocusLeft" },
{ "keys": "alt+right", "id": "Terminal.MoveFocusRight" },
{ "keys": "alt+up", "id": "Terminal.MoveFocusUp" },
{ "keys": "ctrl+alt+left", "id": "Terminal.MoveFocusPrevious" },
// Clipboard Integration
{ "keys": "ctrl+shift+c", "id": "Terminal.CopySelectedText" },
{ "keys": "ctrl+insert", "id": "Terminal.CopySelectedText" },
{ "keys": "enter", "id": "Terminal.CopySelectedText" },
{ "keys": "ctrl+shift+v", "id": "Terminal.PasteFromClipboard" },
{ "keys": "shift+insert", "id": "Terminal.PasteFromClipboard" },
{ "keys": "ctrl+shift+a", "id": "Terminal.SelectAll" },
{ "keys": "ctrl+shift+m", "id": "Terminal.ToggleMarkMode" },
{ "keys": "menu", "id": "Terminal.ShowContextMenu" },
// Scrollback
{ "keys": "ctrl+shift+down", "id": "Terminal.ScrollDown" },
{ "keys": "ctrl+shift+pgdn", "id": "Terminal.ScrollDownPage" },
{ "keys": "ctrl+shift+up", "id": "Terminal.ScrollUp" },
{ "keys": "ctrl+shift+pgup", "id": "Terminal.ScrollUpPage" },
{ "keys": "ctrl+shift+home", "id": "Terminal.ScrollToTop" },
{ "keys": "ctrl+shift+end", "id": "Terminal.ScrollToBottom" },
// Visual Adjustments
{ "keys": "ctrl+plus", "id": "Terminal.IncreaseFontSize" },
{ "keys": "ctrl+minus", "id": "Terminal.DecreaseFontSize" },
{ "keys": "ctrl+numpad_plus", "id": "Terminal.IncreaseFontSize" },
{ "keys": "ctrl+numpad_minus", "id": "Terminal.DecreaseFontSize" },
{ "keys": "ctrl+0", "id": "Terminal.ResetFontSize" },
{ "keys": "ctrl+numpad_0", "id": "Terminal.ResetFontSize" },
]
}

View File

@@ -38,6 +38,9 @@
<ClInclude Include="../Profile.h" />
<ClInclude Include="../TerminalWarnings.h" />
<ClInclude Include="../NewTabMenuEntry.h" />
<ClInclude Include="../ActionEntry.h">
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
</ClInclude>
<ClInclude Include="../SeparatorEntry.h">
<DependentUpon>../NewTabMenuEntry.h</DependentUpon>
</ClInclude>

View File

@@ -21,11 +21,10 @@
}
]
},
"actions":
"keybindings":
[
{ "command": {"action": "copy", "singleLine": false }, "keys": "ctrl+c" },
{ "command": "paste", "keys": "ctrl+v" },
{ "command": "find", "keys": "ctrl+shift+f" },
{ "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "keys": "alt+shift+d" }
{ "id": "Terminal.CopySelectedText", "keys": "ctrl+c" },
{ "id": "Terminal.PasteFromClipboard", "keys": "ctrl+v" },
{ "id": "Terminal.DuplicatePaneAuto", "keys": "alt+shift+d" }
]
}

View File

@@ -58,6 +58,7 @@ namespace SettingsModelUnitTests
TEST_METHOD(TestCloneInheritanceTree);
TEST_METHOD(TestValidDefaults);
TEST_METHOD(TestInheritedCommand);
TEST_METHOD(TestOverwriteParentCommandAndKeybinding);
TEST_METHOD(LoadFragmentsWithMultipleUpdates);
TEST_METHOD(FragmentActionSimple);
@@ -1235,11 +1236,11 @@ namespace SettingsModelUnitTests
const auto settings = createSettings(badSettings);
// KeyMap: ctrl+a/b are mapped to "invalid"
// ActionMap: "splitPane" and "invalid" are the only deserialized actions
// ActionMap: "splitPane" is the only deserialized action
// NameMap: "splitPane" has no key binding, but it is still added to the name map
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap());
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
VERIFY_ARE_EQUAL(2u, actionMap->_ActionMap.size());
VERIFY_ARE_EQUAL(1u, actionMap->_ActionMap.size());
VERIFY_ARE_EQUAL(1u, actionMap->NameMap().Size());
VERIFY_ARE_EQUAL(5u, settings->Warnings().Size());
@@ -1981,7 +1982,8 @@ namespace SettingsModelUnitTests
},
{
"name": "bar",
"command": "closePane"
"command": "closePane",
"id": "Test.ClosePane"
},
],
})" };
@@ -2005,11 +2007,108 @@ namespace SettingsModelUnitTests
}
{
// Verify ActionMap::GetKeyBindingForAction API
const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(ShortcutAction::ClosePane) };
const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(L"Test.ClosePane") };
VERIFY_IS_NULL(actualKeyChord);
}
}
void DeserializationTests::TestOverwriteParentCommandAndKeybinding()
{
// Tests:
// - Redefine an action whose ID was originally defined in another layer
// - Redefine a keychord that exists in another layer
// - Define a keychord that points to an action in another layer
static constexpr std::string_view settings1Json{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1,
"commandline": "cmd.exe"
}
],
"actions": [
{
"command": "closePane",
"id": "Parent.ClosePane"
},
{
"command": "closePane",
"id": "Parent.ClosePane2"
}
],
"keybindings": [
{
"keys": "ctrl+shift+w",
"id": "Parent.ClosePane"
},
{
"keys": "ctrl+shift+x",
"id": "Parent.ClosePane2"
}
]
})" };
// this child actions and keybindings list
// - redefines Parent.ClosePane to perform a newTab action instead of a closePane action
// - redefines ctrl+shift+x to point to Child.ClosePane instead of Parent.ClosePane2
// - defines ctrl+shift+y to point to Parent.ClosePane2 (an action that does not exist in this child layer)
static constexpr std::string_view settings2Json{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"actions": [
{
"command": "newTab",
"id": "Parent.ClosePane"
},
{
"command": "closePane",
"id": "Child.ClosePane"
}
],
"keybindings": [
{
"id": "Child.ClosePane",
"keys": "ctrl+shift+x"
},
{
"id": "Parent.ClosePane2",
"keys": "ctrl+shift+y"
}
]
})" };
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settings2Json, settings1Json);
const KeyChord ctrlShiftW{ true, false, true, false, static_cast<int>('W'), 0 };
const KeyChord ctrlShiftX{ true, false, true, false, static_cast<int>('X'), 0 };
const KeyChord ctrlShiftY{ true, false, true, false, static_cast<int>('Y'), 0 };
{
// ctrl+shift+w should point to Parent.ClosePane, however Parent.ClosePane should be a newTab action
const auto& cmd{ settings->ActionMap().GetActionByKeyChord(ctrlShiftW) };
VERIFY_IS_NOT_NULL(cmd);
VERIFY_ARE_EQUAL(cmd.ID(), L"Parent.ClosePane");
VERIFY_ARE_EQUAL(cmd.ActionAndArgs().Action(), ShortcutAction::NewTab);
}
{
// ctrl+shift+x should point to Child.ClosePane
const auto& cmd{ settings->ActionMap().GetActionByKeyChord(ctrlShiftX) };
VERIFY_IS_NOT_NULL(cmd);
VERIFY_ARE_EQUAL(cmd.ID(), L"Child.ClosePane");
VERIFY_ARE_EQUAL(cmd.ActionAndArgs().Action(), ShortcutAction::ClosePane);
}
{
// ctrl+shift+y should point to Parent.ClosePane2
const auto& cmd{ settings->ActionMap().GetActionByKeyChord(ctrlShiftY) };
VERIFY_IS_NOT_NULL(cmd);
VERIFY_ARE_EQUAL(cmd.ID(), L"Parent.ClosePane2");
VERIFY_ARE_EQUAL(cmd.ActionAndArgs().Action(), ShortcutAction::ClosePane);
}
}
// This test ensures GH#11597, GH#12520 don't regress.
void DeserializationTests::LoadFragmentsWithMultipleUpdates()
{
@@ -2049,7 +2148,8 @@ namespace SettingsModelUnitTests
"actions": [
{
"command": { "action": "addMark" },
"name": "Test Action"
"name": "Test Action",
"id": "Test.FragmentAction"
},
]
})" };
@@ -2074,6 +2174,7 @@ namespace SettingsModelUnitTests
{
"command": { "action": "addMark" },
"keys": "ctrl+f",
"id": "Test.FragmentAction",
"name": "Test Action"
},
]
@@ -2195,7 +2296,8 @@ namespace SettingsModelUnitTests
"actions": [
{
"command": { "action": "addMark" },
"name": "Test Action"
"name": "Test Action",
"id": "Test.FragmentAction"
},
]
})" };

View File

@@ -157,17 +157,17 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::HashDeduplication()
{
const auto actionMap = winrt::make_self<implementation::ActionMap>();
actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None);
actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None);
actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::User);
actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::User);
VERIFY_ARE_EQUAL(1u, actionMap->_ActionMap.size());
}
void KeyBindingsTests::HashContentArgs()
{
Log::Comment(L"These are two actions with different content args. They should have different hashes for their terminal args.");
Log::Comment(L"These are two actions with different content args. They should have different generated IDs for their terminal args.");
const auto actionMap = winrt::make_self<implementation::ActionMap>();
actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", } , "keys": ["ctrl+c"] } ])"), OriginTag::None);
actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", "index": 0 } , "keys": ["ctrl+shift+c"] } ])"), OriginTag::None);
actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", } , "keys": ["ctrl+c"] } ])"), OriginTag::User);
actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", "index": 0 } , "keys": ["ctrl+shift+c"] } ])"), OriginTag::User);
VERIFY_ARE_EQUAL(2u, actionMap->_ActionMap.size());
KeyChord ctrlC{ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 };
@@ -271,32 +271,32 @@ namespace SettingsModelUnitTests
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings0Json, OriginTag::None);
actionMap->LayerJson(bindings0Json, OriginTag::User);
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings1Json, OriginTag::None);
actionMap->LayerJson(bindings1Json, OriginTag::User);
VERIFY_IS_TRUE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings2Json, OriginTag::None);
actionMap->LayerJson(bindings2Json, OriginTag::User);
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
}
void KeyBindingsTests::TestArbitraryArgs()
{
const std::string bindings0String{ R"([
{ "command": "copy", "keys": ["ctrl+c"] },
{ "command": { "action": "copy", "singleLine": false }, "keys": ["ctrl+shift+c"] },
{ "command": { "action": "copy", "singleLine": true }, "keys": ["alt+shift+c"] },
{ "command": "copy", "id": "Test.CopyNoArgs", "keys": ["ctrl+c"] },
{ "command": { "action": "copy", "singleLine": false }, "id": "Test.CopyMultiline", "keys": ["ctrl+shift+c"] },
{ "command": { "action": "copy", "singleLine": true }, "id": "Test.CopySingleline", "keys": ["alt+shift+c"] },
{ "command": "newTab", "keys": ["ctrl+t"] },
{ "command": { "action": "newTab", "index": 0 }, "keys": ["ctrl+shift+t"] },
{ "command": { "action": "newTab", "index": 11 }, "keys": ["ctrl+shift+y"] },
{ "command": "newTab", "id": "Test.NewTabNoArgs", "keys": ["ctrl+t"] },
{ "command": { "action": "newTab", "index": 0 }, "id": "Test.NewTab0", "keys": ["ctrl+shift+t"] },
{ "command": { "action": "newTab", "index": 11 }, "id": "Test.NewTab11", "keys": ["ctrl+shift+y"] },
{ "command": { "action": "copy", "madeUpBool": true }, "keys": ["ctrl+b"] },
{ "command": { "action": "copy" }, "keys": ["ctrl+shift+b"] },
{ "command": { "action": "copy", "madeUpBool": true }, "id": "Test.CopyFakeArgs", "keys": ["ctrl+b"] },
{ "command": { "action": "copy" }, "id": "Test.CopyNullArgs", "keys": ["ctrl+shift+b"] },
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": ["ctrl+f"] },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "keys": ["ctrl+g"] }
{ "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Test.EnlargeFont", "keys": ["ctrl+f"] },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "id": "Test.ReduceFont", "keys": ["ctrl+g"] }
])" };
@@ -428,10 +428,10 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::TestSplitPaneArgs()
{
const std::string bindings0String{ R"([
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal" } },
{ "keys": ["ctrl+g"], "command": { "action": "splitPane" } },
{ "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } }
{ "keys": ["ctrl+d"], "id": "Test.SplitPaneVertical", "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+e"], "id": "Test.SplitPaneHorizontal", "command": { "action": "splitPane", "split": "horizontal" } },
{ "keys": ["ctrl+g"], "id": "Test.SplitPane", "command": { "action": "splitPane" } },
{ "keys": ["ctrl+h"], "id": "Test.SplitPaneAuto", "command": { "action": "splitPane", "split": "auto" } }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@@ -478,9 +478,9 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::TestSetTabColorArgs()
{
const std::string bindings0String{ R"([
{ "keys": ["ctrl+c"], "command": { "action": "setTabColor", "color": null } },
{ "keys": ["ctrl+d"], "command": { "action": "setTabColor", "color": "#123456" } },
{ "keys": ["ctrl+f"], "command": "setTabColor" },
{ "keys": ["ctrl+c"], "id": "Test.SetTabColorNull", "command": { "action": "setTabColor", "color": null } },
{ "keys": ["ctrl+d"], "id": "Test.SetTabColor", "command": { "action": "setTabColor", "color": "#123456" } },
{ "keys": ["ctrl+f"], "id": "Test.SetTabColorNoArgs", "command": "setTabColor" },
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@@ -521,7 +521,7 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::TestStringOverload()
{
const std::string bindings0String{ R"([
{ "command": "copy", "keys": "ctrl+c" }
{ "command": "copy", "id": "Test.Copy", "keys": "ctrl+c" }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@@ -543,12 +543,12 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::TestScrollArgs()
{
const std::string bindings0String{ R"([
{ "keys": ["up"], "command": "scrollUp" },
{ "keys": ["down"], "command": "scrollDown" },
{ "keys": ["ctrl+up"], "command": { "action": "scrollUp" } },
{ "keys": ["ctrl+down"], "command": { "action": "scrollDown" } },
{ "keys": ["ctrl+shift+up"], "command": { "action": "scrollUp", "rowsToScroll": 10 } },
{ "keys": ["ctrl+shift+down"], "command": { "action": "scrollDown", "rowsToScroll": 10 } }
{ "keys": ["up"], "id": "Test.ScrollUp0", "command": "scrollUp" },
{ "keys": ["down"], "id": "Test.ScrollDown0", "command": "scrollDown" },
{ "keys": ["ctrl+up"], "id": "Test.ScrollUp1", "command": { "action": "scrollUp" } },
{ "keys": ["ctrl+down"], "id": "Test.ScrollDown1", "command": { "action": "scrollDown" } },
{ "keys": ["ctrl+shift+up"], "id": "Test.ScrollUp2", "command": { "action": "scrollUp", "rowsToScroll": 10 } },
{ "keys": ["ctrl+shift+down"], "id": "Test.ScrollDown2", "command": { "action": "scrollDown", "rowsToScroll": 10 } }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@@ -620,8 +620,8 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::TestMoveTabArgs()
{
const std::string bindings0String{ R"([
{ "keys": ["up"], "command": { "action": "moveTab", "direction": "forward" } },
{ "keys": ["down"], "command": { "action": "moveTab", "direction": "backward" } }
{ "keys": ["up"], "id": "Test.MoveTabUp", "command": { "action": "moveTab", "direction": "forward" } },
{ "keys": ["down"], "id": "Test.MoveTabDown", "command": { "action": "moveTab", "direction": "backward" } }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@@ -665,9 +665,9 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::TestToggleCommandPaletteArgs()
{
const std::string bindings0String{ R"([
{ "keys": ["up"], "command": "commandPalette" },
{ "keys": ["ctrl+up"], "command": { "action": "commandPalette", "launchMode" : "action" } },
{ "keys": ["ctrl+shift+up"], "command": { "action": "commandPalette", "launchMode" : "commandLine" } }
{ "keys": ["up"], "id": "Test.CmdPal", "command": "commandPalette" },
{ "keys": ["ctrl+up"], "id": "Test.CmdPalActionMode", "command": { "action": "commandPalette", "launchMode" : "action" } },
{ "keys": ["ctrl+shift+up"], "id": "Test.CmdPalLineMode", "command": { "action": "commandPalette", "launchMode" : "commandLine" } }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@@ -712,10 +712,10 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::TestGetKeyBindingForAction()
{
const std::string bindings0String{ R"([ { "command": "closeWindow", "keys": "ctrl+a" } ])" };
const std::string bindings1String{ R"([ { "command": { "action": "copy", "singleLine": true }, "keys": "ctrl+b" } ])" };
const std::string bindings2String{ R"([ { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+c" } ])" };
const std::string bindings3String{ R"([ { "command": "commandPalette", "keys": "ctrl+shift+p" } ])" };
const std::string bindings0String{ R"([ { "command": "closeWindow", "id": "Test.CloseWindow", "keys": "ctrl+a" } ])" };
const std::string bindings1String{ R"([ { "command": { "action": "copy", "singleLine": true }, "id": "Test.Copy", "keys": "ctrl+b" } ])" };
const std::string bindings2String{ R"([ { "command": { "action": "newTab", "index": 0 }, "id": "Test.NewTab", "keys": "ctrl+c" } ])" };
const std::string bindings3String{ R"([ { "command": "commandPalette", "id": "Test.CmdPal", "keys": "ctrl+shift+p" } ])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
@@ -742,7 +742,7 @@ namespace SettingsModelUnitTests
Log::Comment(L"simple command: no args");
actionMap->LayerJson(bindings0Json, OriginTag::None);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) };
const auto& kbd{ actionMap->GetKeyBindingForAction(L"Test.CloseWindow") };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }, kbd);
}
{
@@ -750,10 +750,7 @@ namespace SettingsModelUnitTests
actionMap->LayerJson(bindings1Json, OriginTag::None);
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
auto args{ winrt::make_self<implementation::CopyTextArgs>() };
args->SingleLine(true);
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) };
const auto& kbd{ actionMap->GetKeyBindingForAction(L"Test.Copy") };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }, kbd);
}
{
@@ -761,11 +758,7 @@ namespace SettingsModelUnitTests
actionMap->LayerJson(bindings2Json, OriginTag::None);
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
auto newTerminalArgs{ winrt::make_self<implementation::NewTerminalArgs>() };
newTerminalArgs->ProfileIndex(0);
auto args{ winrt::make_self<implementation::NewTabArgs>(*newTerminalArgs) };
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) };
const auto& kbd{ actionMap->GetKeyBindingForAction(L"Test.NewTab") };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }, kbd);
}
{
@@ -773,7 +766,7 @@ namespace SettingsModelUnitTests
actionMap->LayerJson(bindings3Json, OriginTag::None);
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
const auto& kbd{ actionMap->GetKeyBindingForAction(L"Test.CmdPal") };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P'), 0 }, kbd);
}
}
@@ -807,7 +800,7 @@ namespace SettingsModelUnitTests
void KeyBindingsTests::KeybindingsWithoutVkey()
{
const auto json = VerifyParseSucceeded(R"!([{"command": "quakeMode", "keys":"shift+sc(255)"}])!");
const auto json = VerifyParseSucceeded(R"!([{"command": "quakeMode", "id": "Test.NoVKey", "keys":"shift+sc(255)"}])!");
const auto actionMap = winrt::make_self<implementation::ActionMap>();
actionMap->LayerJson(json, OriginTag::None);

View File

@@ -47,6 +47,8 @@ namespace SettingsModelUnitTests
TEST_METHOD(RoundtripGenerateActionID);
TEST_METHOD(NoGeneratedIDsForIterableAndNestedCommands);
TEST_METHOD(GeneratedActionIDsEqualForIdenticalCommands);
TEST_METHOD(RoundtripLegacyToModernActions);
TEST_METHOD(MultipleActionsAreCollapsed);
private:
// Method Description:
@@ -120,13 +122,15 @@ namespace SettingsModelUnitTests
"experimental.input.forceVT": false,
"actions": []
"actions": [],
"keybindings": []
})" };
static constexpr std::string_view smallGlobalsString{ R"(
{
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"actions": []
"actions": [],
"keybindings": []
})" };
RoundtripTest<implementation::GlobalAppSettings>(globalsString);
@@ -275,47 +279,50 @@ namespace SettingsModelUnitTests
{
// simple command
static constexpr std::string_view actionsString1{ R"([
{ "command": "paste" }
{ "command": "paste", "id": "Test.Paste" }
])" };
// complex command
static constexpr std::string_view actionsString2A{ R"([
{ "command": { "action": "setTabColor" } }
{ "command": { "action": "setTabColor" }, "id": "Test.SetTabColor" }
])" };
static constexpr std::string_view actionsString2B{ R"([
{ "command": { "action": "setTabColor", "color": "#112233" } }
{ "command": { "action": "setTabColor", "color": "#112233" }, "id": "Test.SetTabColor112233" }
])" };
static constexpr std::string_view actionsString2C{ R"([
{ "command": { "action": "copy" } },
{ "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" } }
{ "command": { "action": "copy" }, "id": "Test.Copy" },
{ "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" }, "id": "Test.CopyWithArgs" }
])" };
// simple command with key chords
static constexpr std::string_view actionsString3{ R"([
{ "command": "toggleAlwaysOnTop", "keys": "ctrl+a" },
{ "command": "toggleAlwaysOnTop", "keys": "ctrl+b" }
])" };
static constexpr std::string_view actionsString3{ R"({ "actions": [
{ "command": "toggleAlwaysOnTop", "id": "Test.ToggleAlwaysOnTop" } ],
"keybindings": [
{ "keys": "ctrl+a", "id": "Test.ToggleAlwaysOnTop" },
{ "keys": "ctrl+b", "id": "Test.ToggleAlwaysOnTop" } ]})" };
// complex command with key chords
static constexpr std::string_view actionsString4A{ R"([
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" },
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
])" };
static constexpr std::string_view actionsString4A{ R"({ "actions":[
{ "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Test.EnlargeFont" } ],
"keybindings": [
{ "keys": "ctrl+c", "id": "Test.EnlargeFont" },
{ "keys": "ctrl+d", "id": "Test.EnlargeFont" } ]})" };
// command with name and icon and multiple key chords
static constexpr std::string_view actionsString5{ R"([
{ "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "keys": "ctrl+e" },
{ "command": "scrollToTop", "keys": "ctrl+f" }
])" };
static constexpr std::string_view actionsString5{ R"({ "actions":[
{ "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "id": "Test.ScrollToTop" } ],
"keybindings": [
{ "id": "Test.ScrollToTop", "keys": "ctrl+f" },
{ "id": "Test.ScrollToTop", "keys": "ctrl+e" } ]})" };
// complex command with new terminal args
static constexpr std::string_view actionsString6{ R"([
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+g" },
{ "command": { "action": "newTab", "index": 0 }, "id": "Test.NewTerminal" },
])" };
// complex command with meaningful null arg
static constexpr std::string_view actionsString7{ R"([
{ "command": { "action": "renameWindow", "name": null }, "keys": "ctrl+h" }
{ "command": { "action": "renameWindow", "name": null }, "id": "Test.MeaningfulNull" }
])" };
// nested command
@@ -397,9 +404,9 @@ namespace SettingsModelUnitTests
])"" };
// unbound command
static constexpr std::string_view actionsString10{ R"([
{ "command": "unbound", "keys": "ctrl+c" }
])" };
static constexpr std::string_view actionsString10{ R"({ "actions": [],
"keybindings": [
{ "id": null, "keys": "ctrl+c" } ]})" };
Log::Comment(L"simple command");
RoundtripTest<implementation::ActionMap>(actionsString1);
@@ -409,14 +416,16 @@ namespace SettingsModelUnitTests
RoundtripTest<implementation::ActionMap>(actionsString2B);
RoundtripTest<implementation::ActionMap>(actionsString2C);
// ActionMap has effectively 2 "to json" calls we need to make, one for the actions and one for the keybindings
// So we cannot use RoundtripTest<ActionMap> for actions + keychords, just use RoundTripTest<GlobalAppSettings>
Log::Comment(L"simple command with key chords");
RoundtripTest<implementation::ActionMap>(actionsString3);
RoundtripTest<implementation::GlobalAppSettings>(actionsString3);
Log::Comment(L"complex commands with key chords");
RoundtripTest<implementation::ActionMap>(actionsString4A);
RoundtripTest<implementation::GlobalAppSettings>(actionsString4A);
Log::Comment(L"command with name and icon and multiple key chords");
RoundtripTest<implementation::ActionMap>(actionsString5);
RoundtripTest<implementation::GlobalAppSettings>(actionsString5);
Log::Comment(L"complex command with new terminal args");
RoundtripTest<implementation::ActionMap>(actionsString6);
@@ -434,7 +443,7 @@ namespace SettingsModelUnitTests
RoundtripTest<implementation::ActionMap>(actionsString9D);
Log::Comment(L"unbound command");
RoundtripTest<implementation::ActionMap>(actionsString10);
RoundtripTest<implementation::GlobalAppSettings>(actionsString10);
}
void SerializationTests::CascadiaSettings()
@@ -503,7 +512,10 @@ namespace SettingsModelUnitTests
}
],
"actions": [
{ "command": { "action": "sendInput", "input": "VT Griese Mode" }, "id": "User.sendInput.E02B3DF9", "keys": "ctrl+k" }
{ "command": { "action": "sendInput", "input": "VT Griese Mode" }, "id": "Test.SendInput" }
],
"keybindings": [
{ "id": "Test.SendInput", "keys": "ctrl+k" }
],
"theme": "system",
"themes": []
@@ -995,7 +1007,6 @@ namespace SettingsModelUnitTests
{
"name": "foo",
"command": "closePane",
"keys": "ctrl+shift+w",
"id": "thisIsMyClosePane"
},
{
@@ -1065,4 +1076,123 @@ namespace SettingsModelUnitTests
VERIFY_ARE_EQUAL(sendInputCmd1.ID(), sendInputCmd1.ID());
}
void SerializationTests::RoundtripLegacyToModernActions()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"id": "Test.SendInput",
"command": { "action": "sendInput", "input": "just some input" },
"keys": "ctrl+shift+w"
},
{
"command": "unbound",
"keys": "ctrl+shift+x"
}
]
})" };
// modern style:
// - no "unbound" actions, these are just keybindings that have no id
// - no keys in actions, these are keybindings with an id
static constexpr std::string_view newSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"command": { "action": "sendInput", "input": "just some input" },
"id": "Test.SendInput"
}
],
"keybindings": [
{
"id": "Test.SendInput",
"keys": "ctrl+shift+w"
},
{
"id": null,
"keys": "ctrl+shift+x"
}
]
})" };
implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto oldResult{ settings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk");
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::MultipleActionsAreCollapsed()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"icon": "myCoolIconPath.png",
"command": { "action": "sendInput", "input": "just some input" },
"keys": "ctrl+shift+w"
},
{
"command": { "action": "sendInput", "input": "just some input" },
"keys": "ctrl+shift+x"
}
]
})" };
// modern style:
// - multiple action blocks whose purpose is simply to define more keybindings for the same action
// get collapsed into one action block, with the name and icon path preserved and have multiple keybindings instead
static constexpr std::string_view newSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"icon": "myCoolIconPath.png",
"command": { "action": "sendInput", "input": "just some input" },
"id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"("
}
],
"keybindings": [
{
"keys": "ctrl+shift+w",
"id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"("
},
{
"keys": "ctrl+shift+x",
"id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"("
}
]
})" };
implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto oldResult{ settings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk");
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
}