diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 481fce33620..dede7300c50 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -1234,6 +1234,34 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } } + + void TerminalPage::_HandleSaveTask(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + if (realArgs.Commandline().empty()) + { + if (const auto termControl{ _GetActiveControl() }) + { + if (termControl.HasSelection()) + { + const auto selections{ termControl.SelectedText(true) }; + auto input = std::accumulate(selections.begin(), selections.end(), std::wstring()); + realArgs.Commandline(input); + } + } + } + + _settings.GlobalSettings().ActionMap().AddSendInputAction(realArgs.GenerateName(), realArgs.Commandline()); + _settings.WriteSettingsToDisk(); + ActionSaved(realArgs.Commandline()); + args.Handled(true); + } + } + } void TerminalPage::_HandleSelectCommand(const IInspectable& /*sender*/, const ActionEventArgs& args) diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index 5060b4ff2f7..49241e73bfa 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -209,6 +209,7 @@ void AppCommandlineArgs::_buildParser() _buildMovePaneParser(); _buildSwapPaneParser(); _buildFocusPaneParser(); + _buildSaveParser(); } // Method Description: @@ -537,6 +538,59 @@ void AppCommandlineArgs::_buildFocusPaneParser() setupSubcommand(_focusPaneShort); } +void AppCommandlineArgs::_buildSaveParser() +{ + _saveCommand = _app.add_subcommand("save", "TODO Desc"); + + auto setupSubcommand = [this](auto* subcommand) { + subcommand->add_option("command", _commandline, RS_A(L"CmdCommandArgDesc")); + subcommand->positionals_at_end(true); + + // When ParseCommand is called, if this subcommand was provided, this + // callback function will be triggered on the same thread. We can be sure + // that `this` will still be safe - this function just lets us know this + // command was parsed. + subcommand->callback([&, this]() { + // Build the NewTab action from the values we've parsed on the commandline. + ActionAndArgs saveAction{}; + saveAction.Action(ShortcutAction::SaveTask); + // _getNewTerminalArgs MUST be called before parsing any other options, + // as it might clear those options while finding the commandline + SaveTaskArgs args{}; + + if (!_commandline.empty()) + { + std::ostringstream cmdlineBuffer; + + for (const auto& arg : _commandline) + { + if (cmdlineBuffer.tellp() != 0) + { + // If there's already something in here, prepend a space + cmdlineBuffer << ' '; + } + + if (arg.find(" ") != std::string::npos) + { + cmdlineBuffer << '"' << arg << '"'; + } + else + { + cmdlineBuffer << arg; + } + } + + args.Commandline(winrt::to_hstring(cmdlineBuffer.str())); + } + + saveAction.Args(args); + _startupActions.push_back(saveAction); + }); + }; + + setupSubcommand(_saveCommand); +} + // Method Description: // - Add the `NewTerminalArgs` parameters to the given subcommand. This enables // that subcommand to support all the properties in a NewTerminalArgs. @@ -700,7 +754,8 @@ bool AppCommandlineArgs::_noCommandsProvided() *_focusPaneCommand || *_focusPaneShort || *_newPaneShort.subcommand || - *_newPaneCommand.subcommand); + *_newPaneCommand.subcommand || + *_saveCommand); } // Method Description: diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h index 9145697f453..0a5bcf345e5 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -92,6 +92,7 @@ class TerminalApp::AppCommandlineArgs final CLI::App* _swapPaneCommand; CLI::App* _focusPaneCommand; CLI::App* _focusPaneShort; + CLI::App* _saveCommand; // Are you adding a new sub-command? Make sure to update _noCommandsProvided! @@ -139,6 +140,7 @@ class TerminalApp::AppCommandlineArgs final winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand); void _addNewTerminalArgs(NewTerminalSubcommand& subcommand); void _buildParser(); + void _buildSaveParser(); void _buildNewTabParser(); void _buildSplitPaneParser(); void _buildFocusTabParser(); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index d8673377ccf..cca27e9ab17 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -4205,6 +4205,34 @@ namespace winrt::TerminalApp::implementation } } + winrt::fire_and_forget TerminalPage::ActionSaved(winrt::hstring input) + { + auto weakThis{ get_weak() }; + co_await wil::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->_actionSavedToast == nullptr) + { + if (auto tip{ page->FindName(L"ActionSavedToast").try_as() }) + { + page->_actionSavedToast = std::make_shared(tip); + // Make sure to use the weak ref when setting up this + // callback. + tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl }); + } + } + _UpdateTeachingTipTheme(ActionSavedToast().try_as()); + ActionSavedTextBox().Text(input); + + if (page->_actionSavedToast != nullptr) + { + page->_actionSavedToast->Open(); + } + } + } + // 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 diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 90caedf4a24..793da7a355a 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -149,6 +149,7 @@ namespace winrt::TerminalApp::implementation winrt::hstring KeyboardServiceDisabledText(); winrt::fire_and_forget IdentifyWindow(); + winrt::fire_and_forget ActionSaved(winrt::hstring input); winrt::fire_and_forget RenameFailed(); winrt::fire_and_forget ShowTerminalWorkingDirectory(); @@ -260,6 +261,7 @@ namespace winrt::TerminalApp::implementation bool _isEmbeddingInboundListener{ false }; std::shared_ptr _windowIdToast{ nullptr }; + std::shared_ptr _actionSavedToast{ nullptr }; std::shared_ptr _windowRenameFailedToast{ nullptr }; std::shared_ptr _windowCwdToast{ nullptr }; diff --git a/src/cascadia/TerminalApp/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index 600ec505c67..7492c9169af 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -219,5 +219,16 @@ Title="{x:Bind WindowProperties.VirtualWorkingDirectory, Mode=OneWay}" x:Load="False" IsLightDismissEnabled="True" /> + + + + + + diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index e90a2b8dcfa..5781bcc685c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -52,6 +52,7 @@ static constexpr std::string_view SwitchToTabKey{ "switchToTab" }; static constexpr std::string_view TabSearchKey{ "tabSearch" }; static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" }; static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" }; +static constexpr std::string_view SaveTaskKey{ "saveTask" }; static constexpr std::string_view SuggestionsKey{ "showSuggestions" }; static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" }; static constexpr std::string_view SetFocusModeKey{ "setFocusMode" }; @@ -388,6 +389,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::TabSearch, RS_(L"TabSearchCommandKey") }, { ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") }, { ShortcutAction::ToggleCommandPalette, MustGenerate }, + { ShortcutAction::SaveTask, MustGenerate }, { ShortcutAction::Suggestions, MustGenerate }, { ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") }, { ShortcutAction::SetFocusMode, MustGenerate }, diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index a276ebeff2b..4c6eb384304 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -32,6 +32,7 @@ #include "ScrollToMarkArgs.g.cpp" #include "AddMarkArgs.g.cpp" #include "FindMatchArgs.g.cpp" +#include "SaveTaskArgs.g.cpp" #include "ToggleCommandPaletteArgs.g.cpp" #include "SuggestionsArgs.g.cpp" #include "NewWindowArgs.g.cpp" @@ -939,6 +940,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } + winrt::hstring SaveTaskArgs::GenerateName() const + { + return winrt::hstring{ + fmt::format(L"Save Task commandline:{}, name: {}, description: {}", Commandline(), Name(), Description()) + }; + } + static winrt::hstring _FormatColorString(const Control::SelectionColor& selectionColor) { if (!selectionColor) diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 6d83964cbbc..7fdd277604e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -33,6 +33,7 @@ #include "ScrollToMarkArgs.g.h" #include "AddMarkArgs.g.h" #include "MoveTabArgs.g.h" +#include "SaveTaskArgs.g.h" #include "ToggleCommandPaletteArgs.g.h" #include "SuggestionsArgs.g.h" #include "FindMatchArgs.g.h" @@ -214,6 +215,12 @@ private: \ #define TOGGLE_COMMAND_PALETTE_ARGS(X) \ X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, CommandPaletteLaunchMode::Action) +//////////////////////////////////////////////////////////////////////////////// +#define SAVE_TASK_ARGS(X) \ + X(winrt::hstring, Name, "name", false, L"") \ + X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), L"") \ + X(winrt::hstring, Description, "description", false, L"") + //////////////////////////////////////////////////////////////////////////////// #define SUGGESTIONS_ARGS(X) \ X(SuggestionsSource, Source, "source", false, SuggestionsSource::Tasks) \ @@ -725,6 +732,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ACTION_ARGS_STRUCT(ToggleCommandPaletteArgs, TOGGLE_COMMAND_PALETTE_ARGS); + ACTION_ARGS_STRUCT(SaveTaskArgs, SAVE_TASK_ARGS); + ACTION_ARGS_STRUCT(SuggestionsArgs, SUGGESTIONS_ARGS); ACTION_ARGS_STRUCT(FindMatchArgs, FIND_MATCH_ARGS); @@ -845,6 +854,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation BASIC_FACTORY(CloseTabArgs); BASIC_FACTORY(MoveTabArgs); BASIC_FACTORY(OpenSettingsArgs); + BASIC_FACTORY(SaveTaskArgs); BASIC_FACTORY(FindMatchArgs); BASIC_FACTORY(NewWindowArgs); BASIC_FACTORY(FocusPaneArgs); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index 32f9005fdee..0741cc82fa6 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -344,6 +344,15 @@ namespace Microsoft.Terminal.Settings.Model FindMatchDirection Direction { get; }; }; + [default_interface] runtimeclass SaveTaskArgs : IActionArgs + { + SaveTaskArgs(); + SaveTaskArgs(String Name, String Commandline, String Description); + String Name; + String Commandline; + String Description; + }; + [default_interface] runtimeclass NewWindowArgs : IActionArgs { NewWindowArgs(NewTerminalArgs terminalArgs); diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 76565dff444..2da97f0d2c5 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -857,6 +857,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation AddAction(*cmd); } + void ActionMap::AddSendInputAction(winrt::hstring name, winrt::hstring input) + { + auto newAction = winrt::make(); + newAction.Action(ShortcutAction::SendInput); + auto sendInputArgs = winrt::make(input); + newAction.Args(sendInputArgs); + auto cmd{ make_self() }; + cmd->ActionAndArgs(newAction); + cmd->Name(name); + AddAction(*cmd); + } + void ActionMap::_recursiveUpdateCommandKeybindingLabels() { const auto& commands{ _ExpandedCommandsCache }; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 4270a081118..f0fba391d52 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -74,6 +74,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys); void DeleteKeyBinding(const Control::KeyChord& keys); void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action); + void AddSendInputAction(winrt::hstring name, winrt::hstring input); Windows::Foundation::Collections::IVector ExpandedCommands(); void ExpandCommands(const Windows::Foundation::Collections::IVectorView& profiles, diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl index b84305b9d0e..69f37fff15f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.idl +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -32,5 +32,6 @@ namespace Microsoft.Terminal.Settings.Model void DeleteKeyBinding(Microsoft.Terminal.Control.KeyChord keys); void RegisterKeyBinding(Microsoft.Terminal.Control.KeyChord keys, ActionAndArgs action); + void AddSendInputAction(String name, String input); } } diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index f9d934e36e0..846f80527c5 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -75,6 +75,7 @@ ON_ALL_ACTIONS(CloseTabsAfter) \ ON_ALL_ACTIONS(TabSearch) \ ON_ALL_ACTIONS(MoveTab) \ + ON_ALL_ACTIONS(SaveTask) \ ON_ALL_ACTIONS(BreakIntoDebugger) \ ON_ALL_ACTIONS(TogglePaneReadOnly) \ ON_ALL_ACTIONS(EnablePaneReadOnly) \ @@ -147,6 +148,7 @@ ON_ALL_ACTIONS_WITH_ARGS(SplitPane) \ ON_ALL_ACTIONS_WITH_ARGS(SwitchToTab) \ ON_ALL_ACTIONS_WITH_ARGS(ToggleCommandPalette) \ + ON_ALL_ACTIONS_WITH_ARGS(SaveTask) \ ON_ALL_ACTIONS_WITH_ARGS(FocusPane) \ ON_ALL_ACTIONS_WITH_ARGS(ExportBuffer) \ ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer) \