-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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
Comments
This comment was marked as off-topic.
This comment was marked as off-topic.
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. |
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! |
Thanks for asking! Unfortunately, there hasn’t been any progress. When there is, this issue will be the first to be updated about it. |
Thanks for the info! Okay I'll be following this issue :) |
_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.
_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.
## 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 ```
## 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.
This comment was marked as off-topic.
This comment was marked as off-topic.
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 |
Any plans to release this soon? |
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 Handy links: </🤖> |
"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? |
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. |
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. |
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. |
@randomascii |
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 |
@EduardDurech There's a lot more details about the admin drag/drop problem over in #6661. |
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! 🎉 |
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. |
seems like a mishandled corner case? @zadjii-msft
|
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 |
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. |
Yep, sure. Those are both known. There are additional notes in: |
Originally posted by @DHowett-MSFT in #443 (comment)
The text was updated successfully, but these errors were encountered: