Skip to content

Add reflection to the ActionArgs in the settings model #18915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

PankajBhojwani
Copy link
Contributor

@PankajBhojwani PankajBhojwani commented May 14, 2025

Summary of the Pull Request

Implements reflection to the various ActionArg types in the settings model, which allows these structs to provide information about themselves (i.e. what args they contain and what types they are). This is necessary as a pre-requisite for the Settings Editor to display and modify these arg values.

Detailed Description of the Pull Request / Additional comments

  • The IActionArgs interface now has additional methods:
    • Get the number of args
    • Get/Set an arg at a specific index
    • Get the arg description of an arg at a specific index; the arg description contains:
      • name of the arg
      • type of the arg
      • whether the arg is required
      • a tag, this is to cover special cases (for example the ColorScheme argument is technically of type "string", but only allows specific values)
  • All the macros in ActionArgsMagic have been updated to support the new interface
  • ActionMap has been updated to support adding/editing/deleting actions and keybindings from outside the SettingsModel
    • It now also tracks for ID changes to commands and updates accordingly
  • Command has been updated to allow setting its values from outside the SettingsModel
  • EnumMappings have been added to various ActionArg enums that weren't there before

Validation Steps Performed

Bug bashed in conjunction with #18917

PR Checklist

  • Closes #xxx
  • Tests added/passed
  • Documentation updated
    • If checked, please file a pull request on our docs repo and link it here: #xxx
  • Schema updated (if necessary)

Comment on lines +24 to +26
static Windows.Foundation.Collections.IMap<Microsoft.Terminal.Settings.Model.ShortcutAction, String> AvailableShortcutActionsAndNames { get; };
static IActionArgs GetEmptyArgsForAction(Microsoft.Terminal.Settings.Model.ShortcutAction shortcutAction);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActionMap already contains:

Windows.Foundation.Collections.IMapView<String, ActionAndArgs> AvailableActions { get; };

I feel like these make more sense to be over there

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would swing hard in the other direction actually.

Since they're static, they don't need to be on an existing class at all.

We could put them on a new static class. That way nobody has to instantiate one OR know about CascadiaSettings to do it.

static runtimeclass ActionArgFactory {
    static ...;
}

_ActionMap.erase(oldID);
_ActionMap.emplace(newID, senderCmd);

// update _KeyMap so that all keys that pointed to the old ID now point to the new ID
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably do the same thing for action IDs in the new tab menu (not accessible via ActionMap, but it's something we'll need).

_##name{ name##Param },

// append this argument's description to the internal vector
#define APPEND_ARG_DESCRIPTION(type, name, jsonKey, required, tag, ...) \
_argDescriptions.push_back({ L## #name, L## #type, std::wstring_view(L## #required) != L"false", tag });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::wstring_view(L## #required) != L"false"

wait... why are we converting/comparing required as a string? It looks like it's already a bool in the other macros. Can't we just pass the bool directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's because the term required in these macros is a little misleading - it's not an actual bool, it's a validation check. For example, the ResizeDirection arg has the required parameter as args->ResizeDirection() == ResizeDirection::None, which checks the current actual value of the arg, it's not a general "this arg is required" bool.

Tbh we should probably find a way at some point to transfer over the actual validation check in the ArgDescription so the Settings Editor can use it and display correct error messaging, but for now I think it makes sense to just go with this

Comment on lines -734 to +809
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr);
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, Model::NewTerminalArgs{});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, can we keep it as nullptr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I am leaving the "allow editing different new content types (like creating a snippets pane or a markdown pane etc)" as a future todo, so for that reason we need to initialize this as a NewTerminalArgs here but it shouldn't change anything functionally anywhere else

@microsoft-github-policy-service microsoft-github-policy-service bot added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label May 15, 2025
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noted

@@ -5,12 +5,32 @@ import "Command.idl";

namespace Microsoft.Terminal.Settings.Model
{
enum ArgTag
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ArgTypeHint

ColorScheme
};

struct ArgDescription
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very-nit: more commonly something like this would be called a Descriptor

@@ -168,6 +188,11 @@ namespace Microsoft.Terminal.Settings.Model
UInt64 ContentId{ get; set; };

String ToCommandline();

UInt32 GetArgCount();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to put these four methods in their own interface (IActionArgsDescriptorAccess), and inherit that interface in IActionArgs and NewTerminalArgs.

Right now, they look the same but they cannot be used in the same way.

If it is literally the same interface, you can unify any codepaths that try to access INewTerminalArgs.GetArgCount and IActionArgs.GetArgCount (et al).

The true benefit of IDL is that you can define interfaces that encapsulate common behavior, after all. :)

@microsoft-github-policy-service microsoft-github-policy-service bot removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label May 16, 2025
@@ -741,6 +816,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
}

void ActionMap::AddKeyBinding(Control::KeyChord keys, const winrt::hstring& cmdID)
{
_KeyMap.insert_or_assign(keys, cmdID);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_KeyMap.insert_or_assign(keys, cmdID);
_KeyMap.insert_or_assign(keys, cmdID);
_changeLog.emplace(KeysKey);

We probably want to update the settings logger here to say that we've changed keys.

@@ -757,6 +838,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
AddAction(*cmd, keys);
}

void ActionMap::DeleteUserCommand(const winrt::hstring& cmdID)
{
_ActionMap.erase(cmdID);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_ActionMap.erase(cmdID);
_ActionMap.erase(cmdID);
_changeLog.emplace(IDKey)

This one I'm iffy on, so I want to discuss here.

Currently, there isn't a place where we're tracking if a user added an ID. In the JSON, the user would be using "id" in two cases for Action Map:

  1. create a new action in the "actions" block (i.e. {"command": "terminalChat", "id": "User.terminalChat"})
    • That's fine? We care about action arg usage and that's already being handled.
  2. create a key binding in the "keybindings"block (i.e. {"id": "User.terminalChat", "keys": "ctrl+?})
    • That's fine too. We care about "keys" usage and that's already being handled.

(related: "id" is used in the new tab menu, but we're just tracking "action" there which is sufficient)

Long-term, however, we want _changeLog to be a way for us to know if the user changed anything. So maybe we do want to apply the code suggestion and update case 1 to add "id" too? Thoughts?

@@ -740,4 +740,274 @@
<data name="OpenCWDCommandKey" xml:space="preserve">
<value>Open current working directory</value>
</data>
<data name="CopyText" xml:space="preserve">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lightly pushing back on the capitalization of these. We should probably capitalize the first letter of the first word and that's it.

Comment on lines +1007 to +1009
<data name="QuickFix" xml:space="preserve">
<value>Quick Fix</value>
</data>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the type here is "Quick Fix", but there's no action args and the generated name is "Open quick fix menu". Should we just use the generated name here?

Generalize this comment to all the other actions that have no args. I think the list can basically be found in ActionAndArgs.cpp by looking at GenerateName()

////////////////////////////////////////////////////////////////////////////////
#define NEW_TERMINAL_ARGS(X) \
X(winrt::hstring, Commandline, "commandline", false, ArgTag::None, L"") \
X(winrt::hstring, StartingDirectory, "startingDirectory", false, ArgTag::None, L"") \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
X(winrt::hstring, StartingDirectory, "startingDirectory", false, ArgTag::None, L"") \
X(winrt::hstring, StartingDirectory, "startingDirectory", false, ArgTag::FilePath, L"") \

Or maybe we need a new one for directory path?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants