diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 724f7dafb70..ac01af8cb3b 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -1264,6 +1264,113 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_HandleSaveSnippet(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if constexpr (!Feature_SaveSnippet::IsEnabled()) + { + return; + } + + if (args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + auto commandLine = realArgs.Commandline(); + if (commandLine.empty()) + { + if (const auto termControl{ _GetActiveControl() }) + { + if (termControl.HasSelection()) + { + const auto selections{ termControl.SelectedText(true) }; + const auto selection = std::accumulate(selections.begin(), selections.end(), std::wstring()); + commandLine = selection; + } + } + } + + if (commandLine.empty()) + { + ActionSaveFailed(L"CommandLine is Required"); + return; + } + + try + { + KeyChord keyChord = nullptr; + if (!realArgs.KeyChord().empty()) + { + keyChord = KeyChordSerialization::FromString(winrt::to_hstring(realArgs.KeyChord())); + } + _settings.GlobalSettings().ActionMap().AddSendInputAction(realArgs.Name(), commandLine, keyChord); + _settings.WriteSettingsToDisk(); + ActionSaved(commandLine, realArgs.Name(), realArgs.KeyChord()); + } + catch (const winrt::hresult_error& ex) + { + auto code = ex.code(); + auto message = ex.message(); + ActionSaveFailed(message); + args.Handled(true); + return; + } + + args.Handled(true); + } + } + } + + void TerminalPage::ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord) + { + // If we haven't ever loaded the TeachingTip, then do so now and + // create the toast for it. + if (_actionSavedToast == nullptr) + { + if (auto tip{ FindName(L"ActionSavedToast").try_as() }) + { + _actionSavedToast = std::make_shared(tip); + // Make sure to use the weak ref when setting up this + // callback. + tip.Closed({ get_weak(), &TerminalPage::_FocusActiveControl }); + } + } + _UpdateTeachingTipTheme(ActionSavedToast().try_as()); + + SavedActionName(name); + SavedActionKeyChord(keyChord); + SavedActionCommandLine(input); + + if (_actionSavedToast != nullptr) + { + _actionSavedToast->Open(); + } + } + + void TerminalPage::ActionSaveFailed(winrt::hstring message) + { + // If we haven't ever loaded the TeachingTip, then do so now and + // create the toast for it. + if (_actionSaveFailedToast == nullptr) + { + if (auto tip{ FindName(L"ActionSaveFailedToast").try_as() }) + { + _actionSaveFailedToast = std::make_shared(tip); + // Make sure to use the weak ref when setting up this + // callback. + tip.Closed({ get_weak(), &TerminalPage::_FocusActiveControl }); + } + } + _UpdateTeachingTipTheme(ActionSaveFailedToast().try_as()); + + ActionSaveFailedMessage().Text(message); + + if (_actionSaveFailedToast != nullptr) + { + _actionSaveFailedToast->Open(); + } + } + void TerminalPage::_HandleSelectCommand(const IInspectable& /*sender*/, const ActionEventArgs& args) { diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index b7a6b581a05..c1154f56728 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -209,6 +209,7 @@ void AppCommandlineArgs::_buildParser() _buildMovePaneParser(); _buildSwapPaneParser(); _buildFocusPaneParser(); + _buildSaveSnippetParser(); } // Method Description: @@ -537,6 +538,72 @@ void AppCommandlineArgs::_buildFocusPaneParser() setupSubcommand(_focusPaneShort); } +void AppCommandlineArgs::_buildSaveSnippetParser() +{ + _saveCommand = _app.add_subcommand("x-save-snippet", RS_A(L"SaveSnippetDesc")); + + auto setupSubcommand = [this](auto* subcommand) { + subcommand->add_option("--name,-n", _saveInputName, RS_A(L"SaveSnippetArgDesc")); + subcommand->add_option("--keychord,-k", _keyChordOption, RS_A(L"KeyChordArgDesc")); + 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 action from the values we've parsed on the commandline. + ActionAndArgs saveSnippet{}; + saveSnippet.Action(ShortcutAction::SaveSnippet); + // First, parse out the commandline in the same way that + // _getNewTerminalArgs does it + SaveSnippetArgs 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())); + } + + if (!_keyChordOption.empty()) + { + args.KeyChord(winrt::to_hstring(_keyChordOption)); + } + + if (!_saveInputName.empty()) + { + winrt::hstring hString = winrt::to_hstring(_saveInputName); + args.Name(hString); + } + + saveSnippet.Args(args); + _startupActions.push_back(saveSnippet); + }); + }; + + setupSubcommand(_saveCommand); +} + // Method Description: // - Add the `NewTerminalArgs` parameters to the given subcommand. This enables // that subcommand to support all the properties in a NewTerminalArgs. @@ -710,7 +777,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 7eb5a34f93d..7eb2516bb38 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -93,6 +93,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! @@ -123,6 +124,8 @@ class TerminalApp::AppCommandlineArgs final bool _focusPrevTab{ false }; int _focusPaneTarget{ -1 }; + std::string _saveInputName; + std::string _keyChordOption; // Are you adding more args here? Make sure to reset them in _resetStateToDefault const Commandline* _currentCommandline{ nullptr }; @@ -141,6 +144,7 @@ class TerminalApp::AppCommandlineArgs final winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand); void _addNewTerminalArgs(NewTerminalSubcommand& subcommand); void _buildParser(); + void _buildSaveSnippetParser(); void _buildNewTabParser(); void _buildSplitPaneParser(); void _buildFocusTabParser(); diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 15b79c40ffc..efe3e1278e2 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -288,6 +288,15 @@ An optional command, with arguments, to be spawned in the new tab or pane + + Save command line as input action + + + An optional argument + + + An optional argument + Move focus to another tab @@ -898,4 +907,10 @@ Restart the active pane connection + + Action saved + + + Action save failed + diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index d0629da8036..238fecf9afc 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -357,7 +357,9 @@ - + + Designer + @@ -466,10 +468,8 @@ - - @@ -727,4 +727,7 @@ Open about dialog This will open the "about" dialog, to display version info and other documentation - \ No newline at end of file + + Save Snippet + + diff --git a/src/features.xml b/src/features.xml index 47d86790f9d..b87e09bb7c4 100644 --- a/src/features.xml +++ b/src/features.xml @@ -155,6 +155,17 @@ + + Feature_SaveSnippet + Save Snippet + 9971 + AlwaysDisabled + + Dev + Canary + + + Feature_QuickFix Enables the Quick Fix menu