Skip to content

Allow editing actions in the settings UI #18917

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 25 commits into
base: dev/pabhoj/settings_model_reflection
Choose a base branch
from

Conversation

PankajBhojwani
Copy link
Contributor

@PankajBhojwani PankajBhojwani commented May 15, 2025

Summary of the Pull Request

Targets #18915

The actions page now has a list of all the commands (default, user, fragments etc) and clicking a command from that page brings you to an "Edit action" page where you can fully view and edit both the action type and any additional arguments.

One todo remaining that shouldn't block reviews:

  • Accessibility fixes

Detailed Description of the Pull Request / Additional comments

Actions View Model

  • Added several new view models
    • CommandViewModel (view model for a Command), a list of these is created and managed by ActionsViewModel
    • ActionArgsViewModel (view model for an ActionArgs), created and managed by CommandViewModel
    • ArgWrapper (view model for each individual argument inside an ActionArgs), created and managed by ActionArgsViewModel

Actions page

  • No longer a list of only keybindings, instead it is a list of every command Terminal knows about

EditAction page

  • New page that you get to by clicking a command from the Actions page
  • Bound to a CommandViewModel
  • Allows editing the type of shortcut action and the command name
  • Depending on the shortcut action, displays a list of additional arguments allowed for the command with the appropriate templating (bool arguments are switches, flags are checkboxes etc)

Validation Steps Performed

Bug bashed

PR Checklist

This comment has been minimized.

This comment has been minimized.

Copy link
Member

@carlos-zamora carlos-zamora left a comment

Choose a reason for hiding this comment

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

Other feedback:

  • "Clear buffer" action is being shown as "Clear mark" (binding to ctrl+shift+k shown correctly)

Comment on lines -206 to -218
<!-- Key Chord Text -->
<Border Grid.Column="1"
Padding="2,0,2,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{ThemeResource KeyChordBorderStyle}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}">

<TextBlock FontSize="14"
Style="{ThemeResource KeyChordTextBlockStyle}"
Text="{x:Bind KeyChordText, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</Border>
Copy link
Member

Choose a reason for hiding this comment

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

Key bindings aren't shown anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is intentional for now since some commands can have multiple keybindings bound to them. As a follow up we can convert these list view items into expanders that show 1-3 of the associated keybindings

Copy link
Member

@carlos-zamora carlos-zamora left a comment

Choose a reason for hiding this comment

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

It'd be really nice if you added a comment by each of the ArgWrapper data templates saying what's a good action type and arg combo to test this out. I've been manually going through ActionArgs.h to figure this out. Not the end of the world, but it'd be a nice QOL improvement 😁

Almost done reviewing this. Just need to go over ActionsViewModel

Comment on lines 234 to 237
<DataTemplate x:Key="ListItemTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem HorizontalContentAlignment="Stretch" />
</DataTemplate>
Copy link
Member

Choose a reason for hiding this comment

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

Looks like ListItemTemplate is not being used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's used in EditAction.cpp!


<DataTemplate x:Key="Int32Template"
x:DataType="local:ArgWrapper">
<ListViewItem>
Copy link
Member

Choose a reason for hiding this comment

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

[Keyboard navigation] Tabbing lets us move our focus into 3 spots here (in order): (1) 1st list view item -> (2) 2nd list view item -> (3) number box

I suspect that the redundant list view item is from the first ListViewItem in the data template. I think you should be able to remove it because the data template controls what's inside the ListViewItem already. You can repeat this for all the other data templates here.

As for the other list view item, you'll want to unset it as a tab stop. I think you can do that with ItemContainerStyle on the ListView itself. Create a style with IsTabStop = False and pass it in. That way, the user can tab directly into the number box (or whatever control is being used).

It's worth checking the UIA tree with Accessibility Insights too. Idk if you'll need to change the AccessibilityView on these to hide them from the tree, of if just removing the tab stop is enough.

<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}" />
<local:NullableColorPicker x:Uid="Actions_NullableColorPicker"
Copy link
Member

Choose a reason for hiding this comment

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

Null color is "No color"? Hmm... but I'm not sure how to handle this one exactly either. For Tab color, it opens the color picker. But for new terminal args, it... what, uses the theme color?

Comment on lines +630 to +639
<TextBlock x:Uid="Actions_Name"
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center" />
<TextBox Grid.Row="0"
Grid.Column="1"
Width="300"
HorizontalAlignment="Left"
PlaceholderText="{x:Bind ViewModel.DisplayName, Mode=OneWay}"
Text="{x:Bind ViewModel.Name, Mode=TwoWay}" />
Copy link
Member

Choose a reason for hiding this comment

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

Each of these pairs has the same problem I mentioned earlier with a screen reader. Visually, we can tell that the text block is the header for the control. With a screen reader, we don't have that, so we need to set the AutomationProperty.Name manually

Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center" />
<ListView Grid.Row="3"
Copy link
Member

Choose a reason for hiding this comment

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

Consider ItemsRepeater instead of ListView. We're not using the functionality of the ListView, really. We just want a stack panel layout of our own controls.

Copy link
Member

Choose a reason for hiding this comment

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

ItemsControl works too. I used it over in my extensions page:

<ItemsControl x:Name="ActiveExtensionsList"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do use ListView.Header which neither ItemsRepeater nor ItemsControl have, we could mimic it with a Grid or something but might as well just use ListView.Header I think

@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 21, 2025
Copy link
Member

@carlos-zamora carlos-zamora left a comment

Choose a reason for hiding this comment

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

Alright! Finally finished reviewing everything. Left a bunch of comments all around. Tried to test things out with the bug bash build as I went and did some accessibility testing too. I recommend that after you get the accessibility stuff in, you run it through Accessibility Insights' FastPass to check for common issues. That said, I think once you fix the comments I added in, you should be pretty much good to go there.

There's a bit more cacheing that can be done. I think it's worth it since we've got A TON of commands/actions. There was also a concern I had about null-checking somewhere in there so we probably want to fix that.

The ArgsWrapper stuff is really cool. Good work all-around dude 😊. Excited to see this land when you make the changes. We're close!

@@ -18,383 +24,1194 @@ using namespace winrt::Windows::UI::Xaml::Data;
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Microsoft::Terminal::Settings::Model;

// todo:
Copy link
Member

Choose a reason for hiding this comment

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

Fair. We should file an issue and link it here for tracking.

I wonder if in the near term, we could add support for them, but just expose a text box. It wouldn't be ideal, but we'd still show the actions in the main list and users would be able to view/modify them. Then a cleaner UI could be added in later (possibly even by the community).

} \
std::sort(enumList.begin(), enumList.end(), EnumEntryReverseComparator<enumType>()); \
_EnumList = winrt::single_threaded_observable_vector<winrt::Microsoft::Terminal::Settings::Editor::EnumEntry>(std::move(enumList)); \
_NotifyChanges(L"EnumList", L"EnumValue");
Copy link
Member

Choose a reason for hiding this comment

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

(Regarding this _NotifyChanges() and all the other macros that have a similar one)

Who's listening to these? It looks like we don't have a PropertyChanged handler for specifically these values.

Conversely, shouldn't the property changed events be more specific? Like, shouldn't it notify L## #enumMappingsName L"EnumList" to pass over the specific one?

std::vector<winrt::Microsoft::Terminal::Settings::Editor::EnumEntry> enumList; \
const auto mappings = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \
enumType unboxedValue; \
auto nullEntry = winrt::make<winrt::Microsoft::Terminal::Settings::Editor::implementation::EnumEntry>(RS_(L"Actions_NullEnumValue"), nullptr); \
Copy link
Member

Choose a reason for hiding this comment

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

Somewhere I mentioned that we should provide a more specific string for the null value. Looks like this is the spot to do it. You can probably just move the resource ID into the macro definition

std::vector<winrt::Microsoft::Terminal::Settings::Editor::FlagEntry> flagList; \
const auto mappings = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \
enumType unboxedValue{ 0 }; \
auto nullEntry = winrt::make<winrt::Microsoft::Terminal::Settings::Editor::implementation::FlagEntry>(RS_(L"Actions_NullEnumValue"), nullptr, true); \
Copy link
Member

Choose a reason for hiding this comment

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

Same here. You can probably move this up into the macro definition to have it be a custom string

// even though the arg type is technically a string, we want an enum list for color schemes specifically
std::vector<winrt::Microsoft::Terminal::Settings::Editor::EnumEntry> namesList;
const auto currentSchemeName = unbox_value<winrt::hstring>(_Value);
auto nullEntry = winrt::make<winrt::Microsoft::Terminal::Settings::Editor::implementation::EnumEntry>(RS_(L"Actions_NullEnumValue"), nullptr);
Copy link
Member

Choose a reason for hiding this comment

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

This may be where you can add a custom value for the null enum value. Maybe hardcode a Name check so that we do a different one depending on the setting?

Comment on lines +117 to +142
// unboxing functions
winrt::hstring UnboxString(const Windows::Foundation::IInspectable& value);
winrt::hstring UnboxGuid(const Windows::Foundation::IInspectable& value);
int32_t UnboxInt32(const Windows::Foundation::IInspectable& value);
float UnboxInt32Optional(const Windows::Foundation::IInspectable& value);
uint32_t UnboxUInt32(const Windows::Foundation::IInspectable& value);
float UnboxUInt32Optional(const Windows::Foundation::IInspectable& value);
float UnboxUInt64(const Windows::Foundation::IInspectable& value);
float UnboxFloat(const Windows::Foundation::IInspectable& value);
bool UnboxBool(const Windows::Foundation::IInspectable& value);
winrt::Windows::Foundation::IReference<bool> UnboxBoolOptional(const Windows::Foundation::IInspectable& value);
winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> UnboxTerminalCoreColorOptional(const Windows::Foundation::IInspectable& value);
winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> UnboxWindowsUIColorOptional(const Windows::Foundation::IInspectable& value);

// bind back functions
void StringBindBack(const winrt::hstring& newValue);
void GuidBindBack(const winrt::hstring& newValue);
void Int32BindBack(const double newValue);
void Int32OptionalBindBack(const double newValue);
void UInt32BindBack(const double newValue);
void UInt32OptionalBindBack(const double newValue);
void UInt64BindBack(const double newValue);
void FloatBindBack(const double newValue);
void BoolOptionalBindBack(const Windows::Foundation::IReference<bool> newValue);
void TerminalCoreColorBindBack(const winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> newValue);
void WindowsUIColorBindBack(const winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> newValue);
Copy link
Member

Choose a reason for hiding this comment

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

These are all pretty simple functions, and that's great. Would there be any benefit to moving these over to be converters so they can be used throughout the project? Or am I overengineering it? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The BindBack functions all need to stay here since it involves modifying the underlying data, so I figured it would be cleaner if we just leave the Unbox functions here too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Happy to move just the unbox ones over to converters if we feel strongly about it though

Comment on lines 996 to 1001
// Populate AvailableActionsAndNames
_AvailableActionsAndNamesMap = Model::CascadiaSettings::AvailableShortcutActionsAndNames();
for (const auto unimplemented : UnimplementedShortcutActions)
{
_AvailableActionsAndNamesMap.Remove(unimplemented);
}
Copy link
Member

Choose a reason for hiding this comment

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

We can probably cache this, right? Looks like CascadiaSettings::AvailableShortcutActionsAndNames does the work each time we call it, then we'll remove the same 2 unimplemented shortcut actions. Maybe we should just store it as a static so we don't regenerate it? It shouldn't change at runtime.


void ActionsViewModel::AttemptDeleteKeyChord(const Control::KeyChord& keys)
{
// Update the settings model
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
// Update the settings model
// Update the settings model
assert(keys);

Thoughts on adding an assert here? If somebody is calling this with an empty keys, they're probably holding it wrong, right? So maybe we should notify the developer that they're not using it right.

@microsoft-github-policy-service microsoft-github-policy-service bot added the No-Recent-Activity This issue/PR is going stale and may be auto-closed without further activity. label May 28, 2025
@carlos-zamora carlos-zamora removed the No-Recent-Activity This issue/PR is going stale and may be auto-closed without further activity. label May 29, 2025
@carlos-zamora
Copy link
Member

Oh! Also add in the "New" badge. You should be able to add it like the same way I did for the Extensions page. Just look at ExtensionsViewModel::DisplayBadge and how it's used/set-up.

@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 Jun 3, 2025

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

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.

2 participants