Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix panes being dropped when tearing off tabs #18627

Merged
merged 2 commits into from
Feb 26, 2025

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented Feb 25, 2025

I don't actually know why this is happening, because it doesn't
happen with startup actions specified in the settings file.
In any case, it's fixed with more delays.

Closes #18572

Validation Steps Performed

  • Create a tab with 2 panes
  • Tear it off into a new window
  • New window has 1 tab with 2 panes ✅

@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Bug It either shouldn't be doing this or needs an investigation. Area-Windowing Window frame, quake mode, tearout Priority-1 A description (P1) Product-Terminal The new Windows Terminal. labels Feb 25, 2025
{
actionArgs.Handled(true);
ProcessStartupActions(actions, false);
ProcessStartupActions(std::move(actions), false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use IVector, when everything from start to finish in this stack talks in std::vector already?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you ugh

@@ -375,7 +374,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::HandoffToElevated(const CascadiaSettings& settings)
{
if (!_startupActions)
if (_startupActions.empty())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this was a bug. Handing off to elevated with 0 tabs doesn't seem useful to me.


// Handle it on a subsequent pass of the UI thread.
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
const auto strong = get_strong();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified lifetime management.

{
// We didn't actually need to change the virtual CWD, so we don't
// need to restore it
restoreCwd.release();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subjectively speaking, I found this highly confusing. Rewrote it to be a literal translation of the comment above.
(= "If the caller provided a CWD, [...] switch back once we're done" = if (!cwd.empty()))

// Curiously, this does not seem to be required when using startupActions, but only when
// tearing out a tab (this currently creates a new window with injected startup actions).
// This indicates that this is really more of an architectural issue and not a fundamental one.
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Low);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug fix. If the rest is too much, I'll extract this.

@@ -1200,7 +1196,7 @@ namespace winrt::TerminalApp::implementation
{
try
{
const auto& args = ActionAndArgs::Deserialize(content);
const auto args = ActionAndArgs::Deserialize(content);
Copy link
Member Author

@lhecker lhecker Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C++ is a whack language so be careful when binding return values to references. Doing so incorrectly breaks return-value-optimization (as it did here)!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @carlos-zamora this is an excellent example of where const auto& on a local goes wrong!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Double-checking) So what's the lesson here? Don't store returned IVectors (or any winrt collection) to a const auto&? Does it still work correctly, but it just gets rid of the optimization?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you have a function that returns something by-value and you bind it to a reference here's what actually happens:

const auto __hidden_variable__ = ActionAndArgs::Deserialize(content);
const auto& args = __hidden_variable__;

// ...

return args;

As you can see in the example, you return a reference at the end, not the original object. This forces C++ to make a copy of the object. If you don't bind it to a reference, then you'll also return the original object at the end and "return value optimization" will kick in.

The problem is when you have a function that returns a reference, but you bind it to a value. Because then you'll also make a copy:

const auto args = return_a_reference_to_arguments();
//     ^        ^
//     |   Because `args` is a value type, but the function returns a reference,
//     |   this invokes the `vector(const vector& other)` copy constructor.
//     |
// "auto" always infers a value type, never refences, etc.
// So, in this case it may be `std::vector`, not `std::vector&`.

C++ has a fix for this con job of supposed type inference and it's this:

decltype(auto) args = return_a_reference_to_arguments();
//   ^
// This always infers the exact type, including references, constness, pointers, etc.

It couldn't have been just let or var or something. 😐
In any case, if you use Resharper (currently free to use via the so-called EAP), you get little inlay hints that tell you when an invisible copy constructor is called. This allows me to quickly spot these issues.

@lhecker lhecker force-pushed the dev/lhecker/18572-tab-tearoff branch from 176beba to 3ee9efd Compare February 25, 2025 15:49
@lhecker lhecker force-pushed the dev/lhecker/18572-tab-tearoff branch from 3ee9efd to 472e608 Compare February 25, 2025 15:49
Copy link
Member

@carlos-zamora carlos-zamora left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! :)

Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ever since you told me to use std::vector, it's been great btw. Love to actually be able to see what's inside a vector. Big fan of this change!

@@ -1200,7 +1196,7 @@ namespace winrt::TerminalApp::implementation
{
try
{
const auto& args = ActionAndArgs::Deserialize(content);
const auto args = ActionAndArgs::Deserialize(content);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Double-checking) So what's the lesson here? Don't store returned IVectors (or any winrt collection) to a const auto&? Does it still work correctly, but it just gets rid of the optimization?

Copy link
Member

@carlos-zamora carlos-zamora left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! :)

@lhecker lhecker enabled auto-merge (squash) February 26, 2025 18:57
@lhecker lhecker merged commit e1b28e7 into main Feb 26, 2025
19 checks passed
@lhecker lhecker deleted the dev/lhecker/18572-tab-tearoff branch February 26, 2025 18:58
DHowett pushed a commit that referenced this pull request Feb 26, 2025
I don't actually know why this is happening, because it doesn't
happen with startup actions specified in the settings file.
In any case, it's fixed with more delays.

Closes #18572

## Validation Steps Performed
* Create a tab with 2 panes
* Tear it off into a new window
* New window has 1 tab with 2 panes ✅

(cherry picked from commit e1b28e7)
Service-Card-Id: PVTI_lADOAF3p4s4AxadtzgXTsJY PVTI_lADOAF3p4s4AxadtzgXsQ6w
Service-Version: 1.23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Windowing Window frame, quake mode, tearout Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-1 A description (P1) Product-Terminal The new Windows Terminal.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Drag tab into new window lost all panels besides the first
3 participants