diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 5d3410951..735fbe974 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -85,12 +85,14 @@ "openNewTabDropdown", "openSettings", "openTabColorPicker", + "openWindowRenamer", "paste", "prevTab", "renameTab", "openTabRenamer", "resetFontSize", "resizePane", + "renameWindow", "scrollDown", "scrollDownPage", "scrollUp", @@ -651,6 +653,38 @@ } ] }, + "RenameTabAction": { + "description": "Arguments corresponding to a renameTab Action", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "renameTab" }, + "title": { + "type": "string", + "default": "", + "description": "A title to assign to the tab. If omitted or null, this action will restore the tab's title to the original value." + } + } + } + ] + }, + "RenameWindowAction": { + "description": "Arguments corresponding to a renameWindow Action", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "renameWindow" }, + "name": { + "type": "string", + "default": "", + "description": "A name to assign to the window." + } + } + } + ] + }, "Keybinding": { "additionalProperties": false, "properties": { @@ -679,6 +713,8 @@ { "$ref": "#/definitions/NewWindowAction" }, { "$ref": "#/definitions/NextTabAction" }, { "$ref": "#/definitions/PrevTabAction" }, + { "$ref": "#/definitions/RenameTabAction" }, + { "$ref": "#/definitions/RenameWindowAction" }, { "type": "null" } ] }, diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 921c7630b..df9edfa69 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -85,6 +85,9 @@ namespace TerminalAppLocalTests TEST_METHOD(NextMRUTab); TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder); + TEST_METHOD(TestWindowRenameSuccessful); + TEST_METHOD(TestWindowRenameFailure); + TEST_CLASS_SETUP(ClassSetup) { return true; @@ -948,4 +951,57 @@ namespace TerminalAppLocalTests // will also dismiss itself immediately when that's called. So we can't // really inspect the contents of the list in this test, unfortunately. } + + void TabTests::TestWindowRenameSuccessful() + { + auto page = _commonSetup(); + page->RenameWindowRequested([&page](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) { + // In the real terminal, this would bounce up to the monarch and + // come back down. Instead, immediately call back and set the name. + page->WindowName(args.ProposedName()); + }); + + bool windowNameChanged = false; + page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable { + if (args.PropertyName() == L"WindowNameForDisplay") + { + windowNameChanged = true; + } + }); + + TestOnUIThread([&page]() { + page->_RequestWindowRename(winrt::hstring{ L"Foo" }); + }); + TestOnUIThread([&]() { + VERIFY_ARE_EQUAL(L"Foo", page->_WindowName); + VERIFY_IS_TRUE(windowNameChanged, + L"The window name should have changed, and we should have raised a notification that WindowNameForDisplay changed"); + }); + } + void TabTests::TestWindowRenameFailure() + { + auto page = _commonSetup(); + page->RenameWindowRequested([&page](auto&&, auto&&) { + // In the real terminal, this would bounce up to the monarch and + // come back down. Instead, immediately call back to tell the terminal it failed. + page->RenameFailed(); + }); + + bool windowNameChanged = false; + + page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable { + if (args.PropertyName() == L"WindowNameForDisplay") + { + windowNameChanged = true; + } + }); + + TestOnUIThread([&page]() { + page->_RequestWindowRename(winrt::hstring{ L"Foo" }); + }); + TestOnUIThread([&]() { + VERIFY_IS_FALSE(windowNameChanged, + L"The window name should not have changed, we should have rejected the change."); + }); + } } diff --git a/src/cascadia/LocalTests_TerminalApp/pch.h b/src/cascadia/LocalTests_TerminalApp/pch.h index 737bdf179..6b9aee970 100644 --- a/src/cascadia/LocalTests_TerminalApp/pch.h +++ b/src/cascadia/LocalTests_TerminalApp/pch.h @@ -45,6 +45,7 @@ Author(s): #include #include #include +#include #include #include #include diff --git a/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj b/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj index d0e988284..519a9c69a 100644 --- a/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj +++ b/src/cascadia/Remoting/Microsoft.Terminal.RemotingLib.vcxproj @@ -25,6 +25,9 @@ Monarch.idl + + Peasant.idl + Peasant.idl @@ -51,6 +54,9 @@ Monarch.idl + + Peasant.idl + Peasant.idl diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index 5fd86a251..5001dc2ca 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -76,6 +76,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // Add an event listener to the peasant's WindowActivated event. peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated }); peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows }); + peasant.RenameRequested({ this, &Monarch::_renameRequested }); _peasants[newPeasantsId] = peasant; @@ -631,4 +632,52 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation }; _forAllPeasantsIgnoringTheDead(callback, onError); } + + // Method Description: + // - This is an event handler for the RenameRequested event. A + // Peasant may raise that event when they want to be renamed to something else. + // - We will check if there are any other windows with this name. If there + // are, then we'll reject the rename by setting args.Succeeded=false. + // - If there aren't any other windows with this name, then we'll set + // args.Succeeded=true, allowing the window to keep this name. + // Arguments: + // - args: Contains the requested window name and a boolean (Succeeded) + // indicating if the request was successful. + // Return Value: + // - + void Monarch::_renameRequested(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args) + { + bool successfullyRenamed = false; + + try + { + args.Succeeded(false); + const auto name{ args.NewName() }; + // Try to find a peasant that currently has this name + const auto id = _lookupPeasantIdForName(name); + if (_getPeasant(id) == nullptr) + { + // If there is one, then oh no! The requestor is not allowed to + // be renamed. + args.Succeeded(true); + successfullyRenamed = true; + } + + TraceLoggingWrite(g_hRemotingProvider, + "Monarch_renameRequested", + TraceLoggingWideString(name.c_str(), "name", "The newly proposed name"), + TraceLoggingInt64(successfullyRenamed, "successfullyRenamed", "true if the peasant is allowed to rename themselves to that name."), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + // If this fails, we don't _really_ care. The peasant died, but + // they're the only one who cares about the result. + TraceLoggingWrite(g_hRemotingProvider, + "Monarch_renameRequested_Failed", + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + } + } } diff --git a/src/cascadia/Remoting/Monarch.h b/src/cascadia/Remoting/Monarch.h index 796c6aa07..7996934fd 100644 --- a/src/cascadia/Remoting/Monarch.h +++ b/src/cascadia/Remoting/Monarch.h @@ -74,11 +74,15 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation void _doHandleActivatePeasant(const winrt::com_ptr& args); void _clearOldMruEntries(const uint64_t peasantID); + void _forAllPeasantsIgnoringTheDead(std::function callback, + std::function errorCallback); + void _identifyWindows(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); - void _forAllPeasantsIgnoringTheDead(std::function callback, - std::function errorCallback); + void _renameRequested(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args); + friend class RemotingUnitTests::RemotingTests; }; } diff --git a/src/cascadia/Remoting/Peasant.cpp b/src/cascadia/Remoting/Peasant.cpp index 8eb9ffe52..c47cf1e98 100644 --- a/src/cascadia/Remoting/Peasant.cpp +++ b/src/cascadia/Remoting/Peasant.cpp @@ -164,4 +164,34 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); } + void Peasant::RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args) + { + bool successfullyNotified = false; + const auto oldName{ _WindowName }; + try + { + // Try/catch this, because the other side of this event is handled + // by the monarch. The monarch might have died. If they have, this + // will throw an exception. Just eat it, the election thread will + // handle hooking up the new one. + _RenameRequestedHandlers(*this, args); + if (args.Succeeded()) + { + _WindowName = args.NewName(); + } + successfullyNotified = true; + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + } + TraceLoggingWrite(g_hRemotingProvider, + "Peasant_RequestRename", + TraceLoggingUInt64(GetID(), "peasantID", "Our ID"), + TraceLoggingWideString(oldName.c_str(), "oldName", "Our old name"), + TraceLoggingWideString(args.NewName().c_str(), "newName", "The proposed name"), + TraceLoggingBoolean(args.Succeeded(), "succeeded", "true if the monarch ok'd this new name for us."), + TraceLoggingBoolean(successfullyNotified, "successfullyNotified", "true if we successfully notified the monarch"), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + } } diff --git a/src/cascadia/Remoting/Peasant.h b/src/cascadia/Remoting/Peasant.h index 5b402e616..67785c95b 100644 --- a/src/cascadia/Remoting/Peasant.h +++ b/src/cascadia/Remoting/Peasant.h @@ -5,6 +5,7 @@ #include "Peasant.g.h" #include "../cascadia/inc/cppwinrt_utils.h" +#include "RenameRequestArgs.h" namespace RemotingUnitTests { @@ -24,6 +25,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation void ActivateWindow(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args); void RequestIdentifyWindows(); void DisplayWindowId(); + void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args); winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs(); @@ -34,6 +36,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs); TYPED_EVENT(IdentifyWindowsRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs); private: Peasant(const uint64_t testPID); diff --git a/src/cascadia/Remoting/Peasant.idl b/src/cascadia/Remoting/Peasant.idl index 1c3ff9123..583a494a6 100644 --- a/src/cascadia/Remoting/Peasant.idl +++ b/src/cascadia/Remoting/Peasant.idl @@ -13,6 +13,13 @@ namespace Microsoft.Terminal.Remoting String CurrentDirectory(); }; + runtimeclass RenameRequestArgs + { + RenameRequestArgs(String newName); + String NewName { get; }; + Boolean Succeeded; + }; + runtimeclass WindowActivatedArgs { WindowActivatedArgs(UInt64 peasantID, Guid desktopID, Windows.Foundation.DateTime activatedTime); @@ -37,10 +44,13 @@ namespace Microsoft.Terminal.Remoting void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested void DisplayWindowId(); // Tells us to display its own ID (which causes a DisplayWindowIdRequested to be raised) + void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested + event Windows.Foundation.TypedEventHandler WindowActivated; event Windows.Foundation.TypedEventHandler ExecuteCommandlineRequested; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; event Windows.Foundation.TypedEventHandler DisplayWindowIdRequested; + event Windows.Foundation.TypedEventHandler RenameRequested; }; [default_interface] runtimeclass Peasant : IPeasant diff --git a/src/cascadia/Remoting/RenameRequestArgs.cpp b/src/cascadia/Remoting/RenameRequestArgs.cpp new file mode 100644 index 000000000..cab69f408 --- /dev/null +++ b/src/cascadia/Remoting/RenameRequestArgs.cpp @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +#include "pch.h" +#include "RenameRequestArgs.h" +#include "RenameRequestArgs.g.cpp" diff --git a/src/cascadia/Remoting/RenameRequestArgs.h b/src/cascadia/Remoting/RenameRequestArgs.h new file mode 100644 index 000000000..7fb751ade --- /dev/null +++ b/src/cascadia/Remoting/RenameRequestArgs.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Class Name: +- RenameRequestArgs.h + +--*/ +#pragma once + +#include "RenameRequestArgs.g.h" +#include "../cascadia/inc/cppwinrt_utils.h" + +namespace winrt::Microsoft::Terminal::Remoting::implementation +{ + struct RenameRequestArgs : public RenameRequestArgsT + { + WINRT_PROPERTY(winrt::hstring, NewName); + WINRT_PROPERTY(bool, Succeeded, false); + + public: + RenameRequestArgs(winrt::hstring newName) : + _NewName{ newName } {}; + }; +} + +namespace winrt::Microsoft::Terminal::Remoting::factory_implementation +{ + BASIC_FACTORY(RenameRequestArgs); +} diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 46c9d1a27..238e160c2 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -708,4 +708,47 @@ namespace winrt::TerminalApp::implementation IdentifyWindow(); args.Handled(true); } + + void TerminalPage::_HandleRenameWindow(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + const auto newName = realArgs.Name(); + const auto request = winrt::make_self(newName); + _RenameWindowRequestedHandlers(*this, *request); + } + } + args.Handled(false); + } + + void TerminalPage::_HandleOpenWindowRenamer(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (WindowRenamer() == nullptr) + { + // We need to use FindName to lazy-load this object + if (MUX::Controls::TeachingTip tip{ FindName(L"WindowRenamer").try_as() }) + { + tip.Closed({ get_weak(), &TerminalPage::_FocusActiveControl }); + } + } + + WindowRenamer().IsOpen(true); + + // PAIN: We can't immediately focus the textbox in the TeachingTip. It's + // not technically focusable until it is opened. However, it doesn't + // provide an event to tell us when it is opened. That's tracked in + // microsoft/microsoft-ui-xaml#1607. So for now, the user _needs_ to + // click on the text box manually. + // + // We're also not using a ContentDialog for this, because in Xaml + // Islands a text box in a ContentDialog won't receive _any_ keypresses. + // Fun! + // WindowRenamerTextBox().Focus(FocusState::Programmatic); + + args.Handled(true); + } } diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index c8acf24ba..7f55b5f68 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -1410,4 +1410,13 @@ namespace winrt::TerminalApp::implementation _root->WindowId(id); } } + + void AppLogic::RenameFailed() + { + if (_root) + { + _root->RenameFailed(); + } + } + } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 0438061bc..b35b72321 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -61,6 +61,7 @@ namespace winrt::TerminalApp::implementation bool AlwaysOnTop() const; void IdentifyWindow(); + void RenameFailed(); winrt::hstring WindowName(); void WindowName(const winrt::hstring& name); uint64_t WindowId(); @@ -156,6 +157,7 @@ namespace winrt::TerminalApp::implementation FORWARDED_TYPED_EVENT(RaiseVisualBell, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, RaiseVisualBell); FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress); FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested); + FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested); #ifdef UNIT_TESTING friend class TerminalAppLocalTests::CommandlineTest; diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 35fa8f713..19647928d 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -51,6 +51,7 @@ namespace TerminalApp void IdentifyWindow(); String WindowName; UInt64 WindowId; + void RenameFailed(); Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi); Boolean CenterOnLaunch { get; }; @@ -83,5 +84,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler RaiseVisualBell; event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; + event Windows.Foundation.TypedEventHandler RenameWindowRequested; } } diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 6c32fbe39..e94778573 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -603,10 +603,25 @@ unnamed window text used to identify when a window hasn't been assigned a name by the user + + Enter a new name: + + + OK + + + Cancel + + + Failed to rename window + + + Another window with that name already exists + Maximize Restore Down - + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 05b7d6c07..8da8e8992 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -15,6 +15,7 @@ #include "ColorHelper.h" #include "DebugTapConnection.h" #include "SettingsTab.h" +#include "RenameWindowRequestedArgs.g.cpp" using namespace winrt; using namespace winrt::Windows::Foundation::Collections; @@ -2568,7 +2569,7 @@ namespace winrt::TerminalApp::implementation if (_WindowName != value) { _WindowName = value; - _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowNameForDisplay" }); + _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowNameForDisplay" }); } } @@ -2584,7 +2585,7 @@ namespace winrt::TerminalApp::implementation if (_WindowId != value) { _WindowId = value; - _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowIdForDisplay" }); + _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowIdForDisplay" }); } } @@ -2613,4 +2614,72 @@ namespace winrt::TerminalApp::implementation winrt::hstring{ fmt::format(L"<{}>", RS_(L"UnnamedWindowName")) } : _WindowName; } + + // Method Description: + // - Called when an attempt to rename the window has failed. This will open + // the toast displaying a message to the user that the attempt to rename + // the window has failed. + // - This will load the RenameFailedToast TeachingTip the first time it's called. + // Arguments: + // - + // Return Value: + // - + winrt::fire_and_forget TerminalPage::RenameFailed() + { + auto weakThis{ get_weak() }; + co_await winrt::resume_foreground(Dispatcher()); + if (auto page{ weakThis.get() }) + { + // If we haven't ever loaded the TeachingTip, then do so now and + // create the toast for it. + if (page->_windowRenameFailedToast == nullptr) + { + if (MUX::Controls::TeachingTip tip{ page->FindName(L"RenameFailedToast").try_as() }) + { + page->_windowRenameFailedToast = std::make_shared(tip); + // Make sure to use the weak ref when setting up this + // callback. + tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl }); + } + } + + if (page->_windowRenameFailedToast != nullptr) + { + page->_windowRenameFailedToast->Open(); + } + } + } + + // Method Description: + // - Called when the user hits the "Ok" button on the WindowRenamer TeachingTip. + // - Will raise an event that will bubble up to the monarch, asking if this + // name is acceptable. + // - If it is, we'll eventually get called back in TerminalPage::WindowName(hstring). + // - If not, then TerminalPage::RenameFailed will get called. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_WindowRenamerActionClick(const IInspectable& /*sender*/, + const IInspectable& /*eventArgs*/) + { + auto newName = WindowRenamerTextBox().Text(); + _RequestWindowRename(newName); + } + + void TerminalPage::_RequestWindowRename(const winrt::hstring& newName) + { + auto request = winrt::make(newName); + // The WindowRenamer is _not_ a Toast - we want it to stay open until the user dismisses it. + WindowRenamer().IsOpen(false); + _RenameWindowRequestedHandlers(*this, request); + // We can't just use request.Successful here, because the handler might + // (will) be handling this asynchronously, so when control returns to + // us, this hasn't actually been handled yet. We'll get called back in + // RenameFailed if this fails. + // + // Theoretically we could do a IAsyncOperation kind + // of thing with co_return winrt::make(false). + } + } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 1a6f4278a..6e1c10f39 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -7,6 +7,7 @@ #include "TerminalTab.h" #include "AppKeyBindings.h" #include "AppCommandlineArgs.h" +#include "RenameWindowRequestedArgs.g.h" #include "Toast.h" #define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); @@ -35,6 +36,15 @@ namespace winrt::TerminalApp::implementation ScrollDown = 1 }; + struct RenameWindowRequestedArgs : RenameWindowRequestedArgsT + { + WINRT_PROPERTY(winrt::hstring, ProposedName); + + public: + RenameWindowRequestedArgs(const winrt::hstring& name) : + _ProposedName{ name } {}; + }; + struct TerminalPage : TerminalPageT { public: @@ -82,6 +92,7 @@ namespace winrt::TerminalApp::implementation winrt::hstring KeyboardServiceDisabledText(); winrt::fire_and_forget IdentifyWindow(); + winrt::fire_and_forget RenameFailed(); winrt::fire_and_forget ProcessStartupActions(Windows::Foundation::Collections::IVector actions, const bool initial, @@ -110,6 +121,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable); TYPED_EVENT(Initialized, IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs); TYPED_EVENT(IdentifyWindowsRequested, IInspectable, IInspectable); + TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs); private: friend struct TerminalPageT; // for Xaml to bind events @@ -162,6 +174,7 @@ namespace winrt::TerminalApp::implementation bool _shouldStartInboundListener{ false }; std::shared_ptr _windowIdToast{ nullptr }; + std::shared_ptr _windowRenameFailedToast{ nullptr }; void _ShowAboutDialog(); winrt::Windows::Foundation::IAsyncOperation _ShowCloseWarningDialog(); @@ -311,6 +324,9 @@ namespace winrt::TerminalApp::implementation void _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); + void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs); + void _RequestWindowRename(const winrt::hstring& newName); + #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp #define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index d725d67ca..bf665d91b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -5,6 +5,11 @@ namespace TerminalApp { delegate void LastTabClosedEventArgs(); + [default_interface] runtimeclass RenameWindowRequestedArgs + { + String ProposedName { get; }; + }; + interface IDialogPresenter { Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog); @@ -27,6 +32,7 @@ namespace TerminalApp UInt64 WindowId; String WindowNameForDisplay { get; }; String WindowIdForDisplay { get; }; + void RenameFailed(); // We cannot use the default XAML APIs because we want to make sure // that there's only one application-global dialog visible at a time, @@ -47,5 +53,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler Initialized; event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; + event Windows.Foundation.TypedEventHandler RenameWindowRequested; } } diff --git a/src/cascadia/TerminalApp/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index 20bed6717..794b66de7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -126,6 +126,22 @@ Title="{x:Bind WindowIdForDisplay}" x:Load="False" IsLightDismissEnabled="True" - Subtitle="{x:Bind WindowNameForDisplay}" /> + Subtitle="{x:Bind WindowNameForDisplay, Mode=OneWay}" /> + + + + + + + + diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index de6e65453..7cdfc9679 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -55,6 +55,8 @@ static constexpr std::string_view TogglePaneReadOnlyKey{ "toggleReadOnlyMode" }; static constexpr std::string_view NewWindowKey{ "newWindow" }; static constexpr std::string_view IdentifyWindowKey{ "identifyWindow" }; static constexpr std::string_view IdentifyWindowsKey{ "identifyWindows" }; +static constexpr std::string_view RenameWindowKey{ "renameWindow" }; +static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" }; static constexpr std::string_view ActionKey{ "action" }; @@ -123,6 +125,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { NewWindowKey, ShortcutAction::NewWindow }, { IdentifyWindowKey, ShortcutAction::IdentifyWindow }, { IdentifyWindowsKey, ShortcutAction::IdentifyWindows }, + { RenameWindowKey, ShortcutAction::RenameWindow }, + { OpenWindowRenamerKey, ShortcutAction::OpenWindowRenamer }, }; using ParseResult = std::tuple>; @@ -157,6 +161,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::NewWindow, NewWindowArgs::FromJson }, { ShortcutAction::PrevTab, PrevTabArgs::FromJson }, { ShortcutAction::NextTab, NextTabArgs::FromJson }, + { ShortcutAction::RenameWindow, RenameWindowArgs::FromJson }, { ShortcutAction::Invalid, nullptr }, }; @@ -330,6 +335,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") }, { ShortcutAction::IdentifyWindow, RS_(L"IdentifyWindowCommandKey") }, { ShortcutAction::IdentifyWindows, RS_(L"IdentifyWindowsCommandKey") }, + { ShortcutAction::RenameWindow, RS_(L"ResetWindowNameCommandKey") }, + { ShortcutAction::OpenWindowRenamer, RS_(L"OpenWindowRenamerCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index cf4a4956f..aebc2f4ec 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -28,6 +28,7 @@ #include "NewWindowArgs.g.cpp" #include "PrevTabArgs.g.cpp" #include "NextTabArgs.g.cpp" +#include "RenameWindowArgs.g.cpp" #include @@ -567,4 +568,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation const auto mode = _SwitcherMode.Value() == TabSwitcherMode::MostRecentlyUsed ? L"most recently used" : L"in order"; return winrt::hstring(fmt::format(L"{}, {}", RS_(L"NextTabCommandKey"), mode)); } + + winrt::hstring RenameWindowArgs::GenerateName() const + { + // "Rename window to \"{_Name}\"" + // "Clear window name" + if (!_Name.empty()) + { + return winrt::hstring{ + fmt::format(std::wstring_view(RS_(L"RenameWindowCommandKey")), + _Name.c_str()) + }; + } + return RS_(L"ResetWindowNameCommandKey"); + } } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index ef5378190..3ef0c0bc9 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -30,6 +30,7 @@ #include "NewWindowArgs.g.h" #include "PrevTabArgs.g.h" #include "NextTabArgs.g.h" +#include "RenameWindowArgs.g.h" #include "../../cascadia/inc/cppwinrt_utils.h" #include "JsonUtils.h" @@ -1001,6 +1002,39 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return *copy; } }; + + struct RenameWindowArgs : public RenameWindowArgsT + { + RenameWindowArgs() = default; + WINRT_PROPERTY(winrt::hstring, Name); + static constexpr std::string_view NameKey{ "name" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + auto otherAsUs = other.try_as(); + if (otherAsUs) + { + return otherAsUs->_Name == _Name; + } + return false; + }; + static FromJsonResult FromJson(const Json::Value& json) + { + // LOAD BEARING: Not using make_self here _will_ break you in the future! + auto args = winrt::make_self(); + JsonUtils::GetValueForKey(json, NameKey, args->_Name); + return { *args, {} }; + } + IActionArgs Copy() const + { + auto copy{ winrt::make_self() }; + copy->_Name = _Name; + return *copy; + } + }; } namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index dcfb0219e..a65c9b46b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -246,4 +246,9 @@ namespace Microsoft.Terminal.Settings.Model { Windows.Foundation.IReference SwitcherMode; }; + + [default_interface] runtimeclass RenameWindowArgs : IActionArgs + { + String Name { get; }; + }; } diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index b5b40252e..cf32e5f36 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -70,4 +70,6 @@ ON_ALL_ACTIONS(FindMatch) \ ON_ALL_ACTIONS(NewWindow) \ ON_ALL_ACTIONS(IdentifyWindow) \ - ON_ALL_ACTIONS(IdentifyWindows) + ON_ALL_ACTIONS(IdentifyWindows) \ + ON_ALL_ACTIONS(RenameWindow) \ + ON_ALL_ACTIONS(OpenWindowRenamer) diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 65ca12e9d..c85aa0146 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -381,4 +381,14 @@ Open Settings... + + Rename window to "{0}" + {0} will be replaced with a user-defined string + + + Reset window name + + + Rename window... + diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index be855b333..46bc49417 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -295,8 +295,8 @@ { "command": "renameTab" }, { "command": "openTabRenamer" }, { "command": "commandPalette", "keys":"ctrl+shift+p" }, - { "command": "identifyWindow" }, + { "command": "openWindowRenamer" }, // Tab Management // "command": "closeTab" is unbound by default. diff --git a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp index d8ea2921b..35f145c3b 100644 --- a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp +++ b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp @@ -59,10 +59,12 @@ namespace RemotingUnitTests void DisplayWindowId() { throw winrt::hresult_error{}; }; Remoting::CommandlineArgs InitialArgs() { throw winrt::hresult_error{}; } Remoting::WindowActivatedArgs GetLastActivatedArgs() { throw winrt::hresult_error{}; } + void RequestRename(const Remoting::RenameRequestArgs& /*args*/) { throw winrt::hresult_error{}; } TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, Remoting::WindowActivatedArgs); TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, Remoting::CommandlineArgs); TYPED_EVENT(IdentifyWindowsRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, Remoting::RenameRequestArgs); }; class RemotingTests @@ -99,6 +101,10 @@ namespace RemotingUnitTests TEST_METHOD(ProposeCommandlineForNamedDeadWindow); + TEST_METHOD(TestRenameWindowSuccessfully); + TEST_METHOD(TestRenameSameNameAsAnother); + TEST_METHOD(TestRenameSameNameAsADeadPeasant); + TEST_CLASS_SETUP(ClassSetup) { return true; @@ -1212,6 +1218,7 @@ namespace RemotingUnitTests VERIFY_ARE_EQUAL(1u, m0->_peasants.size()); VERIFY_ARE_EQUAL(p2->GetID(), m0->_lookupPeasantIdForName(L"two")); } + void RemotingTests::GetMruPeasantAfterNameLookupForDeadPeasant() { // This test is trying to hit the catch in Monarch::_lookupPeasantIdForName. @@ -1356,4 +1363,147 @@ namespace RemotingUnitTests VERIFY_ARE_EQUAL(L"two", result.WindowName()); } } + + void RemotingTests::TestRenameWindowSuccessfully() + { + Log::Comment(L"Attempt to rename a window. This should succeed."); + + const auto monarch0PID = 12345u; + const auto peasant1PID = 23456u; + const auto peasant2PID = 34567u; + + com_ptr m0; + m0.attach(new Remoting::implementation::Monarch(monarch0PID)); + + com_ptr p1; + p1.attach(new Remoting::implementation::Peasant(peasant1PID)); + + com_ptr p2; + p2.attach(new Remoting::implementation::Peasant(peasant2PID)); + + VERIFY_IS_NOT_NULL(m0); + VERIFY_IS_NOT_NULL(p1); + VERIFY_IS_NOT_NULL(p2); + p1->WindowName(L"one"); + p2->WindowName(L"two"); + + VERIFY_ARE_EQUAL(0, p1->GetID()); + VERIFY_ARE_EQUAL(0, p2->GetID()); + + m0->AddPeasant(*p1); + m0->AddPeasant(*p2); + + VERIFY_ARE_EQUAL(1, p1->GetID()); + VERIFY_ARE_EQUAL(2, p2->GetID()); + + VERIFY_ARE_EQUAL(2u, m0->_peasants.size()); + + Remoting::RenameRequestArgs eventArgs{ L"foo" }; + p1->RequestRename(eventArgs); + + VERIFY_IS_TRUE(eventArgs.Succeeded()); + VERIFY_ARE_EQUAL(L"foo", p1->WindowName()); + + VERIFY_ARE_EQUAL(0, m0->_lookupPeasantIdForName(L"one")); + VERIFY_ARE_EQUAL(p2->GetID(), m0->_lookupPeasantIdForName(L"two")); + VERIFY_ARE_EQUAL(p1->GetID(), m0->_lookupPeasantIdForName(L"foo")); + } + + void RemotingTests::TestRenameSameNameAsAnother() + { + Log::Comment(L"Try renaming a window to a name used by another peasant." + L" This should fail."); + + const auto monarch0PID = 12345u; + const auto peasant1PID = 23456u; + const auto peasant2PID = 34567u; + + com_ptr m0; + m0.attach(new Remoting::implementation::Monarch(monarch0PID)); + + com_ptr p1; + p1.attach(new Remoting::implementation::Peasant(peasant1PID)); + + com_ptr p2; + p2.attach(new Remoting::implementation::Peasant(peasant2PID)); + + VERIFY_IS_NOT_NULL(m0); + VERIFY_IS_NOT_NULL(p1); + VERIFY_IS_NOT_NULL(p2); + p1->WindowName(L"one"); + p2->WindowName(L"two"); + + VERIFY_ARE_EQUAL(0, p1->GetID()); + VERIFY_ARE_EQUAL(0, p2->GetID()); + + m0->AddPeasant(*p1); + m0->AddPeasant(*p2); + + VERIFY_ARE_EQUAL(1, p1->GetID()); + VERIFY_ARE_EQUAL(2, p2->GetID()); + + VERIFY_ARE_EQUAL(2u, m0->_peasants.size()); + + Remoting::RenameRequestArgs eventArgs{ L"two" }; + p1->RequestRename(eventArgs); + + VERIFY_IS_FALSE(eventArgs.Succeeded()); + VERIFY_ARE_EQUAL(L"one", p1->WindowName()); + + VERIFY_ARE_EQUAL(p1->GetID(), m0->_lookupPeasantIdForName(L"one")); + VERIFY_ARE_EQUAL(p2->GetID(), m0->_lookupPeasantIdForName(L"two")); + } + void RemotingTests::TestRenameSameNameAsADeadPeasant() + { + Log::Comment(L"We'll try renaming a window to the name of a window that" + L" has died. This should succeed, without crashing."); + + const auto monarch0PID = 12345u; + const auto peasant1PID = 23456u; + const auto peasant2PID = 34567u; + + com_ptr m0; + m0.attach(new Remoting::implementation::Monarch(monarch0PID)); + + com_ptr p1; + p1.attach(new Remoting::implementation::Peasant(peasant1PID)); + + com_ptr p2; + p2.attach(new Remoting::implementation::Peasant(peasant2PID)); + + VERIFY_IS_NOT_NULL(m0); + VERIFY_IS_NOT_NULL(p1); + VERIFY_IS_NOT_NULL(p2); + p1->WindowName(L"one"); + p2->WindowName(L"two"); + + VERIFY_ARE_EQUAL(0, p1->GetID()); + VERIFY_ARE_EQUAL(0, p2->GetID()); + + m0->AddPeasant(*p1); + m0->AddPeasant(*p2); + + VERIFY_ARE_EQUAL(1, p1->GetID()); + VERIFY_ARE_EQUAL(2, p2->GetID()); + + VERIFY_ARE_EQUAL(2u, m0->_peasants.size()); + + Remoting::RenameRequestArgs eventArgs{ L"two" }; + p1->RequestRename(eventArgs); + + VERIFY_IS_FALSE(eventArgs.Succeeded()); + VERIFY_ARE_EQUAL(L"one", p1->WindowName()); + + VERIFY_ARE_EQUAL(p1->GetID(), m0->_lookupPeasantIdForName(L"one")); + VERIFY_ARE_EQUAL(p2->GetID(), m0->_lookupPeasantIdForName(L"two")); + + RemotingTests::_killPeasant(m0, p2->GetID()); + + p1->RequestRename(eventArgs); + + VERIFY_IS_TRUE(eventArgs.Succeeded()); + VERIFY_ARE_EQUAL(L"two", p1->WindowName()); + VERIFY_ARE_EQUAL(p1->GetID(), m0->_lookupPeasantIdForName(L"two")); + } + } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index a39a0ad66..6082ea459 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -250,6 +250,7 @@ void AppHost::Initialize() _logic.LastTabClosed({ this, &AppHost::LastTabClosed }); _logic.SetTaskbarProgress({ this, &AppHost::SetTaskbarProgress }); _logic.IdentifyWindowsRequested({ this, &AppHost::_IdentifyWindowsRequested }); + _logic.RenameWindowRequested({ this, &AppHost::_RenameWindowRequested }); _window->UpdateTitle(_logic.Title()); @@ -618,8 +619,8 @@ GUID AppHost::_CurrentDesktopGuid() // - // Return Value: // - -winrt::fire_and_forget AppHost::_IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*args*/) +winrt::fire_and_forget AppHost::_IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable /*sender*/, + const winrt::Windows::Foundation::IInspectable /*args*/) { // We'll be raising an event that may result in a RPC call to the monarch - // make sure we're on the background thread, or this will silently fail @@ -643,3 +644,33 @@ void AppHost::_DisplayWindowId(const winrt::Windows::Foundation::IInspectable& / { _logic.IdentifyWindow(); } + +winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Foundation::IInspectable /*sender*/, + const winrt::TerminalApp::RenameWindowRequestedArgs args) +{ + // Capture calling context. + winrt::apartment_context ui_thread; + + // Switch to the BG thread - anything x-proc must happen on a BG thread + co_await winrt::resume_background(); + + if (auto peasant{ _windowManager.CurrentWindow() }) + { + Remoting::RenameRequestArgs requestArgs{ args.ProposedName() }; + + peasant.RequestRename(requestArgs); + + // Switch back to the UI thread. Setting the WindowName needs to happen + // on the UI thread, because it'll raise a PropertyChanged event + co_await ui_thread; + + if (requestArgs.Succeeded()) + { + _logic.WindowName(args.ProposedName()); + } + else + { + _logic.RenameFailed(); + } + } +} diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 723a46048..a3a22603a 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -51,9 +51,12 @@ private: void _FindTargetWindow(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args); - winrt::fire_and_forget _IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable& sender, - const winrt::Windows::Foundation::IInspectable& args); + winrt::fire_and_forget _IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable sender, + const winrt::Windows::Foundation::IInspectable args); void _DisplayWindowId(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + winrt::fire_and_forget _RenameWindowRequested(const winrt::Windows::Foundation::IInspectable sender, + const winrt::TerminalApp::RenameWindowRequestedArgs args); + GUID _CurrentDesktopGuid(); }; diff --git a/tools/README.md b/tools/README.md index 138a40d55..94e841a0b 100644 --- a/tools/README.md +++ b/tools/README.md @@ -62,7 +62,7 @@ running this on save, but you can add a `git` hook to format before committing XSTYLER_PATH="dotnet tool run xstyler --" # Define path to XAML Styler configuration -XSTYLER_CONFIG="../../XamlStyler.json" +XSTYLER_CONFIG="XamlStyler.json" echo "Running XAML Styler on committed XAML files" git diff --cached --name-only --diff-filter=ACM | grep -e '\.xaml$' | \