diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 2ec1ac9033a..7c6e51676e9 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -306,6 +306,7 @@ "enum": [ "adjustFontSize", "clearBuffer", + "closeOtherPanes", "closeOtherTabs", "closePane", "closeTab", diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 10a7f8cfa31..b2b39a1e362 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -166,6 +166,40 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_HandleCloseOtherPanes(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (const auto terminalTab{ _GetFocusedTabImpl() }) + { + const auto activePane = terminalTab->GetActivePane(); + if (terminalTab->GetRootPane() != activePane) + { + _UnZoomIfNeeded(); + + // Accumulate list of all unfocused leaf panes, ignore read-only panes + std::vector unfocusedPaneIds; + const auto activePaneId = activePane->Id(); + terminalTab->GetRootPane()->WalkTree([&](auto&& p) { + const auto id = p->Id(); + if (id.has_value() && id != activePaneId && !p->ContainsReadOnly()) + { + unfocusedPaneIds.push_back(id.value()); + } + }); + + if (!empty(unfocusedPaneIds)) + { + // Start by removing the panes that were least recently added + sort(begin(unfocusedPaneIds), end(unfocusedPaneIds), std::less()); + _ClosePanes(terminalTab->get_weak(), std::move(unfocusedPaneIds)); + args.Handled(true); + return; + } + } + args.Handled(false); + } + } + void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/, const ActionEventArgs& args) { diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index b8c1d079ed2..7d3fab8f5ac 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -1755,6 +1755,9 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) remainingChild->_firstChild = nullptr; remainingChild->_secondChild = nullptr; } + + // Notify the discarded child that it was closed by its parent + closedChild->_ClosedByParentHandlers(); } winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst) diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 2c1fec243d6..c4824fdf9d9 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -193,6 +193,7 @@ class Pane : public std::enable_shared_from_this void CollectTaskbarStates(std::vector& states); + WINRT_CALLBACK(ClosedByParent, winrt::delegate<>); WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); using gotFocusArgs = winrt::delegate, winrt::Windows::UI::Xaml::FocusState>; diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 332b8331292..95ef15c0d47 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -760,6 +760,65 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Disables read-only mode on pane if the user wishes to close it and read-only mode is enabled. + // Arguments: + // - pane: the pane that is about to be closed. + // Return Value: + // - bool indicating whether the (read-only) pane can be closed. + winrt::Windows::Foundation::IAsyncOperation TerminalPage::_PaneConfirmCloseReadOnly(std::shared_ptr pane) + { + if (pane->ContainsReadOnly()) + { + auto warningResult = co_await _ShowCloseReadOnlyDialog(); + + // If the user didn't explicitly click on close tab - leave + if (warningResult != ContentDialogResult::Primary) + { + co_return false; + } + + // Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab + pane->WalkTree([](auto p) { + if (const auto control{ p->GetTerminalControl() }) + { + if (control.ReadOnly()) + { + control.ToggleReadOnly(); + } + } + }); + } + co_return true; + } + + // Method Description: + // - Removes the pane from the tab it belongs to. + // Arguments: + // - pane: the pane to close. + void TerminalPage::_HandleClosePaneRequested(std::shared_ptr pane) + { + // Build the list of actions to recreate the closed pane, + // BuildStartupActions returns the "first" pane and the rest of + // its actions are assuming that first pane has been created first. + // This doesn't handle refocusing anything in particular, the + // result will be that the last pane created is focused. In the + // case of a single pane that is the desired behavior anyways. + auto state = pane->BuildStartupActions(0, 1); + { + ActionAndArgs splitPaneAction{}; + splitPaneAction.Action(ShortcutAction::SplitPane); + SplitPaneArgs splitPaneArgs{ SplitDirection::Automatic, state.firstPane->GetTerminalArgsForPane() }; + splitPaneAction.Args(splitPaneArgs); + + state.args.emplace(state.args.begin(), std::move(splitPaneAction)); + } + _AddPreviouslyClosedPaneOrTab(std::move(state.args)); + + // If specified, detach before closing to directly update the pane structure + pane->Close(); + } + // Method Description: // - Close the currently focused pane. If the pane is the last pane in the // tab, the tab will also be closed. This will happen when we handle the @@ -772,46 +831,10 @@ namespace winrt::TerminalApp::implementation if (const auto pane{ terminalTab->GetActivePane() }) { - if (pane->ContainsReadOnly()) + if (co_await _PaneConfirmCloseReadOnly(pane)) { - auto warningResult = co_await _ShowCloseReadOnlyDialog(); - - // If the user didn't explicitly click on close tab - leave - if (warningResult != ContentDialogResult::Primary) - { - co_return; - } - - // Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab - pane->WalkTree([](auto p) { - if (const auto control{ p->GetTerminalControl() }) - { - if (control.ReadOnly()) - { - control.ToggleReadOnly(); - } - } - }); + _HandleClosePaneRequested(pane); } - - // Build the list of actions to recreate the closed pane, - // BuildStartupActions returns the "first" pane and the rest of - // its actions are assuming that first pane has been created first. - // This doesn't handle refocusing anything in particular, the - // result will be that the last pane created is focused. In the - // case of a single pane that is the desired behavior anyways. - auto state = pane->BuildStartupActions(0, 1); - { - ActionAndArgs splitPaneAction{}; - splitPaneAction.Action(ShortcutAction::SplitPane); - SplitPaneArgs splitPaneArgs{ SplitDirection::Automatic, state.firstPane->GetTerminalArgsForPane() }; - splitPaneAction.Args(splitPaneArgs); - - state.args.emplace(state.args.begin(), std::move(splitPaneAction)); - } - _AddPreviouslyClosedPaneOrTab(std::move(state.args)); - - pane->Close(); } } else if (auto index{ _GetFocusedTabIndex() }) @@ -824,6 +847,38 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Close all panes with the given IDs sequentially. + // Arguments: + // - weakTab: weak reference to the tab that the pane belongs to. + // - paneIds: collection of the IDs of the panes that are marked for removal. + void TerminalPage::_ClosePanes(weak_ref weakTab, std::vector paneIds) + { + if (auto strongTab{ weakTab.get() }) + { + // Close all unfocused panes one by one + while (!paneIds.empty()) + { + const auto id = paneIds.back(); + paneIds.pop_back(); + + if (const auto pane{ strongTab->GetRootPane()->FindPane(id) }) + { + pane->ClosedByParent([ids{ std::move(paneIds) }, weakThis{ get_weak() }, weakTab]() { + if (auto strongThis{ weakThis.get() }) + { + strongThis->_ClosePanes(weakTab, std::move(ids)); + } + }); + + // Close the pane which will eventually trigger the closed by parent event + _HandleClosePaneRequested(pane); + break; + } + } + } + } + // Method Description: // - Close the tab at the given index. void TerminalPage::_CloseTabAtIndex(uint32_t index) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 3d5b70efe5e..538654daef0 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -322,8 +322,11 @@ namespace winrt::TerminalApp::implementation winrt::com_ptr _GetFocusedTabImpl() const noexcept; TerminalApp::TabBase _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept; + void _HandleClosePaneRequested(std::shared_ptr pane); winrt::fire_and_forget _SetFocusedTab(const winrt::TerminalApp::TabBase tab); winrt::fire_and_forget _CloseFocusedPane(); + void _ClosePanes(weak_ref weakTab, std::vector paneIds); + winrt::Windows::Foundation::IAsyncOperation _PaneConfirmCloseReadOnly(std::shared_ptr pane); void _AddPreviouslyClosedPaneOrTab(std::vector&& args); winrt::fire_and_forget _RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr page); diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 8591457e611..b9aaaf1aae3 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -10,6 +10,7 @@ #include static constexpr std::string_view AdjustFontSizeKey{ "adjustFontSize" }; +static constexpr std::string_view CloseOtherPanesKey{ "closeOtherPanes" }; static constexpr std::string_view CloseOtherTabsKey{ "closeOtherTabs" }; static constexpr std::string_view ClosePaneKey{ "closePane" }; static constexpr std::string_view CloseTabKey{ "closeTab" }; @@ -329,6 +330,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static const auto GeneratedActionNames = []() { return std::unordered_map{ { ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") }, + { ShortcutAction::CloseOtherPanes, RS_(L"CloseOtherPanesCommandKey") }, { ShortcutAction::CloseOtherTabs, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") }, { ShortcutAction::CloseTab, L"" }, // Intentionally omitted, must be generated by GenerateName diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index 23d4cf2110c..800e4352878 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -23,80 +23,81 @@ // each action. This is _NOT_ something that should be used when any individual // case should be customized. -#define ALL_SHORTCUT_ACTIONS \ - ON_ALL_ACTIONS(CopyText) \ - ON_ALL_ACTIONS(PasteText) \ - ON_ALL_ACTIONS(OpenNewTabDropdown) \ - ON_ALL_ACTIONS(DuplicateTab) \ - ON_ALL_ACTIONS(NewTab) \ - ON_ALL_ACTIONS(CloseWindow) \ - ON_ALL_ACTIONS(CloseTab) \ - ON_ALL_ACTIONS(ClosePane) \ - ON_ALL_ACTIONS(NextTab) \ - ON_ALL_ACTIONS(PrevTab) \ - ON_ALL_ACTIONS(SendInput) \ - ON_ALL_ACTIONS(SplitPane) \ - ON_ALL_ACTIONS(ToggleSplitOrientation) \ - ON_ALL_ACTIONS(TogglePaneZoom) \ - ON_ALL_ACTIONS(SwitchToTab) \ - ON_ALL_ACTIONS(AdjustFontSize) \ - ON_ALL_ACTIONS(ResetFontSize) \ - ON_ALL_ACTIONS(ScrollUp) \ - ON_ALL_ACTIONS(ScrollDown) \ - ON_ALL_ACTIONS(ScrollUpPage) \ - ON_ALL_ACTIONS(ScrollDownPage) \ - ON_ALL_ACTIONS(ScrollToTop) \ - ON_ALL_ACTIONS(ScrollToBottom) \ - ON_ALL_ACTIONS(ScrollToMark) \ - ON_ALL_ACTIONS(AddMark) \ - ON_ALL_ACTIONS(ClearMark) \ - ON_ALL_ACTIONS(ClearAllMarks) \ - ON_ALL_ACTIONS(ResizePane) \ - ON_ALL_ACTIONS(MoveFocus) \ - ON_ALL_ACTIONS(MovePane) \ - ON_ALL_ACTIONS(SwapPane) \ - ON_ALL_ACTIONS(Find) \ - ON_ALL_ACTIONS(ToggleShaderEffects) \ - ON_ALL_ACTIONS(ToggleFocusMode) \ - ON_ALL_ACTIONS(ToggleFullscreen) \ - ON_ALL_ACTIONS(ToggleAlwaysOnTop) \ - ON_ALL_ACTIONS(OpenSettings) \ - ON_ALL_ACTIONS(SetFocusMode) \ - ON_ALL_ACTIONS(SetFullScreen) \ - ON_ALL_ACTIONS(SetMaximized) \ - ON_ALL_ACTIONS(SetColorScheme) \ - ON_ALL_ACTIONS(SetTabColor) \ - ON_ALL_ACTIONS(OpenTabColorPicker) \ - ON_ALL_ACTIONS(RenameTab) \ - ON_ALL_ACTIONS(OpenTabRenamer) \ - ON_ALL_ACTIONS(ExecuteCommandline) \ - ON_ALL_ACTIONS(ToggleCommandPalette) \ - ON_ALL_ACTIONS(CloseOtherTabs) \ - ON_ALL_ACTIONS(CloseTabsAfter) \ - ON_ALL_ACTIONS(TabSearch) \ - ON_ALL_ACTIONS(MoveTab) \ - ON_ALL_ACTIONS(BreakIntoDebugger) \ - ON_ALL_ACTIONS(TogglePaneReadOnly) \ - ON_ALL_ACTIONS(FindMatch) \ - ON_ALL_ACTIONS(NewWindow) \ - ON_ALL_ACTIONS(IdentifyWindow) \ - ON_ALL_ACTIONS(IdentifyWindows) \ - ON_ALL_ACTIONS(RenameWindow) \ - ON_ALL_ACTIONS(OpenWindowRenamer) \ - ON_ALL_ACTIONS(GlobalSummon) \ - 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) \ - ON_ALL_ACTIONS(AdjustOpacity) \ - ON_ALL_ACTIONS(RestoreLastClosed) \ - ON_ALL_ACTIONS(SelectAll) \ - ON_ALL_ACTIONS(MarkMode) \ - ON_ALL_ACTIONS(ToggleBlockSelection) \ - ON_ALL_ACTIONS(SwitchSelectionEndpoint) +#define ALL_SHORTCUT_ACTIONS \ + ON_ALL_ACTIONS(CopyText) \ + ON_ALL_ACTIONS(PasteText) \ + ON_ALL_ACTIONS(OpenNewTabDropdown) \ + ON_ALL_ACTIONS(DuplicateTab) \ + ON_ALL_ACTIONS(NewTab) \ + ON_ALL_ACTIONS(CloseWindow) \ + ON_ALL_ACTIONS(CloseTab) \ + ON_ALL_ACTIONS(ClosePane) \ + ON_ALL_ACTIONS(NextTab) \ + ON_ALL_ACTIONS(PrevTab) \ + ON_ALL_ACTIONS(SendInput) \ + ON_ALL_ACTIONS(SplitPane) \ + ON_ALL_ACTIONS(ToggleSplitOrientation) \ + ON_ALL_ACTIONS(TogglePaneZoom) \ + ON_ALL_ACTIONS(SwitchToTab) \ + ON_ALL_ACTIONS(AdjustFontSize) \ + ON_ALL_ACTIONS(ResetFontSize) \ + ON_ALL_ACTIONS(ScrollUp) \ + ON_ALL_ACTIONS(ScrollDown) \ + ON_ALL_ACTIONS(ScrollUpPage) \ + ON_ALL_ACTIONS(ScrollDownPage) \ + ON_ALL_ACTIONS(ScrollToTop) \ + ON_ALL_ACTIONS(ScrollToBottom) \ + ON_ALL_ACTIONS(ScrollToMark) \ + ON_ALL_ACTIONS(AddMark) \ + ON_ALL_ACTIONS(ClearMark) \ + ON_ALL_ACTIONS(ClearAllMarks) \ + ON_ALL_ACTIONS(ResizePane) \ + ON_ALL_ACTIONS(MoveFocus) \ + ON_ALL_ACTIONS(MovePane) \ + ON_ALL_ACTIONS(SwapPane) \ + ON_ALL_ACTIONS(Find) \ + ON_ALL_ACTIONS(ToggleShaderEffects) \ + ON_ALL_ACTIONS(ToggleFocusMode) \ + ON_ALL_ACTIONS(ToggleFullscreen) \ + ON_ALL_ACTIONS(ToggleAlwaysOnTop) \ + ON_ALL_ACTIONS(OpenSettings) \ + ON_ALL_ACTIONS(SetFocusMode) \ + ON_ALL_ACTIONS(SetFullScreen) \ + ON_ALL_ACTIONS(SetMaximized) \ + ON_ALL_ACTIONS(SetColorScheme) \ + ON_ALL_ACTIONS(SetTabColor) \ + ON_ALL_ACTIONS(OpenTabColorPicker) \ + ON_ALL_ACTIONS(RenameTab) \ + ON_ALL_ACTIONS(OpenTabRenamer) \ + ON_ALL_ACTIONS(ExecuteCommandline) \ + ON_ALL_ACTIONS(ToggleCommandPalette) \ + ON_ALL_ACTIONS(CloseOtherTabs) \ + ON_ALL_ACTIONS(CloseTabsAfter) \ + ON_ALL_ACTIONS(TabSearch) \ + ON_ALL_ACTIONS(MoveTab) \ + ON_ALL_ACTIONS(BreakIntoDebugger) \ + ON_ALL_ACTIONS(TogglePaneReadOnly) \ + ON_ALL_ACTIONS(FindMatch) \ + ON_ALL_ACTIONS(NewWindow) \ + ON_ALL_ACTIONS(IdentifyWindow) \ + ON_ALL_ACTIONS(IdentifyWindows) \ + ON_ALL_ACTIONS(RenameWindow) \ + ON_ALL_ACTIONS(OpenWindowRenamer) \ + ON_ALL_ACTIONS(GlobalSummon) \ + 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) \ + ON_ALL_ACTIONS(AdjustOpacity) \ + ON_ALL_ACTIONS(RestoreLastClosed) \ + ON_ALL_ACTIONS(SelectAll) \ + ON_ALL_ACTIONS(MarkMode) \ + ON_ALL_ACTIONS(ToggleBlockSelection) \ + ON_ALL_ACTIONS(SwitchSelectionEndpoint) \ + ON_ALL_ACTIONS(CloseOtherPanes) #define ALL_SHORTCUT_ACTIONS_WITH_ARGS \ ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \ diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 49135e63364..4f1514af722 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -567,4 +567,7 @@ Switch selection endpoint + + Close all other panes + diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 7a672723c33..96ac06fe9e4 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -359,6 +359,7 @@ { "command": { "action": "switchToTab", "index": 4294967295 }, "keys": "ctrl+alt+9" }, // Pane Management + { "command": "closeOtherPanes" }, { "command": "closePane", "keys": "ctrl+shift+w" }, { "command": { "action": "splitPane", "split": "up" } }, { "command": { "action": "splitPane", "split": "down" }, "keys": "alt+shift+-" },