diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 4d4425fd3f2..d4f972d9ede 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -281,6 +281,7 @@ "ShortcutActionName": { "enum": [ "adjustFontSize", + "clearBuffer", "closeOtherTabs", "closePane", "closeTab", @@ -289,6 +290,7 @@ "commandPalette", "copy", "duplicateTab", + "exportBuffer", "find", "findMatch", "focusPane", @@ -434,6 +436,14 @@ ], "type": "string" }, + "ClearBufferType": { + "enum": [ + "all", + "screen", + "scrollback" + ], + "type": "string" + }, "NewTerminalArgs": { "properties": { "commandline": { @@ -1266,6 +1276,48 @@ } ] }, + "ClearBufferAction": { + "description": "Arguments corresponding to a clearBuffer Action", + "allOf": [ + { + "$ref": "#/$defs/ShortcutAction" + }, + { + "properties": { + "action": { + "type": "string", + "const": "clearBuffer" + }, + "clear": { + "$ref": "#/$defs/ClearBufferType", + "default": "all", + "description": "What to clear. Accepts one of `screen`, `scrollback` or `all` (for both)." + } + } + } + ] + }, + "ExportBufferAction": { + "description": "Arguments corresponding to a exportBuffer Action", + "allOf": [ + { + "$ref": "#/$defs/ShortcutAction" + }, + { + "properties": { + "action": { + "type": "string", + "const": "exportBuffer" + }, + "path": { + "type": "string", + "default": "", + "description": "The path to export the text buffer to. If left blank, the Terminal will open a file picker to choose the path." + } + } + } + ] + }, "GlobalSummonAction": { "description": "This is a special action that works globally in the OS, rather than only in the context of the terminal window. When pressed, this action will summon the terminal window.", "allOf": [ @@ -1429,6 +1481,12 @@ { "$ref": "#/$defs/FocusPaneAction" }, + { + "$ref": "#/$defs/ExportBufferAction" + }, + { + "$ref": "#/$defs/ClearBufferAction" + }, { "$ref": "#/$defs/GlobalSummonAction" }, diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index fa5f7210207..213d514b063 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -906,6 +906,27 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } + void TerminalPage::_HandleExportBuffer(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (const auto activeTab{ _GetFocusedTabImpl() }) + { + if (args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + _ExportTab(*activeTab, realArgs.Path()); + args.Handled(true); + return; + } + } + + // If we didn't have args, or the args weren't ExportBufferArgs (somehow) + _ExportTab(*activeTab, L""); + args.Handled(true); + } + } + void TerminalPage::_HandleClearBuffer(const IInspectable& /*sender*/, const ActionEventArgs& args) { diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 04d4260d941..2989e847a3c 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -194,7 +194,9 @@ namespace winrt::TerminalApp::implementation if (page && tab) { - page->_ExportTab(*tab); + // Passing null args to the ExportBuffer handler will default it + // to prompting for the path + page->_HandleExportBuffer(nullptr, nullptr); } }); @@ -359,7 +361,7 @@ namespace winrt::TerminalApp::implementation // - Exports the content of the Terminal Buffer inside the tab // Arguments: // - tab: tab to export - winrt::fire_and_forget TerminalPage::_ExportTab(const TerminalTab& tab) + winrt::fire_and_forget TerminalPage::_ExportTab(const TerminalTab& tab, winrt::hstring filepath) { // This will be used to set up the file picker "filter", to select .txt // files by default. @@ -376,25 +378,38 @@ namespace winrt::TerminalApp::implementation { if (const auto control{ tab.GetActiveTerminalControl() }) { - // GH#11356 - we can't use the UWP apis for writing the file, - // because they don't work elevated (shocker) So just use the - // shell32 file picker manually. - auto path = co_await SaveFilePicker(*_hostingHwnd, [control](auto&& dialog) { - THROW_IF_FAILED(dialog->SetClientGuid(clientGuidExportFile)); - try - { - // Default to the Downloads folder - auto folderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_Downloads, KF_FLAG_DEFAULT, nullptr) }; - dialog->SetDefaultFolder(folderShellItem.get()); - } - CATCH_LOG(); // non-fatal - THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedFileTypes), supportedFileTypes)); - THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed - THROW_IF_FAILED(dialog->SetDefaultExtension(L"txt")); - - // Default to using the tab title as the file name - THROW_IF_FAILED(dialog->SetFileName((control.Title() + L".txt").c_str())); - }); + auto path = filepath; + + if (path.empty()) + { + // GH#11356 - we can't use the UWP apis for writing the file, + // because they don't work elevated (shocker) So just use the + // shell32 file picker manually. + path = co_await SaveFilePicker(*_hostingHwnd, [control](auto&& dialog) { + THROW_IF_FAILED(dialog->SetClientGuid(clientGuidExportFile)); + try + { + // Default to the Downloads folder + auto folderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_Downloads, KF_FLAG_DEFAULT, nullptr) }; + dialog->SetDefaultFolder(folderShellItem.get()); + } + CATCH_LOG(); // non-fatal + THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedFileTypes), supportedFileTypes)); + THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed + THROW_IF_FAILED(dialog->SetDefaultExtension(L"txt")); + + // Default to using the tab title as the file name + THROW_IF_FAILED(dialog->SetFileName((control.Title() + L".txt").c_str())); + }); + } + else + { + // The file picker isn't going to give us paths with + // environment variables, but the user might have set one in + // the settings. Expand those here. + + path = { wil::ExpandEnvironmentStringsW(path.c_str()) }; + } if (!path.empty()) { diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 07da72e37f9..bbe9e4f2ecb 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -256,7 +256,7 @@ namespace winrt::TerminalApp::implementation void _DuplicateTab(const TerminalTab& tab); void _SplitTab(TerminalTab& tab); - winrt::fire_and_forget _ExportTab(const TerminalTab& tab); + winrt::fire_and_forget _ExportTab(const TerminalTab& tab, winrt::hstring filepath); winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab); void _CloseTabAtIndex(uint32_t index); diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 623f4f46956..b6d8d2b1f3e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -69,6 +69,7 @@ static constexpr std::string_view GlobalSummonKey{ "globalSummon" }; static constexpr std::string_view QuakeModeKey{ "quakeMode" }; static constexpr std::string_view FocusPaneKey{ "focusPane" }; static constexpr std::string_view OpenSystemMenuKey{ "openSystemMenu" }; +static constexpr std::string_view ExportBufferKey{ "exportBuffer" }; static constexpr std::string_view ClearBufferKey{ "clearBuffer" }; static constexpr std::string_view MultipleActionsKey{ "multipleActions" }; static constexpr std::string_view QuitKey{ "quit" }; @@ -378,6 +379,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") }, { ShortcutAction::FocusPane, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::OpenSystemMenu, RS_(L"OpenSystemMenuCommandKey") }, + { ShortcutAction::ExportBuffer, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::ClearBuffer, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::MultipleActions, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::Quit, RS_(L"QuitCommandKey") }, diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index 2ce75db24df..c13e0e42313 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -37,6 +37,7 @@ #include "RenameWindowArgs.g.cpp" #include "GlobalSummonArgs.g.cpp" #include "FocusPaneArgs.g.cpp" +#include "ExportBufferArgs.g.cpp" #include "ClearBufferArgs.g.cpp" #include "MultipleActionsArgs.g.cpp" @@ -727,6 +728,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Id()) }; } + + winrt::hstring ExportBufferArgs::GenerateName() const + { + if (!Path().empty()) + { + // "Export text to {path}" + return winrt::hstring{ + fmt::format(std::wstring_view(RS_(L"ExportBufferToPathCommandKey")), + Path()) + }; + } + else + { + // "Export text" + return RS_(L"ExportBufferCommandKey"); + } + } + winrt::hstring ClearBufferArgs::GenerateName() const { // "Clear Buffer" diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 072c54f2cfa..2246d9baab5 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -39,6 +39,7 @@ #include "RenameWindowArgs.g.h" #include "GlobalSummonArgs.g.h" #include "FocusPaneArgs.g.h" +#include "ExportBufferArgs.g.h" #include "ClearBufferArgs.g.h" #include "MultipleActionsArgs.g.h" @@ -210,6 +211,10 @@ private: #define FOCUS_PANE_ARGS(X) \ X(uint32_t, Id, "id", false, 0u) +//////////////////////////////////////////////////////////////////////////////// +#define EXPORT_BUFFER_ARGS(X) \ + X(winrt::hstring, Path, "path", false, L"") + //////////////////////////////////////////////////////////////////////////////// #define CLEAR_BUFFER_ARGS(X) \ X(winrt::Microsoft::Terminal::Control::ClearBufferType, Clear, "clear", false, winrt::Microsoft::Terminal::Control::ClearBufferType::All) @@ -631,6 +636,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ACTION_ARGS_STRUCT(FocusPaneArgs, FOCUS_PANE_ARGS); + ACTION_ARGS_STRUCT(ExportBufferArgs, EXPORT_BUFFER_ARGS); + ACTION_ARGS_STRUCT(ClearBufferArgs, CLEAR_BUFFER_ARGS); struct MultipleActionsArgs : public MultipleActionsArgsT @@ -713,6 +720,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation BASIC_FACTORY(FocusPaneArgs); BASIC_FACTORY(PrevTabArgs); BASIC_FACTORY(NextTabArgs); + BASIC_FACTORY(ExportBufferArgs); BASIC_FACTORY(ClearBufferArgs); BASIC_FACTORY(MultipleActionsArgs); } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index 61cc5d2f85d..bcbc63b1b6c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -342,6 +342,12 @@ namespace Microsoft.Terminal.Settings.Model UInt32 Id { get; }; }; + [default_interface] runtimeclass ExportBufferArgs : IActionArgs + { + ExportBufferArgs(String path); + String Path { get; }; + }; + [default_interface] runtimeclass ClearBufferArgs : IActionArgs { ClearBufferArgs(Microsoft.Terminal.Control.ClearBufferType clear); diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index d8d26d3b81e..91154ffbfc5 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -83,6 +83,7 @@ ON_ALL_ACTIONS(QuakeMode) \ ON_ALL_ACTIONS(FocusPane) \ ON_ALL_ACTIONS(OpenSystemMenu) \ + ON_ALL_ACTIONS(ExportBuffer) \ ON_ALL_ACTIONS(ClearBuffer) \ ON_ALL_ACTIONS(MultipleActions) \ ON_ALL_ACTIONS(Quit) \ @@ -121,5 +122,6 @@ ON_ALL_ACTIONS_WITH_ARGS(SwitchToTab) \ ON_ALL_ACTIONS_WITH_ARGS(ToggleCommandPalette) \ ON_ALL_ACTIONS_WITH_ARGS(FocusPane) \ + ON_ALL_ACTIONS_WITH_ARGS(ExportBuffer) \ ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer) \ ON_ALL_ACTIONS_WITH_ARGS(MultipleActions) diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 0d4a7a39d42..a055f4d150e 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -473,6 +473,13 @@ Focus pane {0} {0} will be replaced with a user-specified number + + Export text to {0} + {0} will be replaced with a user-specified file path + + + Export text + Clear buffer A command to clear the entirety of the Terminal output buffer diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index b779204db81..2c4f458ed64 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -393,6 +393,7 @@ { "command": "scrollToTop", "keys": "ctrl+shift+home" }, { "command": "scrollToBottom", "keys": "ctrl+shift+end" }, { "command": { "action": "clearBuffer", "clear": "all" } }, + { "command": "exportBuffer" }, // Visual Adjustments { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+plus" },