Repository: microsoft/terminal (language: cpp)

Issue:
Display connection state in tab & add tab context menu entry for restarting connection
<!-- 
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨

I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement.
2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement.
3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number).
4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement.
5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement.

All good? Then proceed!
-->
# Environment
CPU	        AMD Ryzen 7 3700X 8-Core Processor                3.60 GHz
Memory	32.0 GB
OS           64-bit

# Description of the new feature/enhancement
1、Add a status indicator to the tab indicates connection status
2、Add connect and disconnect buttons to the tab's Pop-up list(or somewhere)

<!-- 
A clear and concise description of what the problem is that the new feature would solve.
Describe why and how a user would use this new functionality (if applicable).
-->
# Describe
**The two functions above are closely linked and I have written them together**
1、I want to disconnect the terminal from the background program without closing the terminal page (I need to see the contents,)，and reconnect if necessary，more often than not, I prefer to reopen the current terminal page rather than closing it and creating another
![Snipaste_2023-02-25_21-58-25](https://user-images.githubusercontent.com/33871402/221361825-74a4f80e-92c0-48e0-b50a-ec6c51d4b555.png)
Add connect/disconnect buttons to above list(or somewhere)

2、Let's use an elegant dynamic icon to indicate the current connection status. Every time the connection is accidentally disconnected, the error report on the first line is of little use to me except for the last line which tells me to enter to reconnect (it's a strange feeling that the connection is now disconnected and the content is printed in the terminal...”)
like this:
![Snipaste_2023-02-25_21-56-36](https://user-images.githubusercontent.com/33871402/221361863-756c960e-2720-4dce-ba0f-9314676ee872.png)


# Proposed technical implementation details (optional)
It would be great to have both functions(connect/disconnect button) in one button
<!-- 
A clear and concise description of what you want to happen.
-->

<hr>

_maintainer edit space:_

```[tasklist]
### Tasks
* [ ] When a connection is `Closed`, set an indicator in the tab itself. This could be like the zoom one we have currently. 
* [ ] When the active pane in a tab's connection state is `Closed`, we could add a "Restart connection" action to the tab context menu (ala #14549)
```




Discussion / hints:
Hi @Window-YANG. We're having a bit of trouble understanding how detach works and how it impacts the tab hosting that application. Could you please elaborate on that a bit?

As for the "restarting" aspect of this request, does #14060 sound like what you're looking for?
> Hi @Window-YANG. We're having a bit of trouble understanding how detach works and how it impacts the tab hosting that application. Could you please elaborate on that a bit?
> 
> As for the "restarting" aspect of this request, does #14060 sound like what you're looking for?
Problem 1: restart。Refer to secureCRT. 
![reconnect1](https://user-images.githubusercontent.com/33871402/224592376-b853a78b-e22f-4bdb-9ea3-0a10b742a47a.png)

Restart is 1409, but there are some shortcomings, 1409 confuses me: I can't find any place to actively disconnect (unless I close the terminal or enter a command in the terminal to exit the background program), so it's better to design a button ，no matter what the background program is, I can easily disconnect and reconnect with one click in the terminal

Proble 2: status
As for technical details like how to disconnect, I can't give much advice (one possible way: close the background program and recreate a new one?)

Okay, I think I can see the cross-tabs here. There's a couple things we can do here:

* When a connection is `Closed`, set an indicator in the tab itself. This could be like the zoom one we have currently. 
* When the active pane in a tab's connection state is `Closed`, we could add a "Restart connection" action to the tab context menu ~(ala #14549)~ I MEAN, #15241

I'll xlink with #6700 which has some similar thoughts in it. 

Thanks for the suggestion!

Fix patch:
diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp
index 32cf032606f..8cf6567f37a 100644
--- a/src/cascadia/TerminalApp/Pane.cpp
+++ b/src/cascadia/TerminalApp/Pane.cpp
@@ -1384,6 +1384,18 @@ Profile Pane::GetFocusedProfile()
     return lastFocused ? lastFocused->_profile : nullptr;
 }
 
+// Method Description:
+// - Returns true if the connection state of this pane is closed. If this Pane is not a leaf this will
+//   return false.
+// Arguments:
+// - <none>
+// Return Value:
+// - true if the connection state of this Pane is closed.
+bool Pane::IsConnectionClosed() const
+{
+    return _control && _control.ConnectionState() >= ConnectionState::Closed;
+}
+
 // Method Description:
 // - Returns true if this pane was the last pane to be focused in a tree of panes.
 // Arguments:
diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h
index 6920a90b341..7c04bcabc09 100644
--- a/src/cascadia/TerminalApp/Pane.h
+++ b/src/cascadia/TerminalApp/Pane.h
@@ -75,6 +75,7 @@ class Pane : public std::enable_shared_from_this<Pane>
     winrt::Microsoft::Terminal::Control::TermControl GetLastFocusedTerminalControl();
     winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl();
     winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile();
+    bool IsConnectionClosed() const;
 
     // Method Description:
     // - If this is a leaf pane, return its profile.
diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index df28d046daf..71b0692708d 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -891,4 +891,10 @@
   <data name="CmdAppendCommandLineDesc" xml:space="preserve">
     <value>If set, the command will be appended to the profile's default command instead of replacing it.</value>
   </data>
+  <data name="RestartConnectionText" xml:space="preserve">
+    <value>Restart Connection</value>
+  </data>
+  <data name="RestartConnectionToolTip" xml:space="preserve">
+    <value>Restart the active pane connection</value>
+  </data>
 </root>
diff --git a/src/cascadia/TerminalApp/TabHeaderControl.xaml b/src/cascadia/TerminalApp/TabHeaderControl.xaml
index 5fde709ecbf..3c5c7f7f053 100644
--- a/src/cascadia/TerminalApp/TabHeaderControl.xaml
+++ b/src/cascadia/TerminalApp/TabHeaderControl.xaml
@@ -43,6 +43,12 @@
                   FontSize="12"
                   Glyph="&#xE72E;"
                   Visibility="{x:Bind TabStatus.IsReadOnlyActive, Mode=OneWay}" />
+        <FontIcon x:Name="HeaderClosedIcon"
+                  Margin="0,0,8,0"
+                  FontFamily="{ThemeResource SymbolThemeFontFamily}"
+                  FontSize="12"
+                  Glyph="&#xEA39;"
+                  Visibility="{x:Bind TabStatus.IsConnectionClosed, Mode=OneWay}" />
         <FontIcon x:Name="HeaderBroadcastIcon"
                   Margin="0,0,8,0"
                   FontFamily="{ThemeResource SymbolThemeFontFamily}"
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 024ae18c10a..f7edf639758 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -4816,8 +4816,7 @@ namespace winrt::TerminalApp::implementation
                                             const bool withSelection)
     {
         // withSelection can be used to add actions that only appear if there's
-        // selected text, like "search the web". In this initial draft, it's not
-        // actually augmented by the TerminalPage, so it's left commented out.
+        // selected text, like "search the web"
 
         const auto& menu{ sender.try_as<MUX::Controls::CommandBarFlyout>() };
         if (!menu)
@@ -4870,6 +4869,14 @@ namespace winrt::TerminalApp::implementation
             makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr });
         }
 
+        if (const auto pane{ _GetFocusedTabImpl()->GetActivePane() })
+        {
+            if (pane->IsConnectionClosed())
+            {
+                makeItem(RS_(L"RestartConnectionText"), L"\xE72C", ActionAndArgs{ ShortcutAction::RestartConnection, nullptr });
+            }
+        }
+
         if (withSelection)
         {
             makeItem(RS_(L"SearchWebText"), L"\xF6FA", ActionAndArgs{ ShortcutAction::SearchForText, nullptr });
diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp
index 1b881029b56..4286db604e1 100644
--- a/src/cascadia/TerminalApp/TerminalTab.cpp
+++ b/src/cascadia/TerminalApp/TerminalTab.cpp
@@ -15,6 +15,7 @@ using namespace winrt;
 using namespace winrt::Windows::UI::Xaml;
 using namespace winrt::Windows::UI::Core;
 using namespace winrt::Microsoft::Terminal::Control;
+using namespace winrt::Microsoft::Terminal::TerminalConnection;
 using namespace winrt::Microsoft::Terminal::Settings::Model;
 using namespace winrt::Microsoft::UI::Xaml::Controls;
 using namespace winrt::Windows::System;
@@ -35,6 +36,7 @@ namespace winrt::TerminalApp::implementation
         _activePane = nullptr;
 
         _closePaneMenuItem.Visibility(WUX::Visibility::Collapsed);
+        _restartConnectionMenuItem.Visibility(WUX::Visibility::Collapsed);
 
         auto firstId = _nextPaneId;
 
@@ -890,6 +892,7 @@ namespace winrt::TerminalApp::implementation
             control.TitleChanged(events.titleToken);
             control.TabColorChanged(events.colorToken);
             control.SetTaskbarProgress(events.taskbarToken);
+            control.ConnectionStateChanged(events.stateToken);
             control.ReadOnlyChanged(events.readOnlyToken);
             control.FocusFollowMouseRequested(events.focusToken);
 
@@ -949,6 +952,14 @@ namespace winrt::TerminalApp::implementation
             }
         });
 
+        events.stateToken = control.ConnectionStateChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
+            co_await wil::resume_foreground(dispatcher);
+            if (auto tab{ weakThis.get() })
+            {
+                tab->_UpdateConnectionClosedState();
+            }
+        });
+
         events.readOnlyToken = control.ReadOnlyChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
             co_await wil::resume_foreground(dispatcher);
             if (auto tab{ weakThis.get() })
@@ -1054,6 +1065,40 @@ namespace winrt::TerminalApp::implementation
         _TaskbarProgressChangedHandlers(nullptr, nullptr);
     }
 
+    // Method Description:
+    // - Set an indicator on the tab if any pane is in a closed connection state.
+    // - Show/hide the Restart Connection context menu entry depending on active pane's state.
+    // Arguments:
+    // - <none>
+    // Return Value:
+    // - <none>
+    void TerminalTab::_UpdateConnectionClosedState()
+    {
+        ASSERT_UI_THREAD();
+
+        if (_rootPane)
+        {
+            const bool isClosed = _rootPane->WalkTree([&](const auto& p) {
+                return p->IsConnectionClosed();
+            });
+
+            _tabStatus.IsConnectionClosed(isClosed);
+        }
+
+        if (_activePane)
+        {
+            _restartConnectionMenuItem.Visibility(_activePane->IsConnectionClosed() ?
+                                                      WUX::Visibility::Visible :
+                                                      WUX::Visibility::Collapsed);
+        }
+    }
+
+    void TerminalTab::_RestartActivePaneConnection()
+    {
+        ActionAndArgs restartConnection{ ShortcutAction::RestartConnection, nullptr };
+        _dispatch.DoAction(*this, restartConnection);
+    }
+
     // Method Description:
     // - Mark the given pane as the active pane in this tab. All other panes
     //   will be marked as inactive. We'll also update our own UI state to
@@ -1072,6 +1117,7 @@ namespace winrt::TerminalApp::implementation
         // Update our own title text to match the newly-active pane.
         UpdateTitle();
         _UpdateProgressState();
+        _UpdateConnectionClosedState();
 
         // We need to move the pane to the top of our mru list
         // If its already somewhere in the list, remove it first
@@ -1393,6 +1439,28 @@ namespace winrt::TerminalApp::implementation
             Automation::AutomationProperties::SetHelpText(findMenuItem, findToolTip);
         }
 
+        Controls::MenuFlyoutItem restartConnectionMenuItem = _restartConnectionMenuItem;
+        {
+            // "Restart Connection"
+            Controls::FontIcon restartConnectionSymbol;
+            restartConnectionSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
+            restartConnectionSymbol.Glyph(L"\xE72C");
+
+            restartConnectionMenuItem.Click([weakThis](auto&&, auto&&) {
+                if (auto tab{ weakThis.get() })
+                {
+                    tab->_RestartActivePaneConnection();
+                }
+            });
+            restartConnectionMenuItem.Text(RS_(L"RestartConnectionText"));
+            restartConnectionMenuItem.Icon(restartConnectionSymbol);
+
+            const auto restartConnectionToolTip = RS_(L"RestartConnectionToolTip");
+
+            WUX::Controls::ToolTipService::SetToolTip(restartConnectionMenuItem, box_value(restartConnectionToolTip));
+            Automation::AutomationProperties::SetHelpText(restartConnectionMenuItem, restartConnectionToolTip);
+        }
+
         // Build the menu
         Controls::MenuFlyout contextMenuFlyout;
         Controls::MenuFlyoutSeparator menuSeparator;
@@ -1403,6 +1471,7 @@ namespace winrt::TerminalApp::implementation
         contextMenuFlyout.Items().Append(moveTabToNewWindowMenuItem);
         contextMenuFlyout.Items().Append(exportTabMenuItem);
         contextMenuFlyout.Items().Append(findMenuItem);
+        contextMenuFlyout.Items().Append(restartConnectionMenuItem);
         contextMenuFlyout.Items().Append(menuSeparator);
 
         // GH#5750 - When the context menu is dismissed with ESC, toss the focus
diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h
index 72ec8d8bac2..12c4f79a643 100644
--- a/src/cascadia/TerminalApp/TerminalTab.h
+++ b/src/cascadia/TerminalApp/TerminalTab.h
@@ -109,6 +109,7 @@ namespace winrt::TerminalApp::implementation
         std::shared_ptr<Pane> _zoomedPane{ nullptr };
 
         Windows::UI::Xaml::Controls::MenuFlyoutItem _closePaneMenuItem;
+        Windows::UI::Xaml::Controls::MenuFlyoutItem _restartConnectionMenuItem;
 
         winrt::hstring _lastIconPath{};
         std::optional<winrt::Windows::UI::Color> _runtimeTabColor{};
@@ -125,6 +126,7 @@ namespace winrt::TerminalApp::implementation
             winrt::event_token titleToken;
             winrt::event_token colorToken;
             winrt::event_token taskbarToken;
+            winrt::event_token stateToken;
             winrt::event_token readOnlyToken;
             winrt::event_token focusToken;
 
@@ -171,6 +173,9 @@ namespace winrt::TerminalApp::implementation
 
         void _UpdateProgressState();
 
+        void _UpdateConnectionClosedState();
+        void _RestartActivePaneConnection();
+
         void _DuplicateTab();
 
         virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() override;
diff --git a/src/cascadia/TerminalApp/TerminalTabStatus.h b/src/cascadia/TerminalApp/TerminalTabStatus.h
index 95080935a38..838a909df22 100644
--- a/src/cascadia/TerminalApp/TerminalTabStatus.h
+++ b/src/cascadia/TerminalApp/TerminalTabStatus.h
@@ -12,6 +12,7 @@ namespace winrt::TerminalApp::implementation
         TerminalTabStatus() = default;
 
         WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
+        WINRT_OBSERVABLE_PROPERTY(bool, IsConnectionClosed, _PropertyChangedHandlers);
         WINRT_OBSERVABLE_PROPERTY(bool, IsPaneZoomed, _PropertyChangedHandlers);
         WINRT_OBSERVABLE_PROPERTY(bool, IsProgressRingActive, _PropertyChangedHandlers);
         WINRT_OBSERVABLE_PROPERTY(bool, IsProgressRingIndeterminate, _PropertyChangedHandlers);
diff --git a/src/cascadia/TerminalApp/TerminalTabStatus.idl b/src/cascadia/TerminalApp/TerminalTabStatus.idl
index 57f4e05fa1a..5dc69ffe59b 100644
--- a/src/cascadia/TerminalApp/TerminalTabStatus.idl
+++ b/src/cascadia/TerminalApp/TerminalTabStatus.idl
@@ -7,6 +7,7 @@ namespace TerminalApp
     {
         TerminalTabStatus();
 
+        Boolean IsConnectionClosed { get; set; };
         Boolean IsPaneZoomed { get; set; };
         Boolean IsProgressRingActive { get; set; };
         Boolean IsProgressRingIndeterminate { get; set; };


Test patch: