From 871b8de74f416e83bbf6ea512e649415b1d86434 Mon Sep 17 00:00:00 2001 From: Don-Vito Date: Mon, 30 Aug 2021 21:35:43 +0300 Subject: [PATCH 1/5] Teach command palette to fill in selected commandline upon right arrow (#11069) Closes #11049 --- src/cascadia/TerminalApp/CommandPalette.cpp | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 31ec712b9..03f69625d 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -244,6 +244,17 @@ namespace winrt::TerminalApp::implementation _PreviewActionHandlers(*this, actionPaletteItem.Command()); } } + else if (_currentMode == CommandPaletteMode::CommandlineMode) + { + if (filteredCommand) + { + SearchBoxPlaceholderText(filteredCommand.Item().Name()); + } + else + { + SearchBoxPlaceholderText(RS_(L"CmdPalCommandlinePrompt")); + } + } } void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/, @@ -364,6 +375,17 @@ namespace winrt::TerminalApp::implementation _searchBox().PasteFromClipboard(); e.Handled(true); } + else if (key == VirtualKey::Right && _currentMode == CommandPaletteMode::CommandlineMode) + { + if (const auto command{ _filteredActionsView().SelectedItem().try_as() }) + { + _searchBox().Text(command.Item().Name()); + _searchBox().Select(_searchBox().Text().size(), 0); + _searchBox().Focus(FocusState::Programmatic); + _filteredActionsView().SelectedIndex(-1); + e.Handled(true); + } + } } // Method Description: From efea1e5bad0f32f5457b3d49346001164e45e889 Mon Sep 17 00:00:00 2001 From: Leon Liang Date: Mon, 30 Aug 2021 18:39:03 -0700 Subject: [PATCH 2/5] Add Tray Icon settings to the SettingsUI (#11070) Adds toggle buttons to the settings UI for `minimizeToTray` and `alwaysShowTrayIcon` that I mistakenly left out. --- .../TerminalSettingsEditor/GlobalAppearance.cpp | 5 +++++ .../TerminalSettingsEditor/GlobalAppearance.h | 2 ++ .../TerminalSettingsEditor/GlobalAppearance.idl | 2 ++ .../TerminalSettingsEditor/GlobalAppearance.xaml | 12 ++++++++++++ .../Resources/en-US/Resources.resw | 8 ++++++++ 5 files changed, 29 insertions(+) diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp index d45c34e13..b97aea5ec 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp @@ -198,4 +198,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation globals.Language(currentLanguage); } } + + bool GlobalAppearance::FeatureTrayIconEnabled() const noexcept + { + return Feature_TrayIcon::IsEnabled(); + } } diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h index c566b953a..b8112d41a 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h @@ -25,6 +25,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e); + bool FeatureTrayIconEnabled() const noexcept; + WINRT_PROPERTY(Editor::GlobalAppearancePageNavigationState, State, nullptr); GETSET_BINDABLE_ENUM_SETTING(Theme, winrt::Windows::UI::Xaml::ElementTheme, State().Globals, Theme); GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, State().Globals, TabWidthMode); diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl index 7e3483140..8218d3cad 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl @@ -25,5 +25,7 @@ namespace Microsoft.Terminal.Settings.Editor IInspectable CurrentTabWidthMode; Windows.Foundation.Collections.IObservableVector TabWidthModeList { get; }; + + Boolean FeatureTrayIconEnabled { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml index 5ecb1169f..68800bfb1 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml @@ -84,6 +84,18 @@ + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 7b328dc4d..5edf5c577 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -1090,6 +1090,14 @@ Pane animations Header for a control to toggle animations on panes. "Enabled" value enables the animations. + + Always display an icon in the notification area + Header for a control to toggle whether the tray icon should always be shown. + + + Hide Terminal in the notification area when it is minimized + Header for a control to toggle whether the terminal should hide itself in the tray instead of the taskbar when minimized. + Reset to inherited value. This button will remove a user's customization from a given setting, restoring it to the value that the profile inherited. This is a text label on a button. From 717ea85c9f6de8721a50c6783a02c68e9c4d8085 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 31 Aug 2021 06:07:30 -0500 Subject: [PATCH 3/5] Fix a crash when there aren't any `recentCommands` yet (#11082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first time you open commandline mode, `recentCommands` doesn't exist yet. However, we immediately try to read the `Size()` in a couple places. This'll A/V and we'll crash 😨 The fix is easy - don't try and read the size of the non-existent `recentCommands` Found this while playing with #11069 Regressed in #11030 Didn't bother filing an issue for it when I have the fix in hand --- src/cascadia/TerminalApp/CommandPalette.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 03f69625d..be16f2f1b 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -1241,6 +1241,13 @@ namespace winrt::TerminalApp::implementation IVector CommandPalette::_loadRecentCommands() { const auto recentCommands = ApplicationState::SharedInstance().RecentCommands(); + // If this is the first time we've opened the commandline mode and + // there aren't any recent commands, then just return an empty vector. + if (!recentCommands) + { + return single_threaded_vector(); + } + std::vector parsedCommands; parsedCommands.reserve(std::min(recentCommands.Size(), CommandLineHistoryLength)); @@ -1268,7 +1275,9 @@ namespace winrt::TerminalApp::implementation void CommandPalette::_updateRecentCommands(const hstring& command) { const auto recentCommands = ApplicationState::SharedInstance().RecentCommands(); - const auto countToCopy = std::min(recentCommands.Size(), CommandLineHistoryLength - 1); + // If there aren't and recent commands already in the state, then we + // don't need to copy any. + const auto countToCopy = std::min(recentCommands ? recentCommands.Size() : 0, CommandLineHistoryLength - 1); std::vector newRecentCommands{ countToCopy + 1 }; til::at(newRecentCommands, 0) = command; if (countToCopy) From 8d81497eb7b4a632e28ce2388701ea7ccc4ef12d Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Tue, 31 Aug 2021 15:35:51 -0400 Subject: [PATCH 4/5] Add action to run multiple actions. (#11045) ## Summary of the Pull Request Add a new action that can contain multiple other actions. ## References ## PR Checklist * [x] Closes #3992 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [x] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx ## Detailed Description of the Pull Request / Additional comments Creates a shortcut action that allows a list of actions to be specified as arguments. Steals a bunch of the serialization code from my other pr. Overall, because I had the serialization code written already, this was remarkably easy. I can't think of any combined action to be added to the defaults, so I think this is just a thing for the documentation unless someone else has a good example. I know there are lot of times when the recommended workaround is "make an action with commandline wt.exe ..." and this could be a good replacement for that, but that is all personalized. I didn't add this to the command line parsing, since the command line is already a way to run multiple actions. ## Validation Steps Performed Created a new command, confirmed that "Move right->down" showed up in the command palette, and that running it did the correct behavior (moving right one pane, then down one pane). ``` { "command": { "action": "multipleActions", "name": "Move right->down", "actions": [ {"action": "moveFocus", "direction": "right" }, {"action": "moveFocus", "direction": "down" }, ] } } ``` --- doc/cascadia/profiles.schema.json | 19 +++++ .../TerminalApp/AppActionHandlers.cpp | 17 ++++ .../TerminalSettingsModel/ActionAndArgs.cpp | 2 + .../TerminalSettingsModel/ActionAndArgs.h | 31 ++++++++ .../TerminalSettingsModel/ActionArgs.cpp | 6 ++ .../TerminalSettingsModel/ActionArgs.h | 49 ++++++++++++ .../TerminalSettingsModel/ActionArgs.idl | 8 ++ .../AllShortcutActions.h | 6 +- .../ApplicationState.cpp | 42 ---------- .../TerminalSettingsModel/JsonUtils.h | 77 +++++++++++++++++++ 10 files changed, 213 insertions(+), 44 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index b2e293c76..30a803393 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -268,6 +268,7 @@ "movePane", "swapPane", "moveTab", + "multipleActions", "newTab", "newWindow", "nextTab", @@ -825,6 +826,24 @@ ], "required": [ "direction" ] }, + "MultipleActionsAction": { + "description": "Arguments for the multiple actions command", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "multipleActions" }, + "actions" : { + "$ref": "#/definitions/ShortcutAction", + "type": "array", + "minItems": 1, + "description": "A list of other actions." + } + } + } + ], + "required": [ "actions" ] + }, "CommandPaletteAction": { "description": "Arguments for a commandPalette action", "allOf": [ diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index ee62b60c8..e7f1cfb95 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -874,4 +874,21 @@ namespace winrt::TerminalApp::implementation } } } + + void TerminalPage::_HandleMultipleActions(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + for (const auto& action : realArgs.Actions()) + { + _actionDispatch->DoAction(action); + } + + args.Handled(true); + } + } + } } diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index acef483be..02bb1d486 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -65,6 +65,7 @@ static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" }; 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 MultipleActionsKey{ "multipleActions" }; static constexpr std::string_view ActionKey{ "action" }; @@ -366,6 +367,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::GlobalSummon, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") }, { ShortcutAction::FocusPane, L"" }, // Intentionally omitted, must be generated by GenerateName + { ShortcutAction::MultipleActions, L"" }, // Intentionally omitted, must be generated by GenerateName }; }(); diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.h b/src/cascadia/TerminalSettingsModel/ActionAndArgs.h index 892b54050..abf9a4823 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.h @@ -35,3 +35,34 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation { BASIC_FACTORY(ActionAndArgs); } + +namespace Microsoft::Terminal::Settings::Model::JsonUtils +{ + using namespace winrt::Microsoft::Terminal::Settings::Model; + + template<> + struct ConversionTrait + { + ActionAndArgs FromJson(const Json::Value& json) + { + std::vector v; + return *implementation::ActionAndArgs::FromJson(json, v); + } + + bool CanConvert(const Json::Value& json) const + { + // commands without args might just be a string + return json.isString() || json.isObject(); + } + + Json::Value ToJson(const ActionAndArgs& val) + { + return implementation::ActionAndArgs::ToJson(val); + } + + std::string TypeDescription() const + { + return "ActionAndArgs"; + } + }; +} diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index fab78a2f1..99d55d38e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -34,6 +34,7 @@ #include "RenameWindowArgs.g.cpp" #include "GlobalSummonArgs.g.cpp" #include "FocusPaneArgs.g.cpp" +#include "MultipleActionsArgs.g.cpp" #include @@ -687,4 +688,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Id()) }; } + + winrt::hstring MultipleActionsArgs::GenerateName() const + { + return L""; + } } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index ebf35a1aa..96dbe07ce 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -36,6 +36,7 @@ #include "RenameWindowArgs.g.h" #include "GlobalSummonArgs.g.h" #include "FocusPaneArgs.g.h" +#include "MultipleActionsArgs.g.h" #include "../../cascadia/inc/cppwinrt_utils.h" #include "JsonUtils.h" @@ -1754,6 +1755,53 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } }; + struct MultipleActionsArgs : public MultipleActionsArgsT + { + MultipleActionsArgs() = default; + WINRT_PROPERTY(Windows::Foundation::Collections::IVector, Actions); + static constexpr std::string_view ActionsKey{ "actions" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + auto otherAsUs = other.try_as(); + if (otherAsUs) + { + return otherAsUs->_Actions == _Actions; + } + 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, ActionsKey, args->_Actions); + return { *args, {} }; + } + static Json::Value ToJson(const IActionArgs& val) + { + if (!val) + { + return {}; + } + Json::Value json{ Json::ValueType::objectValue }; + const auto args{ get_self(val) }; + JsonUtils::SetValueForKey(json, ActionsKey, args->_Actions); + return json; + } + IActionArgs Copy() const + { + auto copy{ winrt::make_self() }; + copy->_Actions = _Actions; + return *copy; + } + size_t Hash() const + { + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Actions); + } + }; } namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation @@ -1778,4 +1826,5 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation BASIC_FACTORY(FocusPaneArgs); BASIC_FACTORY(PrevTabArgs); BASIC_FACTORY(NextTabArgs); + BASIC_FACTORY(MultipleActionsArgs); } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index 202262b19..3d89466e9 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "Command.idl"; + namespace Microsoft.Terminal.Settings.Model { interface IActionArgs @@ -308,4 +310,10 @@ namespace Microsoft.Terminal.Settings.Model FocusPaneArgs(UInt32 Id); UInt32 Id { get; }; }; + + [default_interface] runtimeclass MultipleActionsArgs : IActionArgs + { + MultipleActionsArgs(); + Windows.Foundation.Collections.IVector Actions; + } } diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index 7d0d0a728..e2f5488e4 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -78,7 +78,8 @@ ON_ALL_ACTIONS(OpenWindowRenamer) \ ON_ALL_ACTIONS(GlobalSummon) \ ON_ALL_ACTIONS(QuakeMode) \ - ON_ALL_ACTIONS(FocusPane) + ON_ALL_ACTIONS(FocusPane) \ + ON_ALL_ACTIONS(MultipleActions) #define ALL_SHORTCUT_ACTIONS_WITH_ARGS \ ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \ @@ -109,4 +110,5 @@ ON_ALL_ACTIONS_WITH_ARGS(SplitPane) \ ON_ALL_ACTIONS_WITH_ARGS(SwitchToTab) \ ON_ALL_ACTIONS_WITH_ARGS(ToggleCommandPalette) \ - ON_ALL_ACTIONS_WITH_ARGS(FocusPane) + ON_ALL_ACTIONS_WITH_ARGS(FocusPane) \ + ON_ALL_ACTIONS_WITH_ARGS(MultipleActions) diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index f0f536ecf..0984a5330 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -55,48 +55,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); } }; - - template - struct ConversionTrait> - { - winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) const - { - ConversionTrait trait; - std::vector val; - val.reserve(json.size()); - - for (const auto& element : json) - { - val.push_back(trait.FromJson(element)); - } - - return winrt::single_threaded_vector(move(val)); - } - - bool CanConvert(const Json::Value& json) const - { - ConversionTrait trait; - return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); - } - - Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) - { - ConversionTrait trait; - Json::Value json{ Json::arrayValue }; - - for (const auto& key : val) - { - json.append(trait.ToJson(key)); - } - - return json; - } - - std::string TypeDescription() const - { - return fmt::format("vector ({})", ConversionTrait{}.TypeDescription()); - } - }; } using namespace ::Microsoft::Terminal::Settings::Model; diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index 1bab27c36..c5370cdbf 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -177,6 +177,47 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils } }; + template + struct ConversionTrait> + { + std::vector FromJson(const Json::Value& json) + { + std::vector val; + val.reserve(json.size()); + + ConversionTrait trait; + for (const auto& element : json) + { + val.push_back(trait.FromJson(element)); + } + + return val; + } + + bool CanConvert(const Json::Value& json) const + { + ConversionTrait trait; + return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) mutable -> bool { return trait.CanConvert(json); }); + } + + Json::Value ToJson(const std::vector& val) + { + Json::Value json{ Json::arrayValue }; + + ConversionTrait trait; + for (const auto& v : val) + { + json.append(trait.ToJson(v)); + } + + return json; + } + std::string TypeDescription() const + { + return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); + } + }; + template struct ConversionTrait> { @@ -259,6 +300,42 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils } }; + template + struct ConversionTrait> + { + winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) + { + ConversionTrait> trait; + return winrt::single_threaded_vector(std::move(trait.FromJson(json))); + } + + bool CanConvert(const Json::Value& json) const + { + ConversionTrait> trait; + return trait.CanConvert(json); + } + + Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) + { + Json::Value json{ Json::arrayValue }; + + if (val) + { + ConversionTrait trait; + for (const auto& v : val) + { + json.append(trait.ToJson(v)); + } + } + + return json; + } + std::string TypeDescription() const + { + return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); + } + }; + template struct ConversionTrait> { From c089ae0c57d9ecc6d6e0f99eeb6cbcb655bd720e Mon Sep 17 00:00:00 2001 From: Don-Vito Date: Tue, 31 Aug 2021 22:36:43 +0300 Subject: [PATCH 5/5] Allow exporting terminal buffer into file via tab context menu (#11062) ## Summary of the Pull Request **Naive implementation** of exporting the text buffer of the current pane into a text file triggered from the tab context menu. **Disclaimer: this is not an export of the command history,** but rather just a text buffer dumped into a file when asked explicitly. ## References Should provide partial solution for #642. ## Detailed Description of the Pull Request / Additional comments The logic is following: * Open a file save picker * The location is Downloads folder (should be always accessible) * The suggest name of the file equals to the pane's title * The allowed file formats list contains .txt only * If no file selected stop * Lock terminal * Read all lines till the cursor * Format each line by removing trailing white-spaces and adding CRLF if not wrapped * Asynchronously write to selected file * Show confirmation As the action is relatively fast didn't add a progress bar or any other UX. As the buffer is relatively small, holding it entirely in the memory rather than writing line by line to disk. --- .../Resources/en-US/Resources.resw | 12 +++++ src/cascadia/TerminalApp/TabManagement.cpp | 52 +++++++++++++++++++ src/cascadia/TerminalApp/TerminalPage.h | 1 + src/cascadia/TerminalApp/TerminalTab.cpp | 19 +++++++ src/cascadia/TerminalApp/TerminalTab.h | 1 + src/cascadia/TerminalApp/pch.h | 3 ++ src/cascadia/TerminalControl/ControlCore.cpp | 28 ++++++++++ src/cascadia/TerminalControl/ControlCore.h | 2 + src/cascadia/TerminalControl/ControlCore.idl | 2 + src/cascadia/TerminalControl/TermControl.cpp | 5 ++ src/cascadia/TerminalControl/TermControl.h | 2 + src/cascadia/TerminalControl/TermControl.idl | 2 + 12 files changed, 129 insertions(+) diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index f9e8cbf12..9f50d6718 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -685,4 +685,16 @@ Split the window and start in given directory + + Export Text + + + Failed to export terminal content + + + Successfully exported terminal content + + + Plain Text + diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 849bfcc69..0a1f91e46 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -28,6 +28,9 @@ using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::System; using namespace winrt::Windows::ApplicationModel::DataTransfer; using namespace winrt::Windows::UI::Text; +using namespace winrt::Windows::Storage; +using namespace winrt::Windows::Storage::Pickers; +using namespace winrt::Windows::Storage::Provider; using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal::Control; using namespace winrt::Microsoft::Terminal::TerminalConnection; @@ -171,6 +174,16 @@ namespace winrt::TerminalApp::implementation } }); + newTabImpl->ExportTabRequested([weakTab, weakThis{ get_weak() }]() { + auto page{ weakThis.get() }; + auto tab{ weakTab.get() }; + + if (page && tab) + { + page->_ExportTab(*tab); + } + }); + auto tabViewItem = newTabImpl->TabViewItem(); _tabView.TabItems().Append(tabViewItem); @@ -391,6 +404,45 @@ namespace winrt::TerminalApp::implementation CATCH_LOG(); } + // Method Description: + // - Exports the content of the Terminal Buffer inside the tab + // Arguments: + // - tab: tab to export + winrt::fire_and_forget TerminalPage::_ExportTab(const TerminalTab& tab) + { + try + { + if (const auto control{ tab.GetActiveTerminalControl() }) + { + const FileSavePicker savePicker; + savePicker.as()->Initialize(*_hostingHwnd); + savePicker.SuggestedStartLocation(PickerLocationId::Downloads); + const auto fileChoices = single_threaded_vector({ L".txt" }); + savePicker.FileTypeChoices().Insert(RS_(L"PlainText"), fileChoices); + savePicker.SuggestedFileName(control.Title()); + + const StorageFile file = co_await savePicker.PickSaveFileAsync(); + if (file != nullptr) + { + const auto buffer = control.ReadEntireBuffer(); + CachedFileManager::DeferUpdates(file); + co_await FileIO::WriteTextAsync(file, buffer); + const auto status = co_await CachedFileManager::CompleteUpdatesAsync(file); + switch (status) + { + case FileUpdateStatus::Complete: + case FileUpdateStatus::CompleteAndRenamed: + _ShowControlNoticeDialog(RS_(L"NoticeInfo"), RS_(L"ExportSuccess")); + break; + default: + _ShowControlNoticeDialog(RS_(L"NoticeError"), RS_(L"ExportFailure")); + } + } + } + } + CATCH_LOG(); + } + // Method Description: // - Removes the tab (both TerminalControl and XAML) after prompting for approval // Arguments: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 0bdcf4aae..d598d0955 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -220,6 +220,7 @@ namespace winrt::TerminalApp::implementation void _DuplicateTab(const TerminalTab& tab); void _SplitTab(TerminalTab& tab); + winrt::fire_and_forget _ExportTab(const TerminalTab& tab); winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab); void _CloseTabAtIndex(uint32_t index); diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 6f415754c..a3a2a1651 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -1211,6 +1211,23 @@ namespace winrt::TerminalApp::implementation splitTabMenuItem.Icon(splitTabSymbol); } + Controls::MenuFlyoutItem exportTabMenuItem; + { + // "Split Tab" + Controls::FontIcon exportTabSymbol; + exportTabSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" }); + exportTabSymbol.Glyph(L"\xE74E"); // Save + + exportTabMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->_ExportTabRequestedHandlers(); + } + }); + exportTabMenuItem.Text(RS_(L"ExportTabText")); + exportTabMenuItem.Icon(exportTabSymbol); + } + // Build the menu Controls::MenuFlyout contextMenuFlyout; Controls::MenuFlyoutSeparator menuSeparator; @@ -1218,6 +1235,7 @@ namespace winrt::TerminalApp::implementation contextMenuFlyout.Items().Append(renameTabMenuItem); contextMenuFlyout.Items().Append(duplicateTabMenuItem); contextMenuFlyout.Items().Append(splitTabMenuItem); + contextMenuFlyout.Items().Append(exportTabMenuItem); contextMenuFlyout.Items().Append(menuSeparator); // GH#5750 - When the context menu is dismissed with ESC, toss the focus @@ -1592,4 +1610,5 @@ namespace winrt::TerminalApp::implementation DEFINE_EVENT(TerminalTab, TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>); DEFINE_EVENT(TerminalTab, DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>); DEFINE_EVENT(TerminalTab, SplitTabRequested, _SplitTabRequestedHandlers, winrt::delegate<>); + DEFINE_EVENT(TerminalTab, ExportTabRequested, _ExportTabRequestedHandlers, winrt::delegate<>); } diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index 4952fad45..b1824d98e 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -103,6 +103,7 @@ namespace winrt::TerminalApp::implementation DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>); DECLARE_EVENT(DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>); DECLARE_EVENT(SplitTabRequested, _SplitTabRequestedHandlers, winrt::delegate<>); + DECLARE_EVENT(ExportTabRequested, _ExportTabRequestedHandlers, winrt::delegate<>); TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable); private: diff --git a/src/cascadia/TerminalApp/pch.h b/src/cascadia/TerminalApp/pch.h index 1f84f821e..66026a78d 100644 --- a/src/cascadia/TerminalApp/pch.h +++ b/src/cascadia/TerminalApp/pch.h @@ -56,6 +56,9 @@ #include #include #include +#include +#include +#include #include diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 3dcf284a7..c5e0cfac5 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1499,4 +1499,32 @@ namespace winrt::Microsoft::Terminal::Control::implementation _updatePatternLocations->Run(); } + hstring ControlCore::ReadEntireBuffer() const + { + auto terminalLock = _terminal->LockForWriting(); + + const auto& textBuffer = _terminal->GetTextBuffer(); + + std::wstringstream ss; + const auto lastRow = textBuffer.GetLastNonSpaceCharacter().Y; + for (auto rowIndex = 0; rowIndex <= lastRow; rowIndex++) + { + const auto& row = textBuffer.GetRowByOffset(rowIndex); + auto rowText = row.GetText(); + const auto strEnd = rowText.find_last_not_of(UNICODE_SPACE); + if (strEnd != std::string::npos) + { + rowText.erase(strEnd + 1); + ss << rowText; + } + + if (!row.WasWrapForced()) + { + ss << UNICODE_CARRIAGERETURN << UNICODE_LINEFEED; + } + } + + return hstring(ss.str()); + } + } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 3bb321f0b..57052cdd0 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -144,6 +144,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool IsInReadOnlyMode() const; void ToggleReadOnlyMode(); + hstring ReadEntireBuffer() const; + // -------------------------------- WinRT Events --------------------------------- // clang-format off WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs); diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index fa4746ca4..154fa869b 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -81,6 +81,8 @@ namespace Microsoft.Terminal.Control Boolean CursorOn; void EnablePainting(); + String ReadEntireBuffer(); + event FontSizeChangedEventArgs FontSizeChanged; event Windows.Foundation.TypedEventHandler CopyToClipboard; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 3e3d7414e..e1a2afb37 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -2586,4 +2586,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _playWarningBell->Run(); } + + hstring TermControl::ReadEntireBuffer() const + { + return _core.ReadEntireBuffer(); + } } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index e2ee89b82..521c897fd 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -107,6 +107,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation static unsigned int GetPointerUpdateKind(const winrt::Windows::UI::Input::PointerPoint point); static Windows::UI::Xaml::Thickness ParseThicknessFromPadding(const hstring padding); + hstring ReadEntireBuffer() const; + // -------------------------------- WinRT Events --------------------------------- // clang-format off WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 1a043e95b..325d813e2 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -67,5 +67,7 @@ namespace Microsoft.Terminal.Control Boolean ReadOnly { get; }; void ToggleReadOnly(); + + String ReadEntireBuffer(); } }