mirror of
https://github.com/microsoft/terminal.git
synced 2026-07-08 17:56:28 +00:00
Add support for "workspaces" based on window names (redux) (#20314)
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)
This commit is contained in:
@@ -441,6 +441,7 @@
|
||||
"openTabColorPicker",
|
||||
"openTabRenamer",
|
||||
"openWindowRenamer",
|
||||
"openWorkspace",
|
||||
"paste",
|
||||
"prevTab",
|
||||
"quakeMode",
|
||||
@@ -479,6 +480,7 @@
|
||||
"toggleReadOnlyMode",
|
||||
"toggleShaderEffects",
|
||||
"toggleSplitOrientation",
|
||||
"workspaces",
|
||||
"wt",
|
||||
"unbound"
|
||||
],
|
||||
@@ -1721,6 +1723,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"OpenWorkspaceAction": {
|
||||
"description": "Arguments corresponding to an openWorkspace Action",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/ShortcutAction"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"const": "openWorkspace"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The name of the workspace to open. If omitted, the workspaces UI will be shown so the user can pick one."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"FocusPaneAction": {
|
||||
"description": "Arguments corresponding to a focusPane Action",
|
||||
"allOf": [
|
||||
@@ -2040,6 +2064,11 @@
|
||||
"unfocusedFrame": {
|
||||
"description": "The color of the window frame when the window is inactive. This only works on Windows 11",
|
||||
"$ref": "#/$defs/ThemeColor"
|
||||
},
|
||||
"showWorkspacesButton": {
|
||||
"description": "When set to true, the workspaces button will be shown in the tab row.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2208,6 +2237,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/RenameWindowAction"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/OpenWorkspaceAction"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/FocusPaneAction"
|
||||
},
|
||||
|
||||
@@ -895,6 +895,16 @@ namespace winrt::TerminalApp::implementation
|
||||
RequestNewWindow.raise(*this, request);
|
||||
}
|
||||
|
||||
// Ask the WindowEmperor (in-process) to open or summon a named window,
|
||||
// restoring its persisted workspace if one exists. The event bubbles up
|
||||
// through TerminalWindow to AppHost, which calls into the WindowEmperor
|
||||
// directly — no second wt.exe process is launched.
|
||||
void TerminalPage::_OpenWorkspaceWindow(const winrt::hstring name)
|
||||
{
|
||||
const auto args = winrt::make<implementation::OpenWindowRequestedArgs>(name);
|
||||
RequestOpenWindow.raise(*this, args);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& actionArgs)
|
||||
{
|
||||
@@ -916,7 +926,10 @@ namespace winrt::TerminalApp::implementation
|
||||
newContentArgs = NewTerminalArgs{};
|
||||
}
|
||||
|
||||
// Manually fill in the evaluated profile
|
||||
// If this is a NewTerminalArgs, resolve its profile up-front so the
|
||||
// spawned window doesn't need to re-resolve it. Other content types
|
||||
// (e.g. scratchpad) don't have profiles to evaluate — they get passed
|
||||
// through as-is.
|
||||
if (const auto terminalArgs{ newContentArgs.try_as<NewTerminalArgs>() })
|
||||
{
|
||||
const auto profile{ _settings.GetProfileForArgs(terminalArgs) };
|
||||
@@ -1015,6 +1028,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// Fun!
|
||||
// WindowRenamerTextBox().Focus(FocusState::Programmatic);
|
||||
_renamerLayoutUpdatedRevoker.revoke();
|
||||
_renamerLayoutCount = 0;
|
||||
_renamerLayoutUpdatedRevoker = WindowRenamerTextBox().LayoutUpdated(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (auto self{ weakThis.get() })
|
||||
{
|
||||
@@ -1590,4 +1604,35 @@ namespace winrt::TerminalApp::implementation
|
||||
args.Handled(handled);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleOpenWorkspace(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
// Open (or summon) a named window. We launch a new `wt -w <name>`
|
||||
// process which the monarch will route to the correct live window or
|
||||
// restore from a persisted workspace.
|
||||
if (args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<OpenWorkspaceArgs>())
|
||||
{
|
||||
const auto name = realArgs.Name();
|
||||
if (!name.empty())
|
||||
{
|
||||
_OpenWorkspaceWindow(name);
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleWorkspaces(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (_workspaceFlyout && _workspaceDropdown)
|
||||
{
|
||||
_workspaceFlyout.ShowAt(_workspaceDropdown);
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -92,6 +92,10 @@ INewContentArgs Pane::GetTerminalArgsForPane(BuildStartupKind kind) const
|
||||
{
|
||||
// Leaves are the only things that have controls
|
||||
assert(_IsLeaf());
|
||||
if (!_content)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return _content.GetNewTerminalArgs(kind);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace winrt::TerminalApp::implementation
|
||||
WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command, nullptr);
|
||||
WINRT_PROPERTY(winrt::hstring, Content);
|
||||
WINRT_PROPERTY(Windows::Foundation::IReference<Windows::Foundation::Rect>, InitialBounds);
|
||||
WINRT_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::WindowLayout, PersistedLayout, nullptr);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>, StartupActions, nullptr);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace TerminalApp
|
||||
CommandlineArgs Command { get; };
|
||||
String Content { get; };
|
||||
Windows.Foundation.IReference<Windows.Foundation.Rect> InitialBounds { get; };
|
||||
Microsoft.Terminal.Settings.Model.WindowLayout PersistedLayout;
|
||||
Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.ActionAndArgs> StartupActions;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -743,6 +743,42 @@
|
||||
<value>unnamed window</value>
|
||||
<comment>text used to identify when a window hasn't been assigned a name by the user</comment>
|
||||
</data>
|
||||
<data name="NameThisWindowMenuItem" xml:space="preserve">
|
||||
<value>Name this window...</value>
|
||||
<comment>Menu item text shown when the current window has no name assigned</comment>
|
||||
</data>
|
||||
<data name="RenameThisWindowMenuItem" xml:space="preserve">
|
||||
<value>Rename this window...</value>
|
||||
<comment>Menu item text shown when the current window already has a name</comment>
|
||||
</data>
|
||||
<data name="WindowListUnnamedEntry" xml:space="preserve">
|
||||
<value>#{0} (unnamed)</value>
|
||||
<comment>{locked="{0}"}{0} is the window ID number. Shown in the workspace flyout for windows that have no name assigned.</comment>
|
||||
</data>
|
||||
<data name="DeleteWorkspaceMenuItem" xml:space="preserve">
|
||||
<value>Delete workspace?</value>
|
||||
<comment>Menu item text shown in the right-click context menu on a saved workspace</comment>
|
||||
</data>
|
||||
<data name="OpenWorkspaceMenuItem" xml:space="preserve">
|
||||
<value>Open workspace</value>
|
||||
<comment>Menu item text for opening a saved workspace</comment>
|
||||
</data>
|
||||
<data name="ConfirmDeleteWorkspaceTitle" xml:space="preserve">
|
||||
<value>Delete "{0}"?</value>
|
||||
<comment>{locked="{0}"}{0} is the workspace name. Shown as the title of a confirmation dialog when the user tries to delete a saved workspace.</comment>
|
||||
</data>
|
||||
<data name="ConfirmDeleteWorkspaceBody" xml:space="preserve">
|
||||
<value>Are you sure you want to delete "{0}"? Sessions associated with this workspace will also be deleted.</value>
|
||||
<comment>{locked="{0}"}{0} is the workspace name. Body text of the confirmation dialog shown when the user tries to delete a saved workspace.</comment>
|
||||
</data>
|
||||
<data name="ConfirmDeleteWorkspaceDelete" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
<comment>Primary button text on the delete workspace confirmation dialog</comment>
|
||||
</data>
|
||||
<data name="ConfirmDeleteWorkspaceCancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
<comment>Cancel button text on the delete workspace confirmation dialog</comment>
|
||||
</data>
|
||||
<data name="WindowRenamer.Subtitle" xml:space="preserve">
|
||||
<value>Enter a new name:</value>
|
||||
</data>
|
||||
@@ -801,6 +837,9 @@
|
||||
<data name="ElevationShield.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>This Terminal window is running as administrator</value>
|
||||
</data>
|
||||
<data name="WorkspaceDropdown.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Workspaces</value>
|
||||
</data>
|
||||
<data name="CommandPalette_MatchesAvailable" xml:space="preserve">
|
||||
<value>Suggestions found: {0}</value>
|
||||
<comment>{0} will be replaced with a number.</comment>
|
||||
|
||||
@@ -403,6 +403,22 @@ namespace winrt::TerminalApp::implementation
|
||||
_previouslyClosedPanesAndTabs.emplace_back(args);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - If this window has a name, persist its current workspace layout to
|
||||
// ApplicationState. Intended to be called from the close-pane / close-tab
|
||||
// paths while tab/pane content is still alive (before it gets torn down).
|
||||
void TerminalPage::_SaveWorkspaceIfNeeded()
|
||||
{
|
||||
const auto& windowName = _WindowProperties.WindowName();
|
||||
if (!windowName.empty())
|
||||
{
|
||||
if (const auto layout = GetWindowLayout())
|
||||
{
|
||||
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Removes the tab (both TerminalControl and XAML) after prompting for approval
|
||||
// Arguments:
|
||||
@@ -450,6 +466,14 @@ namespace winrt::TerminalApp::implementation
|
||||
auto actions = t->BuildStartupActions(BuildStartupKind::None);
|
||||
_AddPreviouslyClosedPaneOrTab(std::move(actions));
|
||||
|
||||
// If this is the last tab in a named window, persist the workspace
|
||||
// layout now while tab content is still alive. After tab.Close()
|
||||
// the pane content will be torn down by the time _RemoveTab runs.
|
||||
if (_tabs.Size() == 1)
|
||||
{
|
||||
_SaveWorkspaceIfNeeded();
|
||||
}
|
||||
|
||||
tab.Close();
|
||||
}
|
||||
|
||||
@@ -471,6 +495,13 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
const auto focusedTabIndex{ _GetFocusedTabIndex() };
|
||||
|
||||
// NOTE: Workspace persistence for named windows used to live here,
|
||||
// but by the time _RemoveTab runs the pane content may already be
|
||||
// torn down (e.g. from the close-pane path). Instead, workspace
|
||||
// saves are handled earlier:
|
||||
// - Close-pane (last pane): in _HandleClosePaneRequested
|
||||
// - Close-tab: in _HandleCloseTabRequested
|
||||
|
||||
// Removing the tab from the collection should destroy its control and disconnect its connection,
|
||||
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
|
||||
tab.Shutdown();
|
||||
@@ -798,6 +829,21 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
_AddPreviouslyClosedPaneOrTab(std::move(state.args));
|
||||
|
||||
// If this is the last pane on the last tab of a named window, persist
|
||||
// the workspace layout now while the pane content is still alive.
|
||||
// We can't wait until _RemoveTab, because pane->Close() below will
|
||||
// destroy the content before _RemoveTab is reached.
|
||||
if (_tabs.Size() == 1)
|
||||
{
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (activeTab->GetLeafPaneCount() == 1)
|
||||
{
|
||||
_SaveWorkspaceIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If specified, detach before closing to directly update the pane structure
|
||||
pane->Close();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
WINRT_OBSERVABLE_PROPERTY(bool, ShowElevationShield, PropertyChanged.raise, false);
|
||||
WINRT_OBSERVABLE_PROPERTY(bool, ShowWorkspacesButton, PropertyChanged.raise, true);
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, WorkspaceName, PropertyChanged.raise, L"");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,5 +9,7 @@ namespace TerminalApp
|
||||
TabRowControl();
|
||||
Microsoft.UI.Xaml.Controls.TabView TabView { get; };
|
||||
Boolean ShowElevationShield;
|
||||
Boolean ShowWorkspacesButton;
|
||||
String WorkspaceName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
Background="{ThemeResource TabViewBackground}"
|
||||
mc:Ignorable="d">
|
||||
@@ -35,6 +36,7 @@
|
||||
TabWidthMode="Equal">
|
||||
|
||||
<mux:TabView.TabStripHeader>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!-- EA18 is the "Shield" glyph -->
|
||||
<FontIcon x:Uid="ElevationShield"
|
||||
Margin="9,4,0,4"
|
||||
@@ -43,6 +45,36 @@
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Glyph=""
|
||||
Visibility="{x:Bind ShowElevationShield, Mode=OneWay}" />
|
||||
|
||||
<!-- Workspace/windows button -->
|
||||
<Button x:Name="WorkspaceDropdown"
|
||||
x:Uid="WorkspaceDropdown"
|
||||
Margin="4,0,0,4"
|
||||
Padding="8,0,0,0"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Visibility="{x:Bind ShowWorkspacesButton, Mode=OneWay}">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<!-- EE40 is the "TaskViewSettings" glyph -->
|
||||
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Glyph="" />
|
||||
<TextBlock x:Name="WorkspaceNameText"
|
||||
Padding="0,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Text="{x:Bind WorkspaceName, Mode=OneWay}"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(WorkspaceName), Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout x:Name="WorkspaceFlyout" />
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</mux:TabView.TabStripHeader>
|
||||
|
||||
<mux:TabView.TabStripFooter>
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
#include "TerminalSettingsCache.h"
|
||||
|
||||
#include "LaunchPositionRequest.g.cpp"
|
||||
#include "WindowListEntry.g.cpp"
|
||||
#include "WindowListRequest.g.cpp"
|
||||
#include "RenameWindowRequestedArgs.g.cpp"
|
||||
#include "OpenWindowRequestedArgs.g.cpp"
|
||||
#include "RequestMoveContentArgs.g.cpp"
|
||||
#include "TerminalPage.g.cpp"
|
||||
|
||||
@@ -334,6 +337,21 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
auto tabRowImpl = winrt::get_self<implementation::TabRowControl>(_tabRow);
|
||||
_newTabButton = tabRowImpl->NewTabButton();
|
||||
_workspaceFlyout = tabRowImpl->WorkspaceFlyout();
|
||||
_workspaceDropdown = tabRowImpl->WorkspaceDropdown();
|
||||
|
||||
// Set the initial workspace name from the window name.
|
||||
// Use raw WindowName() so unnamed windows show no text.
|
||||
_tabRow.WorkspaceName(_WindowProperties.WindowName());
|
||||
|
||||
// Rebuild the workspace flyout each time it opens so it always
|
||||
// reflects the latest set of persisted workspaces.
|
||||
_workspaceFlyout.Opening([weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->_PopulateWorkspaceFlyout();
|
||||
}
|
||||
});
|
||||
|
||||
if (_settings.GlobalSettings().ShowTabsInTitlebar())
|
||||
{
|
||||
@@ -443,6 +461,12 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
_tabRow.ShowElevationShield(IsRunningElevated() && _settings.GlobalSettings().ShowAdminShield());
|
||||
|
||||
// Apply the ShowWorkspacesButton theme setting.
|
||||
if (const auto theme = _settings.GlobalSettings().CurrentTheme())
|
||||
{
|
||||
_tabRow.ShowWorkspacesButton(theme.Window() ? theme.Window().ShowWorkspacesButton() : true);
|
||||
}
|
||||
|
||||
_adjustProcessPriorityThrottled = std::make_shared<ThrottledFunc<>>(
|
||||
DispatcherQueue::GetForCurrentThread(),
|
||||
til::throttled_func_options{
|
||||
@@ -2285,14 +2309,14 @@ namespace winrt::TerminalApp::implementation
|
||||
QuitRequested.raise(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void TerminalPage::PersistState()
|
||||
WindowLayout TerminalPage::GetWindowLayout()
|
||||
{
|
||||
// This method may be called for a window even if it hasn't had a tab yet or lost all of them.
|
||||
// We shouldn't persist such windows.
|
||||
const auto tabCount = _tabs.Size();
|
||||
if (_startupState != StartupState::Initialized || tabCount == 0)
|
||||
{
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<ActionAndArgs> actions;
|
||||
@@ -2307,7 +2331,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// Avoid persisting a window with zero tabs, because `BuildStartupActions` happened to return an empty vector.
|
||||
if (actions.empty())
|
||||
{
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// if the focused tab was not the last tab, restore that
|
||||
@@ -2356,8 +2380,50 @@ namespace winrt::TerminalApp::implementation
|
||||
RequestLaunchPosition.raise(*this, launchPosRequest);
|
||||
layout.InitialPosition(launchPosRequest.Position());
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
void TerminalPage::PersistState()
|
||||
{
|
||||
// There are two persistence mechanisms in play here:
|
||||
// * PersistedWindowLayouts (vector) — consumed on next startup to
|
||||
// re-open a matching set of windows. Cleared after restore.
|
||||
// * PersistedWorkspaces (name-keyed map) — the full tab/buffer
|
||||
// state of a named window, claimed by name on demand via
|
||||
// ApplicationState::TakeWorkspace.
|
||||
//
|
||||
// For named windows we save the full layout into the workspace map
|
||||
// and drop a lightweight `openWorkspace` stub into the generic vector,
|
||||
// so the generic restore path re-opens the named window which in
|
||||
// turn claims its own workspace. Unnamed windows don't have a stable
|
||||
// key, so their full layout is stored directly in the vector.
|
||||
if (const auto layout = GetWindowLayout())
|
||||
{
|
||||
const auto& windowName = _WindowProperties.WindowName();
|
||||
if (!windowName.empty())
|
||||
{
|
||||
// Persist the full layout into the workspace collection.
|
||||
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
|
||||
|
||||
// Build a minimal layout with just an openWorkspace action
|
||||
// so the generic restore path re-opens this workspace by name.
|
||||
std::vector<ActionAndArgs> actions;
|
||||
ActionAndArgs action;
|
||||
action.Action(ShortcutAction::OpenWorkspace);
|
||||
OpenWorkspaceArgs args{ windowName };
|
||||
action.Args(args);
|
||||
actions.emplace_back(std::move(action));
|
||||
|
||||
WindowLayout stub;
|
||||
stub.TabLayout(winrt::single_threaded_vector<ActionAndArgs>(std::move(actions)));
|
||||
ApplicationState::SharedInstance().AppendPersistedWindowLayout(stub);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplicationState::SharedInstance().AppendPersistedWindowLayout(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether a close-window action should show a confirmation
|
||||
@@ -4057,6 +4123,12 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
_tabRow.ShowElevationShield(IsRunningElevated() && _settings.GlobalSettings().ShowAdminShield());
|
||||
|
||||
// Apply the ShowWorkspacesButton theme setting.
|
||||
if (const auto theme = _settings.GlobalSettings().CurrentTheme())
|
||||
{
|
||||
_tabRow.ShowWorkspacesButton(theme.Window() ? theme.Window().ShowWorkspacesButton() : true);
|
||||
}
|
||||
|
||||
Media::SolidColorBrush transparent{ Windows::UI::Colors::Transparent() };
|
||||
_tabView.Background(transparent);
|
||||
|
||||
@@ -5697,6 +5769,196 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the workspace flyout contents. Called every time the flyout opens
|
||||
// so it reflects the current set of persisted workspaces.
|
||||
void TerminalPage::_PopulateWorkspaceFlyout()
|
||||
{
|
||||
if (!_workspaceFlyout)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_workspaceFlyout.Items().Clear();
|
||||
|
||||
// --- "Name / Rename this window" ---
|
||||
{
|
||||
MenuFlyoutItem item{};
|
||||
item.Text(_WindowProperties.WindowName().empty() ? RS_(L"NameThisWindowMenuItem") : RS_(L"RenameThisWindowMenuItem"));
|
||||
|
||||
auto iconElement = UI::IconPathConverter::IconWUX(L"\uE8AC"); // Rename glyph
|
||||
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
|
||||
item.Icon(iconElement);
|
||||
|
||||
item.Click([weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->_actionDispatch->DoAction(ActionAndArgs{ ShortcutAction::OpenWindowRenamer, nullptr });
|
||||
}
|
||||
});
|
||||
_workspaceFlyout.Items().Append(item);
|
||||
}
|
||||
|
||||
// --- Gather open window info first so we can filter workspaces ---
|
||||
const auto windowListReq{ winrt::make<WindowListRequest>() };
|
||||
RequestWindowList.raise(*this, windowListReq);
|
||||
const auto windowEntries = windowListReq.Entries();
|
||||
|
||||
std::set<winrt::hstring> openWindowNames;
|
||||
if (windowEntries)
|
||||
{
|
||||
for (const auto& entry : windowEntries)
|
||||
{
|
||||
const auto& name = entry.Name();
|
||||
if (!name.empty())
|
||||
{
|
||||
openWindowNames.emplace(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Saved workspaces section (only those not currently open) ---
|
||||
// Collect workspace names that aren't currently open so we can show
|
||||
// them both as top-level "open" items and inside the delete sub-menu.
|
||||
const auto workspaces = ApplicationState::SharedInstance().AllPersistedWorkspaces();
|
||||
if (workspaces && workspaces.Size() > 0)
|
||||
{
|
||||
bool addedSeparator = false;
|
||||
|
||||
for (const auto& pair : workspaces)
|
||||
{
|
||||
const auto name = pair.Key();
|
||||
|
||||
// Skip workspaces that correspond to a currently-open window.
|
||||
if (openWindowNames.contains(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!addedSeparator)
|
||||
{
|
||||
_workspaceFlyout.Items().Append(MenuFlyoutSeparator{});
|
||||
addedSeparator = true;
|
||||
}
|
||||
|
||||
MenuFlyoutItem item{};
|
||||
item.Text(name);
|
||||
|
||||
auto iconElement = UI::IconPathConverter::IconWUX(L"\uE8F1"); // SwitchApps glyph
|
||||
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
|
||||
item.Icon(iconElement);
|
||||
|
||||
item.Click([weakThis{ get_weak() }, name](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->_OpenWorkspaceWindow(name);
|
||||
}
|
||||
});
|
||||
|
||||
// Right-click to delete: attach a context flyout with a
|
||||
// "Delete workspace?" item that opens a confirmation dialog.
|
||||
{
|
||||
WUX::Controls::MenuFlyout deleteFlyout{};
|
||||
deleteFlyout.Placement(WUX::Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedRight);
|
||||
|
||||
WUX::Controls::MenuFlyoutItem deleteItem{};
|
||||
deleteItem.Text(RS_(L"DeleteWorkspaceMenuItem"));
|
||||
|
||||
auto trashIcon = UI::IconPathConverter::IconWUX(L"\xE74D"); // Delete glyph
|
||||
|
||||
deleteItem.Click([weakThis{ get_weak() }, name](auto&&, auto&&) -> safe_void_coroutine {
|
||||
auto page{ weakThis.get() };
|
||||
if (!page)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
// Build and show a confirmation ContentDialog.
|
||||
ContentDialog dialog{};
|
||||
dialog.Title(winrt::box_value(winrt::hstring{ RS_fmt(L"ConfirmDeleteWorkspaceTitle", name) }));
|
||||
dialog.Content(winrt::box_value(winrt::hstring{ RS_fmt(L"ConfirmDeleteWorkspaceBody", name) }));
|
||||
dialog.PrimaryButtonText(RS_(L"ConfirmDeleteWorkspaceDelete"));
|
||||
dialog.CloseButtonText(RS_(L"ConfirmDeleteWorkspaceCancel"));
|
||||
dialog.DefaultButton(ContentDialogButton::Close);
|
||||
|
||||
if (auto presenter{ page->_dialogPresenter.get() })
|
||||
{
|
||||
const auto result = co_await presenter.ShowDialog(dialog);
|
||||
// Re-check after co_await
|
||||
page = weakThis.get();
|
||||
if (!page)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
if (result == ContentDialogResult::Primary)
|
||||
{
|
||||
ApplicationState::SharedInstance().RemoveWorkspace(name);
|
||||
page->_PopulateWorkspaceFlyout();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
deleteFlyout.Items().Append(deleteItem);
|
||||
WUX::Controls::Primitives::FlyoutBase::SetAttachedFlyout(item, deleteFlyout);
|
||||
item.ContextRequested([item](auto&&, auto&&) {
|
||||
WUX::Controls::Primitives::FlyoutBase::ShowAttachedFlyout(item);
|
||||
});
|
||||
}
|
||||
|
||||
_workspaceFlyout.Items().Append(item);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Open windows section ---
|
||||
if (windowEntries && windowEntries.Size() > 0)
|
||||
{
|
||||
_workspaceFlyout.Items().Append(MenuFlyoutSeparator{});
|
||||
|
||||
const auto thisWindowId = _WindowProperties.WindowId();
|
||||
|
||||
for (const auto& entry : windowEntries)
|
||||
{
|
||||
const auto id = entry.Id();
|
||||
const auto& name = entry.Name();
|
||||
|
||||
winrt::hstring displayText;
|
||||
if (name.empty())
|
||||
{
|
||||
displayText = winrt::hstring{ RS_fmt(L"WindowListUnnamedEntry", id) };
|
||||
}
|
||||
else
|
||||
{
|
||||
displayText = winrt::hstring{ fmt::format(FMT_COMPILE(L"#{}: {}"), id, name) };
|
||||
}
|
||||
|
||||
MenuFlyoutItem item{};
|
||||
item.Text(displayText);
|
||||
|
||||
if (id == thisWindowId)
|
||||
{
|
||||
auto iconElement = UI::IconPathConverter::IconWUX(L"\uE73E"); // CheckMark glyph
|
||||
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
|
||||
item.Icon(iconElement);
|
||||
item.IsEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto iconElement = UI::IconPathConverter::IconWUX(L"\uE737"); // ChromeRestore glyph
|
||||
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
|
||||
item.Icon(iconElement);
|
||||
|
||||
item.Click([weakThis{ get_weak() }, id](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->SummonWindowByIdRequested.raise(*page, winrt::make<SummonWindowByIdRequestedArgs>(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_workspaceFlyout.Items().Append(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for our WindowProperties's PropertyChanged event. We'll use this
|
||||
// to pop the "Identify Window" toast when the user renames our window.
|
||||
void TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/, const WUX::Data::PropertyChangedEventArgs& args)
|
||||
@@ -5706,6 +5968,10 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the workspace dropdown label in sync with the window name.
|
||||
// Use raw WindowName() so clearing the name hides the text.
|
||||
_tabRow.WorkspaceName(_WindowProperties.WindowName());
|
||||
|
||||
// DON'T display the confirmation if this is the name we were
|
||||
// given on startup!
|
||||
if (_startupState == StartupState::Initialized)
|
||||
|
||||
@@ -10,8 +10,12 @@
|
||||
#include "AppKeyBindings.h"
|
||||
#include "AppCommandlineArgs.h"
|
||||
#include "RenameWindowRequestedArgs.g.h"
|
||||
#include "OpenWindowRequestedArgs.g.h"
|
||||
#include "SummonWindowByIdRequestedArgs.g.h"
|
||||
#include "RequestMoveContentArgs.g.h"
|
||||
#include "LaunchPositionRequest.g.h"
|
||||
#include "WindowListEntry.g.h"
|
||||
#include "WindowListRequest.g.h"
|
||||
#include "Toast.h"
|
||||
|
||||
#include "WindowsPackageManagerFactory.h"
|
||||
@@ -73,6 +77,24 @@ namespace winrt::TerminalApp::implementation
|
||||
_ProposedName{ name } {};
|
||||
};
|
||||
|
||||
struct OpenWindowRequestedArgs : OpenWindowRequestedArgsT<OpenWindowRequestedArgs>
|
||||
{
|
||||
WINRT_PROPERTY(winrt::hstring, Name);
|
||||
|
||||
public:
|
||||
OpenWindowRequestedArgs(const winrt::hstring& name) :
|
||||
_Name{ name } {};
|
||||
};
|
||||
|
||||
struct SummonWindowByIdRequestedArgs : SummonWindowByIdRequestedArgsT<SummonWindowByIdRequestedArgs>
|
||||
{
|
||||
WINRT_PROPERTY(uint64_t, WindowId);
|
||||
|
||||
public:
|
||||
SummonWindowByIdRequestedArgs(uint64_t id) :
|
||||
_WindowId{ id } {};
|
||||
};
|
||||
|
||||
struct RequestMoveContentArgs : RequestMoveContentArgsT<RequestMoveContentArgs>
|
||||
{
|
||||
WINRT_PROPERTY(winrt::hstring, Window);
|
||||
@@ -94,6 +116,25 @@ namespace winrt::TerminalApp::implementation
|
||||
til::property<winrt::Microsoft::Terminal::Settings::Model::LaunchPosition> Position;
|
||||
};
|
||||
|
||||
struct WindowListEntry : WindowListEntryT<WindowListEntry>
|
||||
{
|
||||
WindowListEntry() = default;
|
||||
|
||||
til::property<uint64_t> Id;
|
||||
til::property<winrt::hstring> Name;
|
||||
};
|
||||
|
||||
struct WindowListRequest : WindowListRequestT<WindowListRequest>
|
||||
{
|
||||
WindowListRequest() :
|
||||
_Entries{ winrt::single_threaded_vector<winrt::TerminalApp::WindowListEntry>() } {}
|
||||
|
||||
winrt::Windows::Foundation::Collections::IVector<winrt::TerminalApp::WindowListEntry> Entries() const { return _Entries; }
|
||||
|
||||
private:
|
||||
winrt::Windows::Foundation::Collections::IVector<winrt::TerminalApp::WindowListEntry> _Entries;
|
||||
};
|
||||
|
||||
struct WinGetSearchParams
|
||||
{
|
||||
winrt::Microsoft::Management::Deployment::PackageMatchField Field;
|
||||
@@ -132,6 +173,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
safe_void_coroutine RequestQuit();
|
||||
safe_void_coroutine CloseWindow();
|
||||
winrt::Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
|
||||
void PersistState();
|
||||
std::vector<IPaneContent> Panes() const;
|
||||
|
||||
@@ -203,6 +245,7 @@ namespace winrt::TerminalApp::implementation
|
||||
til::typed_event<IInspectable, IInspectable> IdentifyWindowsRequested;
|
||||
til::typed_event<IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs> RenameWindowRequested;
|
||||
til::typed_event<IInspectable, IInspectable> SummonWindowRequested;
|
||||
til::typed_event<IInspectable, winrt::TerminalApp::SummonWindowByIdRequestedArgs> SummonWindowByIdRequested;
|
||||
til::typed_event<IInspectable, winrt::TerminalApp::Tab> FocusTabRequested;
|
||||
til::typed_event<IInspectable, winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs> WindowSizeChanged;
|
||||
|
||||
@@ -215,6 +258,8 @@ namespace winrt::TerminalApp::implementation
|
||||
til::typed_event<Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs> RequestReceiveContent;
|
||||
|
||||
til::typed_event<IInspectable, winrt::TerminalApp::LaunchPositionRequest> RequestLaunchPosition;
|
||||
til::typed_event<IInspectable, winrt::TerminalApp::WindowListRequest> RequestWindowList;
|
||||
til::typed_event<IInspectable, winrt::TerminalApp::OpenWindowRequestedArgs> RequestOpenWindow;
|
||||
til::typed_event<IInspectable, winrt::TerminalApp::WindowRequestedArgs> RequestNewWindow;
|
||||
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr);
|
||||
@@ -238,6 +283,8 @@ namespace winrt::TerminalApp::implementation
|
||||
TerminalApp::TabRowControl _tabRow{ nullptr };
|
||||
Windows::UI::Xaml::Controls::Grid _tabContent{ nullptr };
|
||||
Microsoft::UI::Xaml::Controls::SplitButton _newTabButton{ nullptr };
|
||||
Windows::UI::Xaml::Controls::MenuFlyout _workspaceFlyout{ nullptr };
|
||||
Windows::UI::Xaml::Controls::Button _workspaceDropdown{ nullptr };
|
||||
winrt::TerminalApp::ColorPickupFlyout _tabColorPicker{ nullptr };
|
||||
|
||||
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
|
||||
@@ -336,6 +383,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void _restartPaneConnection(const TerminalApp::TerminalPaneContent&, const winrt::Windows::Foundation::IInspectable&);
|
||||
|
||||
void _OpenNewWindow(const Microsoft::Terminal::Settings::Model::INewContentArgs& contentArgs);
|
||||
void _OpenWorkspaceWindow(const winrt::hstring name);
|
||||
|
||||
void _OpenNewTerminalViaDropdown(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
|
||||
|
||||
@@ -365,6 +413,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void _CloseTabAtIndex(uint32_t index);
|
||||
void _RemoveTab(const winrt::TerminalApp::Tab& tab);
|
||||
safe_void_coroutine _RemoveTabs(const std::vector<winrt::TerminalApp::Tab> tabs);
|
||||
void _SaveWorkspaceIfNeeded();
|
||||
|
||||
void _InitializeTab(winrt::com_ptr<Tab> newTabImpl, uint32_t insertPosition = -1);
|
||||
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term);
|
||||
@@ -578,6 +627,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _PopulateContextMenu(const Microsoft::Terminal::Control::TermControl& control, const Microsoft::UI::Xaml::Controls::CommandBarFlyout& sender, const bool withSelection);
|
||||
void _PopulateQuickFixMenu(const Microsoft::Terminal::Control::TermControl& control, const Windows::UI::Xaml::Controls::MenuFlyout& sender);
|
||||
void _PopulateWorkspaceFlyout();
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyout _CreateRunAsAdminFlyout(int profileIndex);
|
||||
|
||||
winrt::Microsoft::Terminal::Control::TermControl _senderOrActiveControl(const winrt::Windows::Foundation::IInspectable& sender);
|
||||
@@ -604,4 +654,5 @@ namespace winrt::TerminalApp::implementation
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(TerminalPage);
|
||||
BASIC_FACTORY(WindowListEntry);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,14 @@ namespace TerminalApp
|
||||
{
|
||||
String ProposedName { get; };
|
||||
};
|
||||
[default_interface] runtimeclass OpenWindowRequestedArgs
|
||||
{
|
||||
String Name { get; };
|
||||
};
|
||||
[default_interface] runtimeclass SummonWindowByIdRequestedArgs
|
||||
{
|
||||
UInt64 WindowId { get; };
|
||||
};
|
||||
[default_interface] runtimeclass RequestMoveContentArgs
|
||||
{
|
||||
String Window { get; };
|
||||
@@ -50,6 +58,20 @@ namespace TerminalApp
|
||||
Microsoft.Terminal.Settings.Model.LaunchPosition Position;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass WindowListEntry
|
||||
{
|
||||
WindowListEntry();
|
||||
UInt64 Id;
|
||||
String Name;
|
||||
}
|
||||
|
||||
// Raised by TerminalPage when it needs the list of open windows.
|
||||
// The handler (AppHost) fills Entries synchronously.
|
||||
[default_interface] runtimeclass WindowListRequest
|
||||
{
|
||||
Windows.Foundation.Collections.IVector<WindowListEntry> Entries { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, Microsoft.Terminal.UI.IDirectKeyListener
|
||||
{
|
||||
TerminalPage(WindowProperties properties, ContentManager manager);
|
||||
@@ -93,6 +115,7 @@ namespace TerminalApp
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, SummonWindowByIdRequestedArgs> SummonWindowByIdRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.WindowSizeChangedEventArgs> WindowSizeChanged;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
|
||||
@@ -103,6 +126,12 @@ namespace TerminalApp
|
||||
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, LaunchPositionRequest> RequestLaunchPosition;
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowListRequest> RequestWindowList;
|
||||
|
||||
// Raised when the page wants the monarch to open (or summon) a
|
||||
// named window in-process, restoring its persisted workspace if
|
||||
// one exists. No new wt.exe process is spawned.
|
||||
event Windows.Foundation.TypedEventHandler<Object, OpenWindowRequestedArgs> RequestOpenWindow;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowRequestedArgs> RequestNewWindow;
|
||||
}
|
||||
|
||||
@@ -258,6 +258,15 @@ namespace winrt::TerminalApp::implementation
|
||||
AppLogic::Current()->NotifyRootInitialized();
|
||||
}
|
||||
|
||||
WindowLayout TerminalWindow::GetWindowLayout() const
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
return _root->GetWindowLayout();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TerminalWindow::PersistState()
|
||||
{
|
||||
if (_root)
|
||||
@@ -1112,6 +1121,11 @@ namespace winrt::TerminalApp::implementation
|
||||
_initialContentArgs = wil::to_vector(actions);
|
||||
}
|
||||
|
||||
void TerminalWindow::SetPersistedLayout(const winrt::Microsoft::Terminal::Settings::Model::WindowLayout& layout)
|
||||
{
|
||||
_cachedLayout = layout;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Parse the provided commandline arguments into actions, and try to
|
||||
// perform them immediately.
|
||||
@@ -1232,7 +1246,14 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalWindow::WindowName(const winrt::hstring& name)
|
||||
{
|
||||
const auto oldIsQuakeMode = _WindowProperties->IsQuakeWindow();
|
||||
const auto oldName = _WindowProperties->WindowName();
|
||||
_WindowProperties->WindowName(name);
|
||||
// If this window had a persisted workspace under the old name, rename
|
||||
// that entry too so we don't leave a stale copy behind.
|
||||
if (!oldName.empty() && !name.empty() && oldName != name)
|
||||
{
|
||||
ApplicationState::SharedInstance().RenameWorkspace(oldName, name);
|
||||
}
|
||||
if (!_root)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void Create();
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout() const;
|
||||
void PersistState();
|
||||
|
||||
void UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args);
|
||||
@@ -80,6 +81,7 @@ namespace winrt::TerminalApp::implementation
|
||||
int32_t SetStartupCommandline(TerminalApp::CommandlineArgs args);
|
||||
void SetStartupContent(const winrt::hstring& content, const Windows::Foundation::IReference<Windows::Foundation::Rect>& contentBounds);
|
||||
void SetStartupActions(const Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
|
||||
void SetPersistedLayout(const winrt::Microsoft::Terminal::Settings::Model::WindowLayout& layout);
|
||||
int32_t ExecuteCommandline(TerminalApp::CommandlineArgs args);
|
||||
void SetSettingsStartupArgs(const std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
|
||||
|
||||
@@ -223,6 +225,7 @@ namespace winrt::TerminalApp::implementation
|
||||
FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress);
|
||||
FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested);
|
||||
FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested);
|
||||
FORWARDED_TYPED_EVENT(SummonWindowByIdRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::SummonWindowByIdRequestedArgs, _root, SummonWindowByIdRequested);
|
||||
FORWARDED_TYPED_EVENT(FocusTabRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::Tab, _root, FocusTabRequested);
|
||||
FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu);
|
||||
FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested);
|
||||
@@ -232,6 +235,8 @@ namespace winrt::TerminalApp::implementation
|
||||
FORWARDED_TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs, _root, RequestReceiveContent);
|
||||
|
||||
FORWARDED_TYPED_EVENT(RequestLaunchPosition, Windows::Foundation::IInspectable, winrt::TerminalApp::LaunchPositionRequest, _root, RequestLaunchPosition);
|
||||
FORWARDED_TYPED_EVENT(RequestWindowList, Windows::Foundation::IInspectable, winrt::TerminalApp::WindowListRequest, _root, RequestWindowList);
|
||||
FORWARDED_TYPED_EVENT(RequestOpenWindow, Windows::Foundation::IInspectable, winrt::TerminalApp::OpenWindowRequestedArgs, _root, RequestOpenWindow);
|
||||
FORWARDED_TYPED_EVENT(RequestNewWindow, Windows::Foundation::IInspectable, winrt::TerminalApp::WindowRequestedArgs, _root, RequestNewWindow);
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
|
||||
@@ -57,11 +57,13 @@ namespace TerminalApp
|
||||
Int32 SetStartupCommandline(CommandlineArgs args);
|
||||
void SetStartupContent(String json, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
|
||||
void SetStartupActions(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.ActionAndArgs> actions);
|
||||
void SetPersistedLayout(Microsoft.Terminal.Settings.Model.WindowLayout layout);
|
||||
Int32 ExecuteCommandline(CommandlineArgs args);
|
||||
|
||||
Boolean ShouldImmediatelyHandoffToElevated();
|
||||
void HandoffToElevated();
|
||||
|
||||
Microsoft.Terminal.Settings.Model.WindowLayout GetWindowLayout();
|
||||
void PersistState();
|
||||
|
||||
Windows.UI.Xaml.UIElement GetRoot();
|
||||
@@ -129,6 +131,7 @@ namespace TerminalApp
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, SummonWindowByIdRequestedArgs> SummonWindowByIdRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, TerminalApp.Tab> FocusTabRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
|
||||
@@ -141,6 +144,8 @@ namespace TerminalApp
|
||||
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LaunchPositionRequest> RequestLaunchPosition;
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowListRequest> RequestWindowList;
|
||||
event Windows.Foundation.TypedEventHandler<Object, OpenWindowRequestedArgs> RequestOpenWindow;
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowRequestedArgs> RequestNewWindow;
|
||||
|
||||
void AttachContent(String content, UInt32 tabIndex);
|
||||
|
||||
@@ -73,6 +73,8 @@ static constexpr std::string_view NewWindowKey{ "newWindow" };
|
||||
static constexpr std::string_view IdentifyWindowKey{ "identifyWindow" };
|
||||
static constexpr std::string_view IdentifyWindowsKey{ "identifyWindows" };
|
||||
static constexpr std::string_view RenameWindowKey{ "renameWindow" };
|
||||
static constexpr std::string_view OpenWorkspaceKey{ "openWorkspace" };
|
||||
|
||||
static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" };
|
||||
static constexpr std::string_view DisplayWorkingDirectoryKey{ "debugTerminalCwd" };
|
||||
static constexpr std::string_view SearchForTextKey{ "searchWeb" };
|
||||
@@ -101,6 +103,7 @@ static constexpr std::string_view OpenScratchpadKey{ "experimental.openScratchpa
|
||||
static constexpr std::string_view OpenAboutKey{ "openAbout" };
|
||||
static constexpr std::string_view QuickFixKey{ "quickFix" };
|
||||
static constexpr std::string_view OpenCWDKey{ "openCWD" };
|
||||
static constexpr std::string_view WorkspacesKey{ "workspaces" };
|
||||
|
||||
static constexpr std::string_view ActionKey{ "action" };
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "PrevTabArgs.g.cpp"
|
||||
#include "NextTabArgs.g.cpp"
|
||||
#include "RenameWindowArgs.g.cpp"
|
||||
#include "OpenWorkspaceArgs.g.cpp"
|
||||
#include "SearchForTextArgs.g.cpp"
|
||||
#include "GlobalSummonArgs.g.cpp"
|
||||
#include "FocusPaneArgs.g.cpp"
|
||||
@@ -795,6 +796,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return RS_switchable_(L"ResetWindowNameCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring OpenWorkspaceArgs::GenerateName(const winrt::WARC::ResourceContext& context) const
|
||||
{
|
||||
if (!Name().empty())
|
||||
{
|
||||
return winrt::hstring{ RS_switchable_fmt(L"OpenWorkspaceCommandKey", Name()) };
|
||||
}
|
||||
return RS_switchable_(L"OpenWorkspaceDefaultCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring SearchForTextArgs::GenerateName(const winrt::WARC::ResourceContext& context) const
|
||||
{
|
||||
if (QueryUrl().empty())
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "PrevTabArgs.g.h"
|
||||
#include "NextTabArgs.g.h"
|
||||
#include "RenameWindowArgs.g.h"
|
||||
#include "OpenWorkspaceArgs.g.h"
|
||||
#include "SearchForTextArgs.g.h"
|
||||
#include "GlobalSummonArgs.g.h"
|
||||
#include "FocusPaneArgs.g.h"
|
||||
@@ -246,6 +247,10 @@ protected: \
|
||||
#define RENAME_WINDOW_ARGS(X) \
|
||||
X(winrt::hstring, Name, "name", false, ArgTypeHint::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define OPEN_WORKSPACE_ARGS(X) \
|
||||
X(winrt::hstring, Name, "name", false, ArgTypeHint::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SEARCH_FOR_TEXT_ARGS(X) \
|
||||
X(winrt::hstring, QueryUrl, "queryUrl", false, ArgTypeHint::None, L"")
|
||||
@@ -940,6 +945,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
ACTION_ARGS_STRUCT(RenameWindowArgs, RENAME_WINDOW_ARGS);
|
||||
|
||||
ACTION_ARGS_STRUCT(OpenWorkspaceArgs, OPEN_WORKSPACE_ARGS);
|
||||
|
||||
ACTION_ARGS_STRUCT(SearchForTextArgs, SEARCH_FOR_TEXT_ARGS);
|
||||
|
||||
struct GlobalSummonArgs : public GlobalSummonArgsT<GlobalSummonArgs>
|
||||
@@ -1059,6 +1066,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
BASIC_FACTORY(SetMaximizedArgs);
|
||||
BASIC_FACTORY(SetColorSchemeArgs);
|
||||
BASIC_FACTORY(RenameWindowArgs);
|
||||
BASIC_FACTORY(OpenWorkspaceArgs);
|
||||
BASIC_FACTORY(ExecuteCommandlineArgs);
|
||||
BASIC_FACTORY(CloseOtherTabsArgs);
|
||||
BASIC_FACTORY(CloseTabsAfterArgs);
|
||||
|
||||
@@ -420,6 +420,12 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
String Name { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass OpenWorkspaceArgs : IActionArgs, IActionArgsDescriptorAccess
|
||||
{
|
||||
OpenWorkspaceArgs(String name);
|
||||
String Name { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass SearchForTextArgs : IActionArgs, IActionArgsDescriptorAccess
|
||||
{
|
||||
String QueryUrl { get; };
|
||||
|
||||
@@ -116,6 +116,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{ ShortcutAction::OpenTabColorPicker, USES_RESOURCE(L"OpenTabColorPickerCommandKey") },
|
||||
{ ShortcutAction::OpenTabRenamer, USES_RESOURCE(L"OpenTabRenamerCommandKey") },
|
||||
{ ShortcutAction::OpenWindowRenamer, USES_RESOURCE(L"OpenWindowRenamerCommandKey") },
|
||||
{ ShortcutAction::OpenWorkspace, USES_RESOURCE(L"OpenWorkspaceDefaultCommandKey") },
|
||||
{ ShortcutAction::PasteText, USES_RESOURCE(L"PasteTextCommandKey") },
|
||||
{ ShortcutAction::PrevTab, USES_RESOURCE(L"PrevTabCommandKey") },
|
||||
{ ShortcutAction::QuickFix, USES_RESOURCE(L"QuickFixCommandKey") },
|
||||
@@ -162,6 +163,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{ ShortcutAction::TogglePaneZoom, USES_RESOURCE(L"TogglePaneZoomCommandKey") },
|
||||
{ ShortcutAction::ToggleShaderEffects, USES_RESOURCE(L"ToggleShaderEffectsCommandKey") },
|
||||
{ ShortcutAction::ToggleSplitOrientation, USES_RESOURCE(L"ToggleSplitOrientationCommandKey") },
|
||||
{ ShortcutAction::Workspaces, USES_RESOURCE(L"WorkspacesCommandKey") },
|
||||
};
|
||||
}();
|
||||
|
||||
@@ -225,6 +227,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return winrt::make<NextTabArgs>();
|
||||
case Model::ShortcutAction::OpenSettings:
|
||||
return winrt::make<OpenSettingsArgs>();
|
||||
case Model::ShortcutAction::OpenWorkspace:
|
||||
return winrt::make<OpenWorkspaceArgs>();
|
||||
case Model::ShortcutAction::SetFocusMode:
|
||||
return winrt::make<SetFocusModeArgs>();
|
||||
case Model::ShortcutAction::SetFullScreen:
|
||||
|
||||
@@ -113,7 +113,9 @@
|
||||
ON_ALL_ACTIONS(OpenScratchpad) \
|
||||
ON_ALL_ACTIONS(OpenAbout) \
|
||||
ON_ALL_ACTIONS(QuickFix) \
|
||||
ON_ALL_ACTIONS(OpenCWD)
|
||||
ON_ALL_ACTIONS(OpenCWD) \
|
||||
ON_ALL_ACTIONS(OpenWorkspace) \
|
||||
ON_ALL_ACTIONS(Workspaces)
|
||||
|
||||
#define ALL_SHORTCUT_ACTIONS_WITH_ARGS \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \
|
||||
@@ -158,7 +160,8 @@
|
||||
ON_ALL_ACTIONS_WITH_ARGS(Suggestions) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(SelectCommand) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(SelectOutput) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(ColorSelection)
|
||||
ON_ALL_ACTIONS_WITH_ARGS(ColorSelection) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(OpenWorkspace)
|
||||
|
||||
// These two macros here are for actions that we only use as internal currency.
|
||||
// They don't need to be parsed by the settings model, or saved as actions to
|
||||
|
||||
@@ -253,6 +253,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return *state;
|
||||
}
|
||||
|
||||
// Need the COMMA macro hack for IMap template arguments in the macros
|
||||
#define COMMA ,
|
||||
|
||||
// Method Description:
|
||||
// - Loads data from the given json blob. Will only read the data that's in
|
||||
// the specified parseSource - so if we're reading the Local state file,
|
||||
@@ -302,6 +305,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return root;
|
||||
}
|
||||
|
||||
#undef COMMA
|
||||
|
||||
void ApplicationState::AppendPersistedWindowLayout(Model::WindowLayout layout)
|
||||
{
|
||||
{
|
||||
@@ -341,6 +346,120 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
void ApplicationState::SaveWorkspace(const hstring& name, const Model::WindowLayout& layout)
|
||||
{
|
||||
{
|
||||
const auto state = _state.lock();
|
||||
if (!state->PersistedWorkspaces || !*state->PersistedWorkspaces)
|
||||
{
|
||||
state->PersistedWorkspaces = winrt::single_threaded_map<hstring, Model::WindowLayout>();
|
||||
}
|
||||
(*state->PersistedWorkspaces).Insert(name, layout);
|
||||
}
|
||||
_throttler();
|
||||
}
|
||||
|
||||
bool ApplicationState::RemoveWorkspace(const hstring& name)
|
||||
{
|
||||
bool removed{ false };
|
||||
{
|
||||
const auto state = _state.lock();
|
||||
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
|
||||
{
|
||||
auto map = *state->PersistedWorkspaces;
|
||||
if (map.HasKey(name))
|
||||
{
|
||||
map.Remove(name);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removed)
|
||||
{
|
||||
_throttler();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Rename a persisted workspace entry from oldName to newName. If there
|
||||
// was no entry for oldName, this is a no-op. If an entry for newName
|
||||
// already exists, it will be overwritten with the layout from oldName.
|
||||
// - If newName is empty, the entry under oldName is simply removed (the
|
||||
// old name no longer points at a valid window, so the persisted layout
|
||||
// would otherwise be left stranded).
|
||||
// Return Value:
|
||||
// - true if the persisted state was modified, false otherwise.
|
||||
bool ApplicationState::RenameWorkspace(const hstring& oldName, const hstring& newName)
|
||||
{
|
||||
if (oldName == newName || oldName.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed{ false };
|
||||
{
|
||||
const auto state = _state.lock();
|
||||
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
|
||||
{
|
||||
auto map = *state->PersistedWorkspaces;
|
||||
if (map.HasKey(oldName))
|
||||
{
|
||||
if (!newName.empty())
|
||||
{
|
||||
const auto layout = map.Lookup(oldName);
|
||||
map.Insert(newName, layout);
|
||||
}
|
||||
map.Remove(oldName);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
_throttler();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Atomically remove and return a persisted workspace entry. This is the
|
||||
// intended API for the startup path that restores a named workspace,
|
||||
// because it guarantees only one caller can claim a given workspace.
|
||||
// Return Value:
|
||||
// - The layout that was stored under `name`, or nullptr if there was none.
|
||||
Model::WindowLayout ApplicationState::TakeWorkspace(const hstring& name)
|
||||
{
|
||||
Model::WindowLayout result{ nullptr };
|
||||
{
|
||||
const auto state = _state.lock();
|
||||
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
|
||||
{
|
||||
auto map = *state->PersistedWorkspaces;
|
||||
if (map.HasKey(name))
|
||||
{
|
||||
result = map.Lookup(name);
|
||||
map.Remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
_throttler();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IMapView<hstring, Model::WindowLayout> ApplicationState::AllPersistedWorkspaces()
|
||||
{
|
||||
const auto state = _state.lock_shared();
|
||||
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
|
||||
{
|
||||
return (*state->PersistedWorkspaces).GetView();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type ApplicationState::name() const noexcept \
|
||||
@@ -359,7 +478,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
\
|
||||
_throttler(); \
|
||||
}
|
||||
#define COMMA ,
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef COMMA
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -16,7 +16,7 @@ Abstract:
|
||||
#include "WindowLayout.g.h"
|
||||
|
||||
#include <inc/cppwinrt_utils.h>
|
||||
#include <JsonUtils.h>
|
||||
#include "JsonUtils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
@@ -34,6 +34,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// This macro generates all getters and setters for ApplicationState.
|
||||
// It provides X with the following arguments:
|
||||
// (source, type, function name, JSON key, ...variadic construction arguments)
|
||||
// Note: we're using the COMMA macro hack for the template arguments as used elsewhere.
|
||||
#define MTSM_APPLICATION_STATE_FIELDS(X) \
|
||||
X(FileSource::Shared, winrt::hstring, SettingsHash, "settingsHash") \
|
||||
X(FileSource::Shared, std::unordered_set<winrt::guid>, GeneratedProfiles, "generatedProfiles") \
|
||||
@@ -42,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines") \
|
||||
X(FileSource::Local, std::unordered_set<hstring>, DismissedBadges, "dismissedBadges") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IMap<hstring COMMA Model::WindowLayout>, PersistedWorkspaces, "persistedWorkspaces") \
|
||||
X(FileSource::Shared, bool, SSHFolderGenerated, "sshFolderGenerated", false)
|
||||
|
||||
struct WindowLayout : WindowLayoutT<WindowLayout>
|
||||
@@ -57,6 +59,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<Model::WindowLayout>;
|
||||
};
|
||||
|
||||
#define COMMA ,
|
||||
|
||||
struct ApplicationState : public ApplicationStateT<ApplicationState>
|
||||
{
|
||||
static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance();
|
||||
@@ -75,6 +79,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
bool DismissBadge(const hstring& badgeId);
|
||||
bool BadgeDismissed(const hstring& badgeId) const;
|
||||
|
||||
void SaveWorkspace(const hstring& name, const Model::WindowLayout& layout);
|
||||
bool RemoveWorkspace(const hstring& name);
|
||||
bool RenameWorkspace(const hstring& oldName, const hstring& newName);
|
||||
Model::WindowLayout TakeWorkspace(const hstring& name);
|
||||
Windows::Foundation::Collections::IMapView<hstring, Model::WindowLayout> AllPersistedWorkspaces();
|
||||
|
||||
// State getters/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type name() const noexcept; \
|
||||
@@ -104,6 +114,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
std::string _readLocalContents() const;
|
||||
void _writeLocalContents(const std::string_view content) const;
|
||||
};
|
||||
#undef COMMA
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Boolean DismissBadge(String badgeId);
|
||||
Boolean BadgeDismissed(String badgeId);
|
||||
|
||||
void SaveWorkspace(String name, WindowLayout layout);
|
||||
Boolean RemoveWorkspace(String name);
|
||||
Boolean RenameWorkspace(String oldName, String newName);
|
||||
WindowLayout TakeWorkspace(String name);
|
||||
Windows.Foundation.Collections.IMapView<String, WindowLayout> AllPersistedWorkspaces();
|
||||
|
||||
String SettingsHash;
|
||||
Windows.Foundation.Collections.IVector<WindowLayout> PersistedWindowLayouts;
|
||||
Windows.Foundation.Collections.IVector<String> RecentCommands;
|
||||
|
||||
@@ -162,7 +162,8 @@ Author(s):
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Frame, "frame", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedFrame, "unfocusedFrame", nullptr) \
|
||||
X(bool, RainbowFrame, "experimental.rainbowFrame", false) \
|
||||
X(bool, UseMica, "useMica", false)
|
||||
X(bool, UseMica, "useMica", false) \
|
||||
X(bool, ShowWorkspacesButton, "showWorkspacesButton", true)
|
||||
|
||||
#define MTSM_THEME_SETTINGS_SETTINGS(X) \
|
||||
X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "theme", winrt::Windows::UI::Xaml::ElementTheme::Default)
|
||||
|
||||
@@ -518,6 +518,13 @@
|
||||
<data name="ResetWindowNameCommandKey" xml:space="preserve">
|
||||
<value>Reset window name</value>
|
||||
</data>
|
||||
<data name="OpenWorkspaceCommandKey" xml:space="preserve">
|
||||
<value>Open workspace "{0}"</value>
|
||||
<comment>{0} will be replaced with the workspace name</comment>
|
||||
</data>
|
||||
<data name="OpenWorkspaceDefaultCommandKey" xml:space="preserve">
|
||||
<value>Open workspace</value>
|
||||
</data>
|
||||
<data name="OpenWindowRenamerCommandKey" xml:space="preserve">
|
||||
<value>Rename window...</value>
|
||||
</data>
|
||||
@@ -740,6 +747,9 @@
|
||||
<data name="OpenCWDCommandKey" xml:space="preserve">
|
||||
<value>Open current working directory</value>
|
||||
</data>
|
||||
<data name="WorkspacesCommandKey" xml:space="preserve">
|
||||
<value>Workspaces...</value>
|
||||
</data>
|
||||
<data name="CloseTab" xml:space="preserve">
|
||||
<value>Close tab</value>
|
||||
</data>
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Windows.UI.Xaml.ElementTheme RequestedTheme { get; };
|
||||
Boolean UseMica { get; };
|
||||
Boolean RainbowFrame { get; };
|
||||
Boolean ShowWorkspacesButton { get; };
|
||||
ThemeColor Frame { get; };
|
||||
ThemeColor UnfocusedFrame { get; };
|
||||
}
|
||||
|
||||
@@ -542,6 +542,7 @@
|
||||
{ "command": "quickFix", "id": "Terminal.QuickFix" },
|
||||
{ "command": { "action": "showSuggestions", "source": "all"}, "id": "Terminal.Suggestions" },
|
||||
{ "command": "openCWD", "id": "Terminal.OpenCWD" },
|
||||
{ "command": "workspaces", "id": "Terminal.Workspaces" },
|
||||
|
||||
// Tab Management
|
||||
// "command": "closeTab" is unbound by default.
|
||||
|
||||
138
src/cascadia/UnitTests_SettingsModel/ApplicationStateTests.cpp
Normal file
138
src/cascadia/UnitTests_SettingsModel/ApplicationStateTests.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalSettingsModel/ApplicationState.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace SettingsModelUnitTests
|
||||
{
|
||||
// Covers the workspace-persistence APIs added to ApplicationState:
|
||||
// SaveWorkspace / RemoveWorkspace / RenameWorkspace / TakeWorkspace /
|
||||
// AllPersistedWorkspaces.
|
||||
// All tests operate on a throw-away ApplicationState instance pointed at
|
||||
// a temp directory, so they don't touch the real user state.
|
||||
class ApplicationStateTests
|
||||
{
|
||||
TEST_CLASS(ApplicationStateTests);
|
||||
|
||||
TEST_METHOD(SaveAndLookupWorkspace);
|
||||
TEST_METHOD(RemoveWorkspaceReturnsFalseWhenMissing);
|
||||
TEST_METHOD(RenameWorkspaceMigratesEntry);
|
||||
TEST_METHOD(RenameWorkspaceNoOpForEmptyOrEqualNames);
|
||||
TEST_METHOD(RenameWorkspaceNoOpForMissingEntry);
|
||||
TEST_METHOD(TakeWorkspaceRemovesAndReturns);
|
||||
TEST_METHOD(TakeWorkspaceReturnsNullWhenMissing);
|
||||
|
||||
private:
|
||||
static std::filesystem::path _tempRoot()
|
||||
{
|
||||
auto root = std::filesystem::temp_directory_path() / L"WT_ApplicationStateTests";
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(root, ec);
|
||||
// Best-effort clean of any leftover state.json from a prior run so
|
||||
// tests see an empty starting point.
|
||||
std::filesystem::remove(root / L"state.json", ec);
|
||||
std::filesystem::remove(root / L"elevated-state.json", ec);
|
||||
return root;
|
||||
}
|
||||
|
||||
static winrt::com_ptr<implementation::ApplicationState> _make()
|
||||
{
|
||||
return winrt::make_self<implementation::ApplicationState>(_tempRoot());
|
||||
}
|
||||
|
||||
static WindowLayout _makeLayout()
|
||||
{
|
||||
WindowLayout layout;
|
||||
layout.TabLayout(winrt::single_threaded_vector<ActionAndArgs>());
|
||||
return layout;
|
||||
}
|
||||
};
|
||||
|
||||
void ApplicationStateTests::SaveAndLookupWorkspace()
|
||||
{
|
||||
auto state = _make();
|
||||
const auto layout = _makeLayout();
|
||||
state->SaveWorkspace(L"win1", layout);
|
||||
|
||||
const auto all = state->AllPersistedWorkspaces();
|
||||
VERIFY_IS_NOT_NULL(all);
|
||||
VERIFY_IS_TRUE(all.HasKey(L"win1"));
|
||||
}
|
||||
|
||||
void ApplicationStateTests::RemoveWorkspaceReturnsFalseWhenMissing()
|
||||
{
|
||||
auto state = _make();
|
||||
VERIFY_IS_FALSE(state->RemoveWorkspace(L"does-not-exist"));
|
||||
|
||||
state->SaveWorkspace(L"win1", _makeLayout());
|
||||
VERIFY_IS_TRUE(state->RemoveWorkspace(L"win1"));
|
||||
VERIFY_IS_FALSE(state->RemoveWorkspace(L"win1"));
|
||||
}
|
||||
|
||||
void ApplicationStateTests::RenameWorkspaceMigratesEntry()
|
||||
{
|
||||
auto state = _make();
|
||||
state->SaveWorkspace(L"oldName", _makeLayout());
|
||||
|
||||
VERIFY_IS_TRUE(state->RenameWorkspace(L"oldName", L"newName"));
|
||||
|
||||
const auto all = state->AllPersistedWorkspaces();
|
||||
VERIFY_IS_NOT_NULL(all);
|
||||
VERIFY_IS_FALSE(all.HasKey(L"oldName"));
|
||||
VERIFY_IS_TRUE(all.HasKey(L"newName"));
|
||||
}
|
||||
|
||||
void ApplicationStateTests::RenameWorkspaceNoOpForEmptyOrEqualNames()
|
||||
{
|
||||
auto state = _make();
|
||||
state->SaveWorkspace(L"win1", _makeLayout());
|
||||
|
||||
VERIFY_IS_FALSE(state->RenameWorkspace(L"win1", L"win1"));
|
||||
VERIFY_IS_FALSE(state->RenameWorkspace(L"", L"win2"));
|
||||
|
||||
// Renaming to an empty name removes the stale entry under the old name.
|
||||
VERIFY_IS_TRUE(state->RenameWorkspace(L"win1", L""));
|
||||
const auto all = state->AllPersistedWorkspaces();
|
||||
if (all)
|
||||
{
|
||||
VERIFY_IS_FALSE(all.HasKey(L"win1"));
|
||||
VERIFY_IS_FALSE(all.HasKey(L""));
|
||||
}
|
||||
|
||||
// Calling again is now a no-op because the entry is gone.
|
||||
VERIFY_IS_FALSE(state->RenameWorkspace(L"win1", L""));
|
||||
}
|
||||
|
||||
void ApplicationStateTests::RenameWorkspaceNoOpForMissingEntry()
|
||||
{
|
||||
auto state = _make();
|
||||
VERIFY_IS_FALSE(state->RenameWorkspace(L"missing", L"newName"));
|
||||
}
|
||||
|
||||
void ApplicationStateTests::TakeWorkspaceRemovesAndReturns()
|
||||
{
|
||||
auto state = _make();
|
||||
state->SaveWorkspace(L"win1", _makeLayout());
|
||||
|
||||
const auto taken = state->TakeWorkspace(L"win1");
|
||||
VERIFY_IS_NOT_NULL(taken);
|
||||
|
||||
// Subsequent Take for the same name must return null — this is the
|
||||
// atomicity guarantee the startup path relies on.
|
||||
VERIFY_IS_NULL(state->TakeWorkspace(L"win1"));
|
||||
}
|
||||
|
||||
void ApplicationStateTests::TakeWorkspaceReturnsNullWhenMissing()
|
||||
{
|
||||
auto state = _make();
|
||||
VERIFY_IS_NULL(state->TakeWorkspace(L"missing"));
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@
|
||||
<ClCompile Include="TerminalSettingsTests.cpp" />
|
||||
<ClCompile Include="ThemeTests.cpp" />
|
||||
<ClCompile Include="MediaResourceTests.cpp" />
|
||||
<ClCompile Include="ApplicationStateTests.cpp" />
|
||||
<ClCompile Include="../TerminalSettingsAppAdapterLib/TerminalSettings.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
|
||||
@@ -66,6 +66,8 @@ Author(s):
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
#include <til/winrt.h>
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
|
||||
// Common includes for most tests:
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
@@ -132,7 +132,12 @@ void AppHost::_HandleCommandlineArgs(const winrt::TerminalApp::WindowRequestedAr
|
||||
// We don't have XAML yet, but we do have other stuff.
|
||||
_windowLogic = _appLogic.CreateNewWindow();
|
||||
|
||||
if (const auto content = windowArgs.Content(); !content.empty())
|
||||
if (const auto layout = windowArgs.PersistedLayout())
|
||||
{
|
||||
_windowLogic.SetPersistedLayout(layout);
|
||||
_launchShowWindowCommand = SW_NORMAL;
|
||||
}
|
||||
else if (const auto content = windowArgs.Content(); !content.empty())
|
||||
{
|
||||
_windowLogic.SetStartupContent(content, windowArgs.InitialBounds());
|
||||
_launchShowWindowCommand = SW_NORMAL;
|
||||
@@ -270,12 +275,15 @@ void AppHost::Initialize()
|
||||
|
||||
_revokers.IsQuakeWindowChanged = _windowLogic.IsQuakeWindowChanged(winrt::auto_revoke, { this, &AppHost::_IsQuakeWindowChanged });
|
||||
_revokers.SummonWindowRequested = _windowLogic.SummonWindowRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowRequested });
|
||||
_revokers.SummonWindowByIdRequested = _windowLogic.SummonWindowByIdRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowByIdRequested });
|
||||
_revokers.FocusTabRequested = _windowLogic.FocusTabRequested(winrt::auto_revoke, { this, &AppHost::_FocusTabRequested });
|
||||
_revokers.OpenSystemMenu = _windowLogic.OpenSystemMenu(winrt::auto_revoke, { this, &AppHost::_OpenSystemMenu });
|
||||
_revokers.QuitRequested = _windowLogic.QuitRequested(winrt::auto_revoke, { this, &AppHost::_RequestQuitAll });
|
||||
_revokers.ShowWindowChanged = _windowLogic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged });
|
||||
_revokers.RequestMoveContent = _windowLogic.RequestMoveContent(winrt::auto_revoke, { this, &AppHost::_handleMoveContent });
|
||||
_revokers.RequestReceiveContent = _windowLogic.RequestReceiveContent(winrt::auto_revoke, { this, &AppHost::_handleReceiveContent });
|
||||
_revokers.RequestWindowList = _windowLogic.RequestWindowList(winrt::auto_revoke, { this, &AppHost::_HandleRequestWindowList });
|
||||
_revokers.RequestOpenWindow = _windowLogic.RequestOpenWindow(winrt::auto_revoke, { this, &AppHost::_HandleOpenWindowRequested });
|
||||
_revokers.RequestNewWindow = _windowLogic.RequestNewWindow(winrt::auto_revoke, { this, &AppHost::_HandleNewWindowRequested });
|
||||
|
||||
// BODGY
|
||||
@@ -416,6 +424,40 @@ void AppHost::_HandleRequestLaunchPosition(const winrt::Windows::Foundation::IIn
|
||||
args.Position(_GetWindowLaunchPosition());
|
||||
}
|
||||
|
||||
void AppHost::_HandleRequestWindowList(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
winrt::TerminalApp::WindowListRequest args)
|
||||
{
|
||||
// Ask the Emperor (on the main thread) for the current window list.
|
||||
// SendMessage blocks until the message is processed, so this is
|
||||
// synchronous and the results vector is filled in-place.
|
||||
std::vector<WindowEmperor::WindowListEntry> entries;
|
||||
SendMessage(_windowManager->GetMainWindow(),
|
||||
WindowEmperor::WM_GET_WINDOW_LIST,
|
||||
0,
|
||||
reinterpret_cast<LPARAM>(&entries));
|
||||
|
||||
auto windowEntries = args.Entries();
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
winrt::TerminalApp::WindowListEntry w;
|
||||
w.Id(entry.Id);
|
||||
w.Name(winrt::hstring{ entry.Name });
|
||||
windowEntries.Append(w);
|
||||
}
|
||||
}
|
||||
|
||||
// In-process replacement for the old `ShellExecute("wt -w ...")` dance.
|
||||
// Asks the WindowEmperor to summon a named window or restore its persisted
|
||||
// workspace, without launching a second wt.exe.
|
||||
void AppHost::_HandleOpenWindowRequested(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::TerminalApp::OpenWindowRequestedArgs& args)
|
||||
{
|
||||
if (_windowManager && args)
|
||||
{
|
||||
_windowManager->OpenWindow(args.Name());
|
||||
}
|
||||
}
|
||||
|
||||
// In-process replacement for the old `ShellExecute("wt -w -1 new-tab ...")`
|
||||
// dance. The page hands us a pre-built WindowRequestedArgs (with its
|
||||
// StartupActions already populated); we just forward it to the WindowEmperor.
|
||||
@@ -1083,6 +1125,23 @@ void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspecta
|
||||
HandleSummon(std::move(summonArgs));
|
||||
}
|
||||
|
||||
void AppHost::_SummonWindowByIdRequested(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::TerminalApp::SummonWindowByIdRequestedArgs& args)
|
||||
{
|
||||
// Summon the window by its ID without creating a new tab.
|
||||
// We look up the target window in WindowEmperor and call HandleSummon directly.
|
||||
const auto targetId = args.WindowId();
|
||||
if (auto* targetWindow = _windowManager->GetWindowById(targetId))
|
||||
{
|
||||
winrt::TerminalApp::SummonWindowBehavior summonBehavior;
|
||||
summonBehavior.MoveToCurrentDesktop(false);
|
||||
summonBehavior.DropdownDuration(0);
|
||||
summonBehavior.ToMonitor(winrt::TerminalApp::MonitorBehavior::InPlace);
|
||||
summonBehavior.ToggleVisibility(false); // Do not toggle, just make visible.
|
||||
targetWindow->HandleSummon(std::move(summonBehavior));
|
||||
}
|
||||
}
|
||||
|
||||
void AppHost::_FocusTabRequested(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::TerminalApp::Tab& tab)
|
||||
{
|
||||
|
||||
@@ -91,12 +91,18 @@ private:
|
||||
void _SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
|
||||
void _SummonWindowByIdRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::TerminalApp::SummonWindowByIdRequestedArgs& args);
|
||||
|
||||
void _FocusTabRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::TerminalApp::Tab& tab);
|
||||
|
||||
void _OpenSystemMenu(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
|
||||
void _HandleOpenWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::TerminalApp::OpenWindowRequestedArgs& args);
|
||||
|
||||
void _HandleNewWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::TerminalApp::WindowRequestedArgs& args);
|
||||
|
||||
@@ -136,6 +142,8 @@ private:
|
||||
void _AppTitleChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&);
|
||||
void _HandleRequestLaunchPosition(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
winrt::TerminalApp::LaunchPositionRequest args);
|
||||
void _HandleRequestWindowList(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
winrt::TerminalApp::WindowListRequest args);
|
||||
|
||||
// Helper struct. By putting these all into one struct, we can revoke them
|
||||
// all at once, by assigning _revokers to a fresh Revokers instance. That'll
|
||||
@@ -157,6 +165,7 @@ private:
|
||||
winrt::TerminalApp::TerminalWindow::IdentifyWindowsRequested_revoker IdentifyWindowsRequested;
|
||||
winrt::TerminalApp::TerminalWindow::IsQuakeWindowChanged_revoker IsQuakeWindowChanged;
|
||||
winrt::TerminalApp::TerminalWindow::SummonWindowRequested_revoker SummonWindowRequested;
|
||||
winrt::TerminalApp::TerminalWindow::SummonWindowByIdRequested_revoker SummonWindowByIdRequested;
|
||||
winrt::TerminalApp::TerminalWindow::FocusTabRequested_revoker FocusTabRequested;
|
||||
winrt::TerminalApp::TerminalWindow::OpenSystemMenu_revoker OpenSystemMenu;
|
||||
winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested;
|
||||
@@ -165,6 +174,8 @@ private:
|
||||
winrt::TerminalApp::TerminalWindow::RequestReceiveContent_revoker RequestReceiveContent;
|
||||
winrt::TerminalApp::TerminalWindow::RequestLaunchPosition_revoker RequestLaunchPosition;
|
||||
winrt::TerminalApp::TerminalWindow::RequestNewWindow_revoker RequestNewWindow;
|
||||
winrt::TerminalApp::TerminalWindow::RequestWindowList_revoker RequestWindowList;
|
||||
winrt::TerminalApp::TerminalWindow::RequestOpenWindow_revoker RequestOpenWindow;
|
||||
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
|
||||
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
|
||||
winrt::TerminalApp::TerminalWindow::WindowSizeChanged_revoker WindowSizeChanged;
|
||||
|
||||
@@ -296,6 +296,60 @@ void WindowEmperor::CreateNewWindow(winrt::TerminalApp::WindowRequestedArgs args
|
||||
}
|
||||
}
|
||||
|
||||
// Public entry point used by in-process callers (e.g. AppHost reacting to a
|
||||
// TerminalPage RequestOpenWindow event) to open or summon a named window -
|
||||
// restoring its persisted workspace if one exists - without spawning a second
|
||||
// wt.exe. Bypasses the commandline parser entirely.
|
||||
void WindowEmperor::OpenWindow(const winrt::hstring& name)
|
||||
{
|
||||
_assertIsMainThread();
|
||||
|
||||
if (name.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If a window with this name is already live, just summon it.
|
||||
// This mirrors the summon behavior in AppHost::DispatchCommandline (which is
|
||||
// what the old `wt -w <name>` ShellExecute path effectively triggered).
|
||||
if (const auto window = GetWindowByName(name))
|
||||
{
|
||||
winrt::TerminalApp::SummonWindowBehavior summon{};
|
||||
summon.MoveToCurrentDesktop(false);
|
||||
summon.DropdownDuration(0);
|
||||
summon.ToMonitor(winrt::TerminalApp::MonitorBehavior::InPlace);
|
||||
summon.ToggleVisibility(false);
|
||||
window->HandleSummon(std::move(summon));
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, create a new window under that name. A default-constructed
|
||||
// CommandlineArgs is supplied as the launch fallback for the case where
|
||||
// no persisted workspace exists; AppHost ignores it when PersistedLayout
|
||||
// is set.
|
||||
_createWindowMaybeRestoringWorkspace(0, name, winrt::TerminalApp::CommandlineArgs{});
|
||||
}
|
||||
|
||||
// Shared tail used by both the commandline dispatch path and OpenWindow():
|
||||
// build a WindowRequestedArgs for a new window and, if the request carries a
|
||||
// name, atomically claim any persisted workspace stored under that name so
|
||||
// it's restored here and no subsequent caller can pick up the same entry.
|
||||
void WindowEmperor::_createWindowMaybeRestoringWorkspace(uint64_t windowId, const winrt::hstring& windowName, winrt::TerminalApp::CommandlineArgs args)
|
||||
{
|
||||
winrt::TerminalApp::WindowRequestedArgs request{ windowId, std::move(args) };
|
||||
request.WindowName(windowName);
|
||||
|
||||
if (!windowName.empty())
|
||||
{
|
||||
if (const auto layout = ApplicationState::SharedInstance().TakeWorkspace(windowName))
|
||||
{
|
||||
request.PersistedLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
CreateNewWindow(std::move(request));
|
||||
}
|
||||
|
||||
AppHost* WindowEmperor::_mostRecentWindow() const noexcept
|
||||
{
|
||||
int64_t max = INT64_MIN;
|
||||
@@ -801,9 +855,7 @@ void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs arg
|
||||
}
|
||||
else
|
||||
{
|
||||
winrt::TerminalApp::WindowRequestedArgs request{ windowId, std::move(args) };
|
||||
request.WindowName(std::move(windowName));
|
||||
CreateNewWindow(std::move(request));
|
||||
_createWindowMaybeRestoringWorkspace(windowId, windowName, std::move(args));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1087,6 +1139,22 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
|
||||
// anyway (since we threw and exited this message handler) so this at least gives back our
|
||||
// deterministic window count management.
|
||||
const auto strong = *it;
|
||||
|
||||
// Before destroying a named window, persist its full
|
||||
// tab/buffer state as a workspace so it can be restored later.
|
||||
try
|
||||
{
|
||||
const auto windowName = strong->Logic().WindowProperties().WindowName();
|
||||
if (!windowName.empty())
|
||||
{
|
||||
if (const auto layout = strong->Logic().GetWindowLayout())
|
||||
{
|
||||
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
_windows.erase(it);
|
||||
try
|
||||
{
|
||||
@@ -1114,6 +1182,19 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
|
||||
host->Logic().IdentifyWindow();
|
||||
}
|
||||
return 0;
|
||||
case WM_GET_WINDOW_LIST:
|
||||
{
|
||||
auto* result = reinterpret_cast<std::vector<WindowListEntry>*>(lParam);
|
||||
if (result)
|
||||
{
|
||||
for (const auto& host : _windows)
|
||||
{
|
||||
const auto props = host->Logic().WindowProperties();
|
||||
result->emplace_back(WindowListEntry{ props.WindowId(), std::wstring{ props.WindowName() } });
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case WM_NOTIFY_FROM_NOTIFICATION_AREA:
|
||||
switch (LOWORD(lParam))
|
||||
{
|
||||
|
||||
@@ -28,6 +28,16 @@ public:
|
||||
WM_MESSAGE_BOX_CLOSED,
|
||||
WM_IDENTIFY_ALL_WINDOWS,
|
||||
WM_NOTIFY_FROM_NOTIFICATION_AREA,
|
||||
WM_GET_WINDOW_LIST,
|
||||
};
|
||||
|
||||
// Used by WM_GET_WINDOW_LIST. Callers allocate a vector on their
|
||||
// stack and pass a pointer through LPARAM; the emperor fills it in
|
||||
// synchronously via SendMessage.
|
||||
struct WindowListEntry
|
||||
{
|
||||
uint64_t Id;
|
||||
std::wstring Name;
|
||||
};
|
||||
|
||||
HWND GetMainWindow() const noexcept;
|
||||
@@ -37,6 +47,8 @@ public:
|
||||
void CreateNewWindow(winrt::TerminalApp::WindowRequestedArgs args);
|
||||
void HandleCommandlineArgs(int nCmdShow);
|
||||
void FocusTabInAnyWindow(const winrt::TerminalApp::Tab& tab) const;
|
||||
// OpenWindow is used for opening a new window or summoning an existing window by name.
|
||||
void OpenWindow(const winrt::hstring& name);
|
||||
|
||||
private:
|
||||
struct SummonWindowSelectionArgs
|
||||
@@ -50,6 +62,7 @@ private:
|
||||
[[nodiscard]] static LRESULT __stdcall _wndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
|
||||
|
||||
AppHost* _mostRecentWindow() const noexcept;
|
||||
void _createWindowMaybeRestoringWorkspace(uint64_t windowId, const winrt::hstring& windowName, winrt::TerminalApp::CommandlineArgs args);
|
||||
bool _summonWindow(const SummonWindowSelectionArgs& args) const;
|
||||
void _summonAllWindows() const;
|
||||
void _dispatchSpecialKey(const MSG& msg) const;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace til
|
||||
{
|
||||
namespace details
|
||||
|
||||
Reference in New Issue
Block a user