Skip to content

Add support for "workspaces" based on window names#20162

Merged
zadjii-msft merged 19 commits into
mainfrom
dev/migrie/workspaces-for-pr
Jun 4, 2026
Merged

Add support for "workspaces" based on window names#20162
zadjii-msft merged 19 commits into
mainfrom
dev/migrie/workspaces-for-pr

Conversation

@zadjii-msft

@zadjii-msft zadjii-msft commented Apr 29, 2026

Copy link
Copy Markdown
Member

This adds a new feature to the Windows Terminal: "Workspaces"

Workspaces are very shamelessly inspired by Edge workspaces of the same name.

The core idea is that when users name a window and they close that window, we will persist that Windows layout and buffers, seperately from the rest of window restoration. So a user can open a named window, open some profiles, some panes, do some stuff in it, then close it, and we will keep that state around for the next time the user opens that window name.

Unnamed windows still behave the same. If you close an unnamed window, and it's not the last window, then we won't persist the state of it.

To facilitate restoring named windows, we add a openWorkspace action. This allows us to persist the open workspace action in the window layout restoration path. So when we deserialize the list of tab layouts, and open workspace action will tell us, hey, go retrieve this known workspace from the state.json, instead of trying to serialize the window state in two places.

As demoed in the video, we add a flyout to list the windows that the user has open, and the named workspaces that they have saved. This allows users to quickly reopen previously closed workspaces, as well as quickly rename a window, thereby adding it to the list of saved workspaces. This button can also be hidden using the theme settings.

Closes #17084

This adds a new feature to the Windows Terminal: "Workspaces"

Workspaces are very shamelessly inspired by Edge workspaces of the same name.

{{video here}}

The core idea is that when users name a window and they close that window, we
will persist that Windows layout and buffers, seperately from the rest of window
restoration. So a user can open a named window, open some profiles, some panes,
do some stuff in it, then close it, and we will keep that state around for the
next time the user opens that window name.

Unnamed windows still behave the same. If you close an unnamed window, and it's
not the last window, then we won't persist the state of it.

To facilitate restoring named windows, we add a `openWorkspace` action. This
allows us to persist the open workspace action in the window layout restoration
path. So when we deserialize the list of tab layouts, and open workspace action
will tell us, hey, go retrieve this known workspace from the state.json, instead
of trying to serialize the window state in two places.

<details>
<summary>
state.json
</summary>

```jsonc

	"persistedWindowLayouts" :
	[
		{
			"initialPosition" : "910,462",
			"initialSize" :
			{
				"height" : 623.20001220703125,
				"width" : 934.4000244140625
			},
			"launchMode" : "default",
			"tabLayout" :
			[
				{
					"action" : "newTab",
					"commandline" : "\"C:\\Program Files\\PowerShell\\7\\pwsh.exe\"",
					"profile" : "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
					"sessionId" : "{09d3ef05-ba3d-44ed-a8f1-065d08a806a4}",
					"startingDirectory" : "C:\\Users\\zadji",
					"suppressApplicationTitle" : false,
					"tabTitle" : "PowerShell"
				},
			]
		},
		{
			"tabLayout" :
			[
				{
					"action" : "openWorkspace",
					"name" : "wsl"
				}
			]
		}
	],
	"persistedWorkspaces" :
	{
		"wsl" :
		{
			"initialPosition" : "109,502",
			"initialSize" :
			{
				"height" : 568,
				"width" : 1184.800048828125
			},
			"launchMode" : "default",
			"tabLayout" :
			[
				{
					"action" : "newTab",
					"commandline" : "ubuntu.exe",
					"profile" : "{51855cb2-8cce-5362-8f54-464b92b32386}",
					"sessionId" : "{a7f70c49-71ec-4ac4-9f3a-4884528a3fd6}",
					"startingDirectory" : null,
					"suppressApplicationTitle" : false,
					"tabTitle" : "Ubuntu"
				},
				{
					"action" : "renameWindow",
					"name" : "wsl"
				}
			]
		}
	},
```

</details>

As demoed in the video, we add a flyout to list the windows that the user has
open, and the named workspaces that they have saved. This allows users to
quickly reopen previously closed workspaces, as well as quickly rename a window,
thereby adding it to the list of saved workspaces. This button can also be
hidden using the theme settings.
Comment thread src/cascadia/WindowsTerminal/AppHost.cpp Fixed
Comment thread src/cascadia/WindowsTerminal/AppHost.cpp Fixed
Comment thread src/cascadia/WindowsTerminal/AppHost.cpp Fixed
Comment thread src/cascadia/WindowsTerminal/AppHost.cpp Fixed
@github-actions

This comment has been minimized.

@carlos-zamora

carlos-zamora commented May 5, 2026

Copy link
Copy Markdown
Member

MG edit: collapsing these todos, as they should all be addressed in the most recent push

Feedback from Bug Bash (5/5)
  • Ctrl+Shift+W on a workspace (named) window will crash the app
  • how do i delete a workspace? closing the last tab manually didn't do it...
  • window renamer should automatically take focus
  • nit: add "workspaces" action(s) to make it keyboard accessible
  • Small text: Great! Small button: Eh? Makes it hard to press quickly.
  • Asymmetric button alignment:
image

Personal note: this is a really cool feature. Thanks for making it happen 😊

There was also some discussion as to how to manage workspaces. It should probably go in the settings UI. Looks like management is pretty simple though. We'd really just need the ability to rename, delete, and open them.

@carlos-zamora carlos-zamora left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks and feels great! Excited for this to land!

The bug below is blocking, for sure.
Also, the global setting should be added to the settings UI in the global appearance page.

Bug

Repro:

  1. Open Terminal
  2. Open a second tab and rename the 2 tabs to "Tab 1" and "Tab 2"
  3. Click Workspaces button > Name this window... --> "My Workspace"
  4. ctrl+shift+n --> open new terminal window
  5. (NOTE) at this point, the workspaces flyout show "#1: My Workspace" appropriately. State seems good!
  6. Close "My Workspace" window
  7. Click Workspaces button > "My Workspace"

Expected: "My Workspace" opens in the same state as in stage 5 above

Actual:
Image

Looks like the space in "My Workspace" caused some kind of parsing issue? The workspace is named "My" and the first tab is named "Workspace".

Clicking on the Workspaces button in the other window shows this:

Image

I think I have a separate comment that found the bug over here: https://github.com/microsoft/terminal/pull/20162/changes#r3352221900

Comment thread tools/runformat.cmd
rem run clang-format on c++ files

powershell -noprofile "import-module %OPENCON_TOOLS%\openconsole.psm1; Invoke-CodeFormat"
pwsh -noprofile -c "import-module %OPENCON_TOOLS%\openconsole.psm1; Invoke-CodeFormat"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unrelated change

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So @zadjii-msft did we merge with these unrelated changes in?

Comment thread tools/OpenConsole.psm1
)

$clangFormatPath = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -find "**\x64\bin\clang-format.exe"
$clangFormatPath = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -prerelease -find "**\x64\bin\clang-format.exe"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unrelated change

Comment thread src/cascadia/TerminalSettingsModel/MTSMSettings.h Outdated
Comment thread src/cascadia/TerminalApp/TabRowControl.xaml
Comment thread src/cascadia/TerminalApp/TabRowControl.xaml
Comment thread src/cascadia/TerminalApp/TerminalPage.cpp Outdated
Comment thread src/cascadia/TerminalApp/TerminalPage.cpp Outdated
Comment on lines +5857 to +5858
// Right-click to delete: attach a context flyout with a
// "Delete workspace?" item that opens a confirmation dialog.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Love this functionality. Found it by total accident. Wonder if there's a way to make this more discoverable. Noticed we do something similar with "run as administrator" in the new tab menu.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Lemme tell you, winui menuflyouts suck. I tried a bunch of different things to add like, a ... button to this flyout and nothing looked remotely good. That's just not a pattern that's used in WinUI.

CmdPal did do something similar, but to do that we basically built our own menuflyout manually, and I'm not keen to try and port all that code to C++/winrt 😔

Comment on lines +5857 to +5858
// Right-click to delete: attach a context flyout with a
// "Delete workspace?" item that opens a confirmation dialog.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Separately, is there a way to do this with the keyboard? Only workaround I can think of is renaming the current workspace to an empty string.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In this PR, no. I didn't have a clever solution for that. I figured we'd get it in, feel it out, and figure out what we think feels right after playing with it more.

Comment thread doc/cascadia/profiles.schema.json Outdated
@microsoft-github-policy-service microsoft-github-policy-service Bot added Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something and removed Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something labels Jun 4, 2026

@carlos-zamora carlos-zamora left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work! Excited to see this land! 😊

@carlos-zamora

Copy link
Copy Markdown
Member

Extracted from PR body

Demo

wt-workspaces-000.mp4
state.json
	"persistedWindowLayouts" :
	[
		{
			"initialPosition" : "910,462",
			"initialSize" :
			{
				"height" : 623.20001220703125,
				"width" : 934.4000244140625
			},
			"launchMode" : "default",
			"tabLayout" :
			[
				{
					"action" : "newTab",
					"commandline" : "\"C:\\Program Files\\PowerShell\\7\\pwsh.exe\"",
					"profile" : "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
					"sessionId" : "{09d3ef05-ba3d-44ed-a8f1-065d08a806a4}",
					"startingDirectory" : "C:\\Users\\zadji",
					"suppressApplicationTitle" : false,
					"tabTitle" : "PowerShell"
				},
			]
		},
		{
			"tabLayout" :
			[
				{
					"action" : "openWorkspace",
					"name" : "wsl"
				}
			]
		}
	],
	"persistedWorkspaces" :
	{
		"wsl" :
		{
			"initialPosition" : "109,502",
			"initialSize" :
			{
				"height" : 568,
				"width" : 1184.800048828125
			},
			"launchMode" : "default",
			"tabLayout" :
			[
				{
					"action" : "newTab",
					"commandline" : "ubuntu.exe",
					"profile" : "{51855cb2-8cce-5362-8f54-464b92b32386}",
					"sessionId" : "{a7f70c49-71ec-4ac4-9f3a-4884528a3fd6}",
					"startingDirectory" : null,
					"suppressApplicationTitle" : false,
					"tabTitle" : "Ubuntu"
				},
				{
					"action" : "renameWindow",
					"name" : "wsl"
				}
			]
		}
	},

@zadjii-msft zadjii-msft merged commit 4b8b6ff into main Jun 4, 2026
18 of 20 checks passed
@zadjii-msft zadjii-msft deleted the dev/migrie/workspaces-for-pr branch June 4, 2026 21:00

@DHowett DHowett left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@zadjii-msft I would have blocked this PR had I read it in time. I think we might should revert it, even, until the wt.exe thing is fixed!

seInfo.lpFile = exePath.c_str();
seInfo.lpParameters = cmdline.c_str();
seInfo.nShow = SW_SHOWNORMAL;
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are doing this to ourselves? We are literally calling from inside the process, and we are spawning wt TO SPAWN WindowsTerminal to send a commandline back to ourselves. Why.

This is worse than "we serialize the tab to JSON to tell the other part of the app to launch it anew". Why.

Why.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is literally what openWindow already does literally 20 lines above this. I copypasted that code cause it just worked.

We can refactor that one too... but it seemed like the parity between the two implementations was the easiest path.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fair enough. Do we want to re-open this PR then? Sorry for the back and forth.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nope, we're working on the correct solution

carlos-zamora added a commit that referenced this pull request Jun 4, 2026
…20291)

This reverts commit 4b8b6ff: Add
support for "workspaces" based on window names (#20162)

@DHowett found the following blocking issues:
-
#20162 (comment)
- unrelated changes in `tools/runformat.cmd` and `OpenConsole.psm1`
zadjii-msft added a commit that referenced this pull request Jun 11, 2026
From ye old days where we were a multi-process app, we would
`shellexecute` to create a new Terminal process when we wanted to open a
new window.

Now that we're all one process (#18215), this dance doesn't make sense
anymore.

So: let's just add a bunch of plumbing to take the IContentArgs up to
the emperor and have it hand them back to a new window.

(this will be used to resurrect #20162)
DHowett pushed a commit that referenced this pull request Jun 11, 2026
This is a pair of changes to make the code formatting scripts work,
circa 2026.

* If you only have VS Insiders installed, then vswhere can't actually
find a clang-format to build with. You need `-prerelease` to let it also
find preview builds of VS.
* Years ago we updated OpenConsole.psm1 to only support pwsh 7. I, of
course, use `cmd` as my dev env for terminal, so I use `runformat.cmd`,
which always shelled to `powershell` (5). This updates that tool to use
pwsh 7. Clearly this impacted only me ever.

related to #20162
zadjii-msft added a commit that referenced this pull request Jun 16, 2026
This adds a new feature to the Windows Terminal: "Workspaces"

Workspaces are very shamelessly inspired by Edge workspaces of the same
name.

The core idea is that when users name a window and they close that
window, we will persist that Windows layout and buffers, seperately from
the rest of window restoration. So a user can open a named window, open
some profiles, some panes, do some stuff in it, then close it, and we
will keep that state around for the next time the user opens that window
name.

Unnamed windows still behave the same. If you close an unnamed window,
and it's not the last window, then we won't persist the state of it.

To facilitate restoring named windows, we add a `openWorkspace` action.
This allows us to persist the open workspace action in the window layout
restoration path. So when we deserialize the list of tab layouts, and
open workspace action will tell us, hey, go retrieve this known
workspace from the state.json, instead of trying to serialize the window
state in two places.

As demoed in the video, we add a flyout to list the windows that the
user has open, and the named workspaces that they have saved. This
allows users to quickly reopen previously closed workspaces, as well as
quickly rename a window, thereby adding it to the list of saved
workspaces. This button can also be hidden using the theme settings.

Closes #17084

(this is a reboot of #20162, but with the event from #20311)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Workspaces / Open a group of tabs at one time

4 participants