Skip to content

Commit 274010f

Browse files
committed
PRE-MERGE microsoft#17358 Fix two panes being closed when just one is
2 parents 4485e50 + 3606188 commit 274010f

File tree

8 files changed

+63
-66
lines changed

8 files changed

+63
-66
lines changed

src/cascadia/TerminalApp/Pane.cpp

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
2727
static const int AnimationDurationInMilliseconds = 200;
2828
static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds)));
2929

30-
Pane::Pane(const IPaneContent& content, const bool lastFocused) :
31-
_content{ content },
30+
Pane::Pane(IPaneContent content, const bool lastFocused) :
3231
_lastActive{ lastFocused }
3332
{
33+
_setPaneContent(std::move(content));
3434
_root.Children().Append(_borderFirst);
3535

3636
const auto& control{ _content.GetRoot() };
@@ -1172,17 +1172,7 @@ void Pane::_ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectab
11721172
// - <none>
11731173
void Pane::Close()
11741174
{
1175-
// Pane has two events, CloseRequested and Closed. CloseRequested is raised by the content asking to be closed,
1176-
// but also by the window who owns the tab when it's closing. The event is then caught by the TerminalTab which
1177-
// calls Close() which then raises the Closed event. Now, if this is the last pane in the window, this will result
1178-
// in the window raising CloseRequested again which leads to infinite recursion, so we need to guard against that.
1179-
// Ideally we would have just a single event in the future.
1180-
if (_closed)
1181-
{
1182-
return;
1183-
}
1184-
1185-
_closed = true;
1175+
_setPaneContent(nullptr);
11861176
// Fire our Closed event to tell our parent that we should be removed.
11871177
Closed.raise(nullptr, nullptr);
11881178
}
@@ -1194,7 +1184,7 @@ void Pane::Shutdown()
11941184
{
11951185
if (_IsLeaf())
11961186
{
1197-
_content.Close();
1187+
_setPaneContent(nullptr);
11981188
}
11991189
else
12001190
{
@@ -1598,7 +1588,7 @@ void Pane::_CloseChild(const bool closeFirst)
15981588
_borders = _GetCommonBorders();
15991589

16001590
// take the control, profile, id and isDefTermSession of the pane that _wasn't_ closed.
1601-
_content = remainingChild->_content;
1591+
_setPaneContent(remainingChild->_takePaneContent());
16021592
_id = remainingChild->Id();
16031593

16041594
// Revoke the old event handlers. Remove both the handlers for the panes
@@ -1903,6 +1893,34 @@ void Pane::_SetupChildCloseHandlers()
19031893
});
19041894
}
19051895

1896+
// With this method you take ownership of the control from this Pane.
1897+
// Assign it to another Pane with _setPaneContent() or Close() it.
1898+
IPaneContent Pane::_takePaneContent()
1899+
{
1900+
_closeRequestedRevoker.revoke();
1901+
return std::move(_content);
1902+
}
1903+
1904+
// This method safely sets the content of the Pane. It'll ensure to revoke and
1905+
// assign event handlers, and to Close() the existing content if there's any.
1906+
// The new content can be nullptr to remove any content.
1907+
void Pane::_setPaneContent(IPaneContent content)
1908+
{
1909+
// The IPaneContent::Close() implementation may be buggy and raise the CloseRequested event again.
1910+
// _takePaneContent() avoids this as it revokes the event handler.
1911+
if (const auto c = _takePaneContent())
1912+
{
1913+
c.Close();
1914+
}
1915+
1916+
_content = std::move(content);
1917+
1918+
if (_content)
1919+
{
1920+
_closeRequestedRevoker = _content.CloseRequested(winrt::auto_revoke, [this](auto&&, auto&&) { Close(); });
1921+
}
1922+
}
1923+
19061924
// Method Description:
19071925
// - Sets up row/column definitions for this pane. There are three total
19081926
// row/cols. The middle one is for the separator. The first and third are for
@@ -2470,8 +2488,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
24702488
else
24712489
{
24722490
// Move our control, guid, isDefTermSession into the first one.
2473-
_firstChild = std::make_shared<Pane>(_content);
2474-
_content = nullptr;
2491+
_firstChild = std::make_shared<Pane>(_takePaneContent());
24752492
_firstChild->_broadcastEnabled = _broadcastEnabled;
24762493
}
24772494

@@ -2669,6 +2686,11 @@ bool Pane::_HasChild(const std::shared_ptr<Pane> child)
26692686
});
26702687
}
26712688

2689+
winrt::TerminalApp::TerminalPaneContent Pane::_getTerminalContent() const
2690+
{
2691+
return _IsLeaf() ? _content.try_as<winrt::TerminalApp::TerminalPaneContent>() : nullptr;
2692+
}
2693+
26722694
// Method Description:
26732695
// - Recursive function that finds a pane with the given ID
26742696
// Arguments:

src/cascadia/TerminalApp/Pane.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ struct PaneResources
6262
class Pane : public std::enable_shared_from_this<Pane>
6363
{
6464
public:
65-
Pane(const winrt::TerminalApp::IPaneContent& content,
65+
Pane(winrt::TerminalApp::IPaneContent content,
6666
const bool lastFocused = false);
6767

6868
Pane(std::shared_ptr<Pane> first,
@@ -250,7 +250,6 @@ class Pane : public std::enable_shared_from_this<Pane>
250250

251251
std::optional<uint32_t> _id;
252252
std::weak_ptr<Pane> _parentChildPath{};
253-
bool _closed{ false };
254253
bool _lastActive{ false };
255254
winrt::event_token _firstClosedToken{ 0 };
256255
winrt::event_token _secondClosedToken{ 0 };
@@ -260,10 +259,13 @@ class Pane : public std::enable_shared_from_this<Pane>
260259

261260
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
262261
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
262+
263263
winrt::Windows::UI::Xaml::UIElement::ManipulationDelta_revoker _manipulationDeltaRevoker;
264264
winrt::Windows::UI::Xaml::UIElement::ManipulationStarted_revoker _manipulationStartedRevoker;
265265
bool _shouldManipulate{ false };
266266

267+
winrt::TerminalApp::IPaneContent::CloseRequested_revoker _closeRequestedRevoker;
268+
267269
Borders _borders{ Borders::None };
268270

269271
bool _zoomed{ false };
@@ -272,11 +274,10 @@ class Pane : public std::enable_shared_from_this<Pane>
272274
bool _IsLeaf() const noexcept;
273275
bool _HasFocusedChild() const noexcept;
274276
void _SetupChildCloseHandlers();
277+
winrt::TerminalApp::IPaneContent _takePaneContent();
278+
void _setPaneContent(winrt::TerminalApp::IPaneContent content);
275279
bool _HasChild(const std::shared_ptr<Pane> child);
276-
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const
277-
{
278-
return _IsLeaf() ? _content.try_as<winrt::TerminalApp::TerminalPaneContent>() : nullptr;
279-
}
280+
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const;
280281

281282
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
282283
const float splitSize,

src/cascadia/TerminalApp/ScratchpadContent.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ namespace winrt::TerminalApp::implementation
4545
}
4646
void ScratchpadContent::Close()
4747
{
48-
CloseRequested.raise(*this, nullptr);
4948
}
5049

5150
INewContentArgs ScratchpadContent::GetNewTerminalArgs(const BuildStartupKind /* kind */) const

src/cascadia/TerminalApp/SettingsPaneContent.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ namespace winrt::TerminalApp::implementation
4747
}
4848
void SettingsPaneContent::Close()
4949
{
50-
CloseRequested.raise(*this, nullptr);
5150
}
5251

5352
INewContentArgs SettingsPaneContent::GetNewTerminalArgs(const BuildStartupKind /*kind*/) const

src/cascadia/TerminalApp/TerminalPaneContent.cpp

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ namespace winrt::TerminalApp::implementation
7878
_bellPlayer = nullptr;
7979
_bellPlayerCreated = false;
8080
}
81-
82-
CloseRequested.raise(*this, nullptr);
8381
}
8482

8583
winrt::hstring TerminalPaneContent::Icon() const
@@ -239,19 +237,20 @@ namespace winrt::TerminalApp::implementation
239237

240238
if (_profile)
241239
{
242-
if (_isDefTermSession && _profile.CloseOnExit() == CloseOnExitMode::Automatic)
243-
{
244-
// For 'automatic', we only care about the connection state if we were launched by Terminal
245-
// Since we were launched via defterm, ignore the connection state (i.e. we treat the
246-
// close on exit mode as 'always', see GH #13325 for discussion)
247-
Close();
248-
}
249-
250240
const auto mode = _profile.CloseOnExit();
251-
if ((mode == CloseOnExitMode::Always) ||
252-
((mode == CloseOnExitMode::Graceful || mode == CloseOnExitMode::Automatic) && newConnectionState == ConnectionState::Closed))
241+
242+
if (
243+
// This one is obvious: If the user asked for "always" we do just that.
244+
(mode == CloseOnExitMode::Always) ||
245+
// Otherwise, and unless the user asked for the opposite of "always",
246+
// close the pane when the connection closed gracefully (not failed).
247+
(mode != CloseOnExitMode::Never && newConnectionState == ConnectionState::Closed) ||
248+
// However, defterm handoff can result in Windows Terminal randomly opening which may be annoying,
249+
// so by default we should at least always close the pane, even if the command failed.
250+
// See GH #13325 for discussion.
251+
(mode == CloseOnExitMode::Automatic && _isDefTermSession))
253252
{
254-
Close();
253+
CloseRequested.raise(nullptr, nullptr);
255254
}
256255
}
257256
}
@@ -331,7 +330,7 @@ namespace winrt::TerminalApp::implementation
331330
void TerminalPaneContent::_closeTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
332331
const winrt::Windows::Foundation::IInspectable& /*args*/)
333332
{
334-
Close();
333+
CloseRequested.raise(nullptr, nullptr);
335334
}
336335

337336
void TerminalPaneContent::_restartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,

src/cascadia/TerminalApp/TerminalTab.cpp

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -947,26 +947,6 @@ namespace winrt::TerminalApp::implementation
947947
auto dispatcher = TabViewItem().Dispatcher();
948948
ContentEventTokens events{};
949949

950-
events.CloseRequested = content.CloseRequested(
951-
winrt::auto_revoke,
952-
[this](auto&& sender, auto&&) {
953-
if (const auto content{ sender.try_as<TerminalApp::IPaneContent>() })
954-
{
955-
// Calling Close() while walking the tree is not safe, because Close() mutates the tree.
956-
const auto pane = _rootPane->_FindPane([&](const auto& p) -> std::shared_ptr<Pane> {
957-
if (p->GetContent() == content)
958-
{
959-
return p;
960-
}
961-
return {};
962-
});
963-
if (pane)
964-
{
965-
pane->Close();
966-
}
967-
}
968-
});
969-
970950
events.TitleChanged = content.TitleChanged(
971951
winrt::auto_revoke,
972952
[dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {

src/cascadia/TerminalApp/TerminalTab.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ namespace winrt::TerminalApp::implementation
134134
winrt::TerminalApp::IPaneContent::ConnectionStateChanged_revoker ConnectionStateChanged;
135135
winrt::TerminalApp::IPaneContent::ReadOnlyChanged_revoker ReadOnlyChanged;
136136
winrt::TerminalApp::IPaneContent::FocusRequested_revoker FocusRequested;
137-
winrt::TerminalApp::IPaneContent::CloseRequested_revoker CloseRequested;
138137

139138
// These events literally only apply if the content is a TermControl.
140139
winrt::Microsoft::Terminal::Control::TermControl::KeySent_revoker KeySent;

src/cascadia/TerminalConnection/ConptyConnection.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -446,12 +446,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
446446
try
447447
{
448448
// GH#11556 - make sure to format the error code to this string as an UNSIGNED int
449-
winrt::hstring exitText{ fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status)) };
450-
TerminalOutput.raise(L"\r\n");
451-
TerminalOutput.raise(exitText);
452-
TerminalOutput.raise(L"\r\n");
453-
TerminalOutput.raise(RS_(L"CtrlDToClose"));
454-
TerminalOutput.raise(L"\r\n");
449+
const auto msg1 = fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status));
450+
const auto msg2 = RS_(L"CtrlDToClose");
451+
const auto msg = fmt::format(FMT_COMPILE(L"\r\n{}\r\n{}\r\n"), msg1, msg2);
452+
TerminalOutput.raise(msg);
455453
}
456454
CATCH_LOG();
457455
}

0 commit comments

Comments
 (0)