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

Add support for tab tearoff and tab merge #1256

Closed
Tracked by #13392 ...
DHowett-MSFT opened this issue Jun 14, 2019 · 49 comments · Fixed by #14901
Closed
Tracked by #13392 ...

Add support for tab tearoff and tab merge #1256

DHowett-MSFT opened this issue Jun 14, 2019 · 49 comments · Fixed by #14901
Assignees
Labels
Area-User Interface Issues pertaining to the user interface of the Console or Terminal In-PR This issue has a related PR Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Needs-Tag-Fix Doesn't match tag requirements Product-Terminal The new Windows Terminal.

Comments

@DHowett-MSFT
Copy link
Contributor

I'm repurposing this for tab reordering and demoting it to a Task. We'll track the (much larger) workitem to do tab tearoff as a deliverable for vBacklog.
Notes/reasoning:

  • Tab reordering is easy.
  • Tab tearoff requires us to answer the following questions:
    • WT.exe is a multi-instance app. How do we drag tabs between different processes?
    • We still need to design "new window" for tearoff-without-merge because we need a drop target for the old tab.

Originally posted by @DHowett-MSFT in #443 (comment)

@ghost ghost added Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting Needs-Tag-Fix Doesn't match tag requirements labels Jun 14, 2019
@DHowett-MSFT DHowett-MSFT added Area-User Interface Issues pertaining to the user interface of the Console or Terminal Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal. and removed Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting labels Jun 14, 2019
@ghost ghost removed the Needs-Tag-Fix Doesn't match tag requirements label Jun 14, 2019
@DHowett-MSFT DHowett-MSFT added this to the Terminal Backlog milestone Jun 14, 2019
This was referenced Jun 23, 2019
@paradonym

This comment was marked as off-topic.

@cdmihai
Copy link

cdmihai commented Sep 25, 2019

This would be so useful. I constantly open new tabs on some focused work, start splitting panes within the tab, and then at some point decide to move the whole tab and panes (right now I just recreate the panes and their directories from scratch) to a new terminal window in a new virtual desktop.

@adgustaf
Copy link

Hi,

Any progress on this? Just wanted to say I find this to be very useful.

Thanks for all your hard work on this product!

@DHowett-MSFT
Copy link
Contributor Author

Thanks for asking! Unfortunately, there hasn’t been any progress. When there is, this issue will be the first to be updated about it.

@adgustaf
Copy link

Thanks for the info! Okay I'll be following this issue :)

DHowett pushed a commit that referenced this issue Mar 31, 2023
_This is the last one 🎉_

## Summary

_In the final chapter of our tale, we present a PR of great
significance. It grants the power to tear tabs from their windows and
create a new window where they may be dropped, one not necessarily of
the Terminal sort. The dimensions of the original window are transferred
to this new abode, and its placement on the screen is determined by the
user's placement of the tab._
_This is the last main chapter of the tear-out saga, and the dawning of
the new age._

Closes #5000
Related to #1256

## Detailed description

We're really leaning on the existing `RequestNewWindow` event that the
monarch already had - honestly, most of that was so simple that it could
have just been in the parent PRs. We just need to add new support for
passing in a content blob of json, and making sure the Terminal always
uses that over commandline args. Easy enough.

There's a bit of wackiness here in adjusting the positioning just right
so that the new window appears in the right place, but it feels...
pretty good all things considered.
ianjoneill pushed a commit to ianjoneill/terminal that referenced this issue Apr 1, 2023


_And so begins the first chapter in the epic tale of the Terminal's tab
tear-out. This commit, though humble in its nature, shall mark the
beginning of a grand journey._
_This initial offering, though small in its scope, doth serve to divide
the code that currently resides within TerminalPage and AppLogic, moving
it unto a new entity known as TerminalWindow. In the ages to come, these
classes shall take on separate responsibilities, each with their own
purpose._
_The AppLogic shall hold sway over the entire process, shared among all
Terminal windows, while the AppHost, TerminalWindow, and TerminalPage
shall rule over each individual window._
_This pull request prepares the way for the future, moving state that
pertains to the individual windows into the TerminalWindow. This is a
task of great labor, for it requires moving much code, but the end
result shall bring greater organization to the codebase._
_And so the stage is set, for in the next pull request, the Process
Model v3 shall be revealed, unifying all Terminal windows into a single
process, a grand accomplishment indeed._

_courtesy of G.P.T. Tolkien_

<details>
<summary>Or, as I wrote it originally. </summary>

This is the first of the commits in the long saga which will culminate
in tab tear-out for the Terminal.

This the most functionally trivial of the PRs. It mostly just splits up
code that's currently in TerminalPage & AppLogic, and moves it into a
new class `TerminalWindow`. In the future, these classes will separate
responsibility as such:
* There will be one `AppLogic` per process, shared across all Terminal
windows.
* There will be one `AppHost`, `TerminalWindow`, and `TerminalPage` for
each individual window in the process.

This PR prepares for that by moving some state that's applicable to
_individual windows_ into `TerminalWindow`. This is almost exclusively a
code moving PR. There should be minimal functional changes.

</details>


In the next PR, we'll introduce the actual "Process Model v3", merging
all Terminal windows into a single terminal process.

Related to microsoft#5000. See
microsoft#5000 (comment)
for my current todo list.
Related to microsoft#1256. 

These commits are all artificially broken down pieces. Honestly, I don't
want to really merge them till they're all ready, so we know that the
work e2e. This my feigned attempt to break it into digestable PRs.

Lightly manually tested, things seem to still all work? Most of this
code was actually written in deeper branches, it was only today I
realized it all needed to come back to this branch.

* [x] The window persistence fishy-ness of the subsequent PR isn't
present here. So that's something.
* [x] Localtests still pass

### Detailed description

> Q: Does `AppLogic` not keep track of its windows?

Sure doesn't! I didn't think that was something it needed to know.

>Q: Why does `TerminalWindow` (per-window) have access to the
commandline args (per-process)

It's because it's _not_ per process. Commandline args _are_ per-window.
Consider - you launch the Terminal, then run a `wt -w -1 -- foo`. That
makes its own window. In this process, yes. But that new window has its
own commandline args, separate from the ones that started the original
process.
ianjoneill pushed a commit to ianjoneill/terminal that referenced this issue Apr 1, 2023
## Summary

_In the days of old, the windows were sundered, each with its own
process, like the scattered stars in the sky. But a new age hath dawned,
for all windows now reside within a single process, like the bright gems
of a single crown._
_And lo, there came the `WindowEmperor`, a new lord to rule over the
global state, wielding the power of hotkeys and the beacon of the
notification icon. The `WindowManager` was cast aside, no longer needed
to seek out other processes or determine the `Monarch`._
_Should the `WindowEmperor` determine that a new window shall be raised,
it shall set forth a new thread, born from the ether, to govern this new
realm. On the main thread shall reside a message loop, charged with the
weighty task of preserving the global state, guarded by hotkeys and the
beacon of the notification icon._
_Each window doth live on its own thread, each guarded by the new
`WindowThread`, a knightly champion to hold the `TerminalWindow`,
`AppHost`, and `IslandWindow` in its grasp. And so the windows shall run
free, no longer burdened by their former ways._


All windows are now in a single process, rather than in one process per
window. We'll add a new `WindowEmperor` class to manage global state
such as hotkeys and the notification icon. The `WindowManager` has been
streamlined and no longer needs to connect to other processes or
determine a new `Monarch`. Each window will run on its own thread, using
the new `WindowThread` class to encapsulate the thread and manage the
`TerminalWindow`, `AppHost`, and `IslandWindow`.


* Related to microsoft#5000
* Related to microsoft#1256

## Windows Terminal Process Model 3.0

Everything is now one process. All the windows for a single Terminal
instance live in a singular Terminal process. When a new terminal
process launches, it will still attempt to communicate with an existing
one. If it finds one, it'll pass the commandline to that process and
exit. Otherwise, it'll become the "monarch" and create a new window.

We'll introduce a new abstraction here, called the `WindowEmperor`.
`Monarch` & `Peasant` will still remain, for facilitating cross-window
communication. The Emperor will allow us to have a single dedicated
class for all global state, and will now always represent the "monarch"
(rather than our previously established non-deterministic monarchy to
elevate a random peasant to the role of monarch). We still need to do a
very minimal amount of x-proc calls. Namely, one right on startup, to
see if another `Terminal.exe` was already running. If we find one, then
we toss our commandline at it and bail. If we don't, then we need to
`CoRegister` the Monarch still, to prepare for subsequent launches to
send commands to us.

`WindowManager` takes the most changes here. It had a ton of logic to
redundantly attempt to connect to other monarchs of other processes, or
elect a new one. It doesn't need to do any of that anymore, which is a
pretty dramatic change to that class.

This creates the opportunity to move some lifetime management around.
We've played silly games in the past trying to have individual windows
determine if they're the singular monarch for global state.
`IslandWindow`s no longer need to track things like global hotkeys or
the notification icon. The emperor can do that - there's only ever one
emperor. It can also own a singular copy of the settings model, and hand
out references to each other thread.

Each window lives on separate threads. We'll need to separately
initialize XAML islands for each thread. This is totally fine, and
actually supported these days. We'll use a new class called
`WindowThread` to encapsulate one of these threads. It'll be responsible
for owning the `TerminalWindow`, `AppHost` and `IslandWindow` for a
single thread.

This introduces new classes of bugs we'll need to worry about. It's now
easier than ever to have "wrong thread" bugs when interacting with any
XAML object from another thread. A good case in point - we used to stash
a `static` `Brush` in `Pane`, for the color of the borders. We can't do
that anymore! The first window will end up stashing a brush from its
thread. So now when a second window starts, the app explodes, because
the panes of that window try to draw their borders using a brush from
the wrong thread.

_Another fun change_: The keybinding labels of the command palette.
`TerminalPage` is the thing that ends up expanding iterable `Command`s.
It does this largely with copies - it makes a new `map`, a new `vector`,
copies the `Command`s over, and does the work there before setting up
the cmdpal.
Except, it's not making a copy of the `Command`s, it's making a copy of
the `vector`, with winrt objects all pointing at the `Command` objects
that are ultimately owned by `CascadiaSettings`.
This doesn't matter if there's only one `TerminalPage` - we'll only ever
do that once. However, now there are many Pages, on different threads.
That causes one `TerminalPage` to end up expanding the subcommands of a
`Command` while another `TerminalPage` is ALSO iterating on those
subcommands.

_Emperor message window_: The Emperor will have its own HWND, that's
entirely unrelated to any terminal window. This window is a
`HWND_MESSAGE` window, which specifically cannot be visible, but is
useful for getting messages. We'll use that to handle the notification
icon and global hotkeys. This alleviates the need for the IslandWindow
to raise events for the tray icon up to the AppHost to handle them. Less
plumbing=more good.

### Class ownership diagram

_pretend that I know UML for a second_:

```mermaid
classDiagram
    direction LR
    class Monarch
    class Peasant
    class Emperor
    class WindowThread
    class AppHost

    Monarch "1" --o "*" Peasant: Tracks
    Emperor --* "1" AppLogic: 
    Monarch <..> "1" Emperor
    Peasant "1" .. "1" WindowThread
    Emperor "1" --o "*" WindowThread: Tracks
    WindowThread --* AppHost
    AppHost --* IslandWindow
    AppHost --* TerminalWindow
    TerminalWindow --* TerminalPage
```

* There's still only one `Monarch`. One for the Terminal process.
* There's still many `Peasant`s, one per window.
* The `Monarch` is no longer associated with a window. It's associated
with the `Emperor`, who maintains _all_ the Terminal windows (but is not
associated with any particular window)
* It may be relevant to note: As far as the `Remoting` dll is concerned,
it doesn't care if monarchs and peasants are associated with windows or
not. Prior to this PR, _yes_, the Monarch was in fact associated with a
specific window (which was also associated with a window). Now, the
monarch is associated with the Emperor, who isn't technically any of the
windows.
* The `Emperor` owns the `App` (and by extension, the single `AppLogic`
instance).
* Each Terminal window lives on its own thread, owed by a `WindowThread`
object.
* There's still one `AppHost`, one `IslandWindow`, one `TerminalWindow`
& `TerminalPage` per window.
* `AppLogic` hands out references to its settings to each
`TerminalWindow` as they're created.

### Isolated Mode

This was a bit of a tiny brainstorm Dustin and I discussed. This is a
new setting introduced as an escape watch from the "one process to rule
them all" model. Technically, the Terminal already did something like
this if it couldn't find a `Monarch`, though, we were never really sure
if that hit. This just adds a setting to manually enable this mode.

In isolated mode, we always instantiate a Monarch instance locally,
without attempting to use the `CoRegister`-ed one, and we _never_
register one. This prevents the Terminal from talking with other
windows.
* Global hotkeys won't work right
* Trying to run commandlines in other windows (`wt -w foo`) won't work
* Every window will be its own process again
* Tray icon behavior is left undefined for now.
* Tab tearout straight-up won't work.

### A diagram about settings

This helps explain how settings changes get propagated

```mermaid
sequenceDiagram
    participant Emperor
    participant AppLogic
    
    participant AppHost
    participant TerminalWindow
    participant TerminalPage

    Note Right of AppLogic: AL::ReloadSettings
    AppLogic ->> Emperor: raise SettingsChanged
    Note left of Emperor: E::...GlobalHotkeys
    Note left of Emperor: E::...NotificationIcon
    AppLogic ->> TerminalWindow: raise SettingsChanged<br>(to each window)
    AppLogic ->> TerminalWindow: 
    AppLogic ->> TerminalWindow: 
    Note right of TerminalWindow: TW::UpdateSettingsHandler
    Note right of TerminalWindow: TW::UpdateSettings
    TerminalWindow ->> TerminalPage: SetSettings
    TerminalWindow ->> AppHost: raise SettingsChanged
    Note right of AppHost: AH::_HandleSettingsChanged
```
ianjoneill pushed a commit to ianjoneill/terminal that referenced this issue Apr 1, 2023
## Summary

_Thus we come to the introduction of a new servant, the
`ContentManager`, a singular entity that serves at the behest of the
`emperor`. It is its charge to keep track of all `TermControl` instances
created by the windows, for each window must seek its blessing before
calling forth such an instance._
_With the aid of the `ContentManager`, the `TermControl` shall now be
traced by the hand of fate through the use of unique identifying marks,
known as `GUID`s. Yet, its purpose remains yet unknown, for it is merely
a waypoint upon the journey yet to come._
_This act of bridging also brings a change to the handling of events
within the `TermControl`. This change shall see the addition of a
revoker, similar to the manner in which the `AppHost` hath employed it,
to the `TermControl`. Additionally, there is a new layer of indirection
between the `ControlCore` and the `App` layer, making ready for the day
when the `TermControl` may be repositioned and re-parented with ease._
_Consider this but a trivial act, a mere shadow of things yet to come,
for its impact shall be felt but briefly, like the passing of a gentle
breeze._

Related to microsoft#5000
Related to microsoft#1256

# Detailed description

This PR is another small bridge PR between the big work in microsoft#14843, and
the PR that will enable panes to move between windows.

This introduces a new class, called `ContentManager`. This is a global
singleton object, owned by the emperor. Whenever a window wants to
instantiate a new `TermControl`, it must ask the ContentManager to give
it one. This allows the ContentManager to track each "content" by GUID.
That's it. We don't do anything with them in this PR by itself, we just
track them.

This also includes a small change to the way TermControl events are
handled. It adds an `AppHost`-like revoker struct, and weak_ref's all
the handlers. We also add a layer of indirection between the
ControlCore's raising of events and the App layer's handling. This will
make reparenting content easier in the future.

This is a pretty trivial change which shouldn't have any major side
effects. Consider it exposition of the things to come. It's
intentionally small to try and keep the reviews more managable.
@Korb
Copy link

Korb commented Apr 6, 2023

Is it supposed to add the "Move Tab to New Window" item to the context menu of the tabs
Screenshot_2023-04-06_08-54-54
like Mozilla Firefox
Screenshot_2023-04-06_08-55-44
in the context of this task?

Screenshots of Windows Terminal Preview 1.17.1023 and Mozilla Firefox 111.0.1 (64-bit), Microsoft Windows 10.0.19045.2788.

@zadjii-msft
Copy link
Member

@Korb I haven't added that yet but I'll track that in #15127

@zadjii-msft

This comment was marked as off-topic.

@WeihanLi
Copy link

Is there a release plan for this

@zadjii-msft
Copy link
Member

Is there a release plan for this

We'll release it when it is ready 😜. I'm tracking the release blockers for tear-out specifically over in #14957. There's assorted other release blockers for 1.18 around the repo as well. There have been quite a few ENORMOUS changes to the codebase in 1.18, so bear with us while we make sure that it's in a stable place for release ☺️

@malaclypse
Copy link

Any plans to release this soon?

@zadjii-msft
Copy link
Member

This was released 4 months ago: https://github.com/microsoft/terminal/releases/tag/v1.18.1421.0

Though, unclear why the bot didn't make it's rounds to post in this thread...

<bot hat>

🎉This issue was addressed in #14901, which has now been successfully released as Windows Terminal Preview v1.18.1421.0.:tada:

Handy links:

</🤖>

@kczx3
Copy link

kczx3 commented Sep 20, 2023

"released" is a stretch I'd say since 1.18 has been in pre-release since May 23rd... Is there something specific holding 1.18 back and causing you to miss your quarterly releases?

@zadjii-msft
Copy link
Member

Preview releases are still solid releases. Honestly, that's what I consider the main Terminal releases. 1.18 is held back from Stable for namely #15496, but also to line up 1.19 with some other internal work/announcements/etc.

Also, we're on a roughly quarterly schedule. "Roughly" doing a lot of work here. I'd much rather land a solid release after a 1mo delay, than cut early for the sake of releasing every 3mos.

@kczx3
Copy link

kczx3 commented Sep 20, 2023

No one is trying to downplay the amount of work you're doing on this. We all agree this is a hefty project.

I think we could probably argue back and forth all day about calling preview releases the main release. I'm comfortable sitting tight until it reaches general availability and out of a preview or dev release.

@randomascii
Copy link

I don't think we're pushing you to release faster (I'm not, anyway). But I think that it would be less confusing if you didn't say "this was released 4 months ago" when it was only released to the preview channel.

I am probably not alone in not using the preview channel. I'm sure it's fairly stable, but I've got enough variables in my life so I am using the release channel, so for my purposes it is not yet released.

In other words, it would have been clearer to me if you had said "This was released to the preview channel 4 months ago..." and perhaps an estimate of when it would be released to the, well, release channel.

@kczx3
Copy link

kczx3 commented Sep 21, 2023

@randomascii
Thank you for saying what I was trying to in a much better way.

@EduardDurech
Copy link

Sorry if this has already been noted, there are many hidden comments and did not see it already mentioned

While tab tear-off works in non-elevated Terminal, it doesn't seem to work with elevated Administrative privileges. I'm assuming this would require a new User Account Control prompt

V 1.18.2822.0

@zadjii-msft
Copy link
Member

@EduardDurech There's a lot more details about the admin drag/drop problem over in #6661.

@patricknelson
Copy link

Seeing this in stable and it's awesome, thanks @zadjii-msft! It's great to see a conclusion to "the penultimate chapter in the saga of tear-out!" 😄 With all the unnecessary anger and frustration I've seen you guys get, I figured a bit of appreciation is in order. Thanks for getting this across the finish line! 🎉

@biggestsonicfan
Copy link

Took quite a long time to figure out I had to drag a tab in-between two other tabs to get it to move to a different window and not at the end of other tabs.

@Gravifer
Copy link

seems like a mishandled corner case? @zadjii-msft

Took quite a long time to figure out I had to drag a tab in-between two other tabs to get it to move to a different window and not at the end of other tabs.

@agyild
Copy link

agyild commented Aug 22, 2024

seems like a mishandled corner case? @zadjii-msft

Took quite a long time to figure out I had to drag a tab in-between two other tabs to get it to move to a different window and not at the end of other tabs.

I only found out about this after a Google search which is non-intuitive UX design. Ideally, moving tabs between windows should replicate the behavior in web browsers. @zadjii-msft

@fireattack
Copy link

fireattack commented Aug 22, 2024

Also when you are tearing a tab, the cursor icon is always "🚫" which seems to imply it's an invalid operation, but in reality, you can tear it off.

@zadjii-msft
Copy link
Member

Yep, sure. Those are both known. There are additional notes in:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-User Interface Issues pertaining to the user interface of the Console or Terminal In-PR This issue has a related PR Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Needs-Tag-Fix Doesn't match tag requirements Product-Terminal The new Windows Terminal.
Projects
None yet
Development

Successfully merging a pull request may close this issue.