Compare commits

..

17 Commits

Author SHA1 Message Date
Mike Griese
e5812164c8 Merge remote-tracking branch 'origin/main' into dev/migrie/workspaces-for-pr 2026-05-15 13:06:34 -05:00
aarushi singh
12e3455bb2 Make DECSTR cursor restore behave like xterm in alt screen buffer (#20032)
There is some disagreement among terminal emulators as to whether DECSTR
(and therefore, soft reset) restores the cursor in the active buffer or
in all buffers.

We had previously chosen to restore the cursor in all buffers.

xterm restores the cursor only in the active buffer.

Closes #19918
2026-05-14 17:04:58 +00:00
Mike Griese
b17589b5aa Merge remote-tracking branch 'origin/main' into dev/migrie/workspaces-for-pr 2026-05-14 10:26:44 -05:00
Lucas Trzesniewski
fb71a0462e Add safeUriSchemes setting (#20207)
This adds a `safeUriSchemes` global setting which lets you define
hyperlink URI schemes which the user considers safe. No confirmation
dialog will be shown when trying to open hyperlinks which use these
schemes.

- This solves the root issue, but doesn't introduce any UI or
  documentation changes. I wanted to validate the approach and
  implementation with you first.
- I closely followed the code handling the `disabledProfileSources`
  setting, which is of the same type.
- This feature does not change the behavior of `http`, `https` and
  `file` schemes.

Validation

I ran the dev terminal, and tested the behavior by clicking on `vscode`
hyperlinks generated by ripgrep with various `safeUriSchemes` settings:

- Setting not defined - asks for confirmation
- `["vscode"]` - does not ask for confirmation
- `["foo", "vscode"]` - does not ask for confirmation
- `["foo"]` - asks for confirmation
- `null` - asks for confirmation
- `[]` - asks for confirmation
- `[""]` - asks for confirmation
- `[{"foo": "bar"}]` - fails to deserialize (as expected)

A few uinit tests failed, but they seem unrelated to these changes:
- `KeyBindingTests` in `UnitTests_SettingsModel`, probably because I use
  an AZERTY keyboard.
- A few `Conhost` tests, but I didn't touch this part

Refs #20065
Closes #20191
2026-05-12 21:32:56 +00:00
Dustin L. Howett
c829d4ca54 sixel: prevent allocating an absurd amount of memory or writing OOB (#20213)
This commit implements two fixes for the integer overflow/out-of-bounds
write reported in #20149.

First, it catches any exception generated in sixel char processing
(which will prevent `out_of_memory` or `bad_alloc` from being ignored
sight-unseen, and will prevent the consumption of further DCS content).

Second, it prevents us from allocating memory for an image which will
never be displayed (because it exceeds the height of the display.)

This supersedes prior work in #20153 for the same issues.

Closes #20149
Closes #20153

Co-authored-by: James Holderness <j4_james@hotmail.com>
2026-05-12 18:15:19 +02:00
Dustin L. Howett
b991eb048e Only set startingTitle once, clear up title fallback handling (#20214)
This commit ensures that we only set the starting title once when we
open a new terminal pane.

It also consolidates all title selection into Terminal::GetConsoleTitle,
which is now used in the TitleChanged event.

TitleChanged no longer stores a separate copy of the starting title if
an application attempts to _clear_ the title; that seems like a poorer
implementation of what we already had.

This supersedes work in #20204.

Closes #19340
Closes #20204

Co-authored-by: imsh <im.shaedar@gmail.com>
2026-05-12 00:08:57 +00:00
Dustin L. Howett
3e3b3ad883 Reject DTTERM Window Manipulation resizes with the current size (#20183)
This may help #20182 and #20112
Closes #19310
2026-05-11 17:42:18 -05:00
Mike Griese
74b740cf0c format, for real 2026-05-08 06:40:40 -05:00
Mike Griese
afe42d456f format 2026-05-07 13:43:50 -05:00
Mike Griese
12e2daaeb9 actions are good for our keyboard inclined 2026-05-07 13:41:46 -05:00
Mike Griese
b9896f51e4 delete workspaces ; better padding 2026-05-07 12:55:16 -05:00
Mike Griese
79d681e394 focus the window renamer better 2026-05-06 10:32:34 -05:00
Mike Griese
00aa707825 don't crash when closing the last pane in a workspace 2026-05-06 10:32:11 -05:00
Mike Griese
d5d4f99673 fix ci 2026-05-05 13:33:12 -05:00
Mike Griese
7dd7783aff wle 2026-05-05 11:33:39 -05:00
Mike Griese
ee1601e405 Merge remote-tracking branch 'origin/main' into dev/migrie/workspaces-for-pr 2026-05-05 11:32:01 -05:00
Mike Griese
0c0a16e10a Add support for "workspaces" based on window names
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.
2026-04-28 14:32:46 -05:00
66 changed files with 1803 additions and 959 deletions

View File

@@ -479,6 +479,7 @@
"toggleReadOnlyMode",
"toggleShaderEffects",
"toggleSplitOrientation",
"workspaces",
"wt",
"unbound"
],
@@ -2040,6 +2041,11 @@
"unfocusedFrame": {
"description": "The color of the window frame when the window is inactive. This only works on Windows 11",
"$ref": "#/$defs/ThemeColor"
},
"showWindowsButton": {
"description": "When set to true, the workspace/windows button will be shown in the tab row.",
"type": "boolean",
"default": true
}
}
},
@@ -2472,6 +2478,13 @@
},
"type": "array"
},
"safeUriSchemes": {
"description": "Specifies a list of URI schemes that are considered safe. No confirmation will be required to open URIs with these schemes.",
"items": {
"type": "string"
},
"type": "array"
},
"rendering.graphicsAPI": {
"description": "Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. The default option \"Automatic\" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.",
"type": "string",

View File

@@ -938,6 +938,27 @@ namespace winrt::TerminalApp::implementation
co_return;
}
// Launch `wt -w <name>` so the monarch can either summon an existing
// window with that name or restore a persisted workspace.
safe_void_coroutine TerminalPage::_OpenWorkspaceWindow(const winrt::hstring name)
{
co_await winrt::resume_background();
const auto exePath{ GetWtExePath() };
const auto cmdline = fmt::format(FMT_COMPILE(L"-w {}"), std::wstring_view{ name });
SHELLEXECUTEINFOW seInfo{ 0 };
seInfo.cbSize = sizeof(seInfo);
seInfo.fMask = SEE_MASK_NOASYNC;
seInfo.lpVerb = L"open";
seInfo.lpFile = exePath.c_str();
seInfo.lpParameters = cmdline.c_str();
seInfo.nShow = SW_SHOWNORMAL;
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
co_return;
}
void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/,
const ActionEventArgs& actionArgs)
{
@@ -1058,6 +1079,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() })
{
@@ -1633,4 +1655,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);
}
}

View File

@@ -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);
}

View File

@@ -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);
};
}

View File

@@ -51,5 +51,6 @@ namespace TerminalApp
CommandlineArgs Command { get; };
String Content { get; };
Windows.Foundation.IReference<Windows.Foundation.Rect> InitialBounds { get; };
Microsoft.Terminal.Settings.Model.WindowLayout PersistedLayout;
};
}

View File

@@ -743,6 +743,38 @@
<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>{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>Are you sure you want to delete the workspace "{0}"?</value>
<comment>{0} is the workspace name. Shown in a confirmation dialog 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>

View File

@@ -450,6 +450,21 @@ 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)
{
const auto& windowName = _WindowProperties.WindowName();
if (!windowName.empty())
{
if (const auto layout = GetWindowLayout())
{
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
}
}
}
tab.Close();
}
@@ -471,6 +486,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 +820,28 @@ 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)
{
const auto& windowName = _WindowProperties.WindowName();
if (!windowName.empty())
{
if (const auto layout = GetWindowLayout())
{
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
}
}
}
}
}
// If specified, detach before closing to directly update the pane structure
pane->Close();
}

View File

@@ -25,6 +25,15 @@ namespace winrt::TerminalApp::implementation
InitializeComponent();
}
void TabRowControl::WorkspaceName(const winrt::hstring& value)
{
if (_WorkspaceName != value)
{
_WorkspaceName = value;
PropertyChanged.raise(*this, WUX::Data::PropertyChangedEventArgs{ L"WorkspaceName" });
}
}
// Method Description:
// - Bound in the Xaml editor to the [+] button.
// Arguments:

View File

@@ -19,6 +19,14 @@ namespace winrt::TerminalApp::implementation
til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(bool, ShowElevationShield, PropertyChanged.raise, false);
WINRT_OBSERVABLE_PROPERTY(bool, ShowWindowsButton, PropertyChanged.raise, true);
public:
winrt::hstring WorkspaceName() const noexcept { return _WorkspaceName; }
void WorkspaceName(const winrt::hstring& value);
private:
winrt::hstring _WorkspaceName{};
};
}

View File

@@ -9,5 +9,7 @@ namespace TerminalApp
TabRowControl();
Microsoft.UI.Xaml.Controls.TabView TabView { get; };
Boolean ShowElevationShield;
Boolean ShowWindowsButton;
String WorkspaceName;
}
}

View File

@@ -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,14 +36,44 @@
TabWidthMode="Equal">
<mux:TabView.TabStripHeader>
<!-- EA18 is the "Shield" glyph -->
<FontIcon x:Uid="ElevationShield"
Margin="9,4,0,4"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Glyph="&#xEA18;"
Visibility="{x:Bind ShowElevationShield, Mode=OneWay}" />
<StackPanel Orientation="Horizontal">
<!-- EA18 is the "Shield" glyph -->
<FontIcon x:Uid="ElevationShield"
Margin="9,4,0,4"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Glyph="&#xEA18;"
Visibility="{x:Bind ShowElevationShield, Mode=OneWay}" />
<!-- Workspace/windows button -->
<Button x:Name="WorkspaceDropdown"
Margin="4,0,0,4"
Padding="8,0,0,0"
VerticalAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
Visibility="{x:Bind ShowWindowsButton, Mode=OneWay}">
<Button.Content>
<StackPanel Orientation="Horizontal"
Spacing="8">
<!-- EE40 is the "TaskViewSettings" glyph -->
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xEE40;" />
<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>

View File

@@ -25,6 +25,8 @@
#include "TerminalSettingsCache.h"
#include "LaunchPositionRequest.g.cpp"
#include "WindowListEntry.g.cpp"
#include "WindowListRequest.g.cpp"
#include "RenameWindowRequestedArgs.g.cpp"
#include "RequestMoveContentArgs.g.cpp"
#include "TerminalPage.g.cpp"
@@ -334,6 +336,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 +460,12 @@ namespace winrt::TerminalApp::implementation
_tabRow.ShowElevationShield(IsRunningElevated() && _settings.GlobalSettings().ShowAdminShield());
// Apply the ShowWindowsButton theme setting.
if (const auto theme = _settings.GlobalSettings().CurrentTheme())
{
_tabRow.ShowWindowsButton(theme.Window() ? theme.Window().ShowWindowsButton() : true);
}
_adjustProcessPriorityThrottled = std::make_shared<ThrottledFunc<>>(
DispatcherQueue::GetForCurrentThread(),
til::throttled_func_options{
@@ -2285,14 +2308,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 +2330,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,7 +2379,49 @@ namespace winrt::TerminalApp::implementation
RequestLaunchPosition.raise(*this, launchPosRequest);
layout.InitialPosition(launchPosRequest.Position());
ApplicationState::SharedInstance().AppendPersistedWindowLayout(layout);
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:
@@ -3307,13 +3372,15 @@ namespace winrt::TerminalApp::implementation
return true;
}
bool TerminalPage::_IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri)
bool TerminalPage::_IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri) const
{
if (parsedUri.SchemeName() == L"http" || parsedUri.SchemeName() == L"https")
const auto& schemeName = parsedUri.SchemeName();
if (schemeName == L"http" || schemeName == L"https")
{
return true;
}
if (parsedUri.SchemeName() == L"file")
if (schemeName == L"file")
{
static const auto pathext{ wil::TryGetEnvironmentVariableW<std::wstring>(L"PATHEXT") };
const auto filename = parsedUri.Path();
@@ -3327,6 +3394,16 @@ namespace winrt::TerminalApp::implementation
return true;
}
if (const auto& safeSchemes = _settings.GlobalSettings().SafeUriSchemes())
{
for (const auto& scheme : safeSchemes)
{
if (til::equals_insensitive_ascii(schemeName, scheme))
{
return true;
}
}
}
return false;
}
@@ -4045,6 +4122,12 @@ namespace winrt::TerminalApp::implementation
_tabRow.ShowElevationShield(IsRunningElevated() && _settings.GlobalSettings().ShowAdminShield());
// Apply the ShowWindowsButton theme setting.
if (const auto theme = _settings.GlobalSettings().CurrentTheme())
{
_tabRow.ShowWindowsButton(theme.Window() ? theme.Window().ShowWindowsButton() : true);
}
Media::SolidColorBrush transparent{ Windows::UI::Colors::Transparent() };
_tabView.Background(transparent);
@@ -5685,6 +5768,199 @@ namespace winrt::TerminalApp::implementation
}
}
// Rebuild the workspace flyout contents. Called every time the flyout opens
// 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.count(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"));
WUX::Controls::FontIcon trashIcon{};
trashIcon.Glyph(L"\xE74D"); // Delete glyph
trashIcon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
deleteItem.Icon(trashIcon);
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.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)
@@ -5694,6 +5970,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)

View File

@@ -10,8 +10,11 @@
#include "AppKeyBindings.h"
#include "AppCommandlineArgs.h"
#include "RenameWindowRequestedArgs.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 +76,15 @@ namespace winrt::TerminalApp::implementation
_ProposedName{ 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 +106,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 +163,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 +235,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 +248,7 @@ 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;
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, PropertyChanged.raise, nullptr);
@@ -237,6 +271,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 };
@@ -335,6 +371,7 @@ namespace winrt::TerminalApp::implementation
void _restartPaneConnection(const TerminalApp::TerminalPaneContent&, const winrt::Windows::Foundation::IInspectable&);
safe_void_coroutine _OpenNewWindow(const Microsoft::Terminal::Settings::Model::INewContentArgs newContentArgs);
safe_void_coroutine _OpenWorkspaceWindow(const winrt::hstring name);
void _OpenNewTerminalViaDropdown(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
@@ -438,7 +475,7 @@ namespace winrt::TerminalApp::implementation
safe_void_coroutine _OpenHyperlinkHandler(const IInspectable sender, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs);
static bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);
static bool _IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri);
bool _IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri) const;
void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
bool _CopyText(bool dismissSelection, bool singleLine, bool withControlSequences, Microsoft::Terminal::Control::CopyFormat formats);
@@ -577,6 +614,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);
@@ -603,4 +641,5 @@ namespace winrt::TerminalApp::implementation
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TerminalPage);
BASIC_FACTORY(WindowListEntry);
}

View File

@@ -19,6 +19,10 @@ namespace TerminalApp
{
String ProposedName { get; };
};
[default_interface] runtimeclass SummonWindowByIdRequestedArgs
{
UInt64 WindowId { get; };
};
[default_interface] runtimeclass RequestMoveContentArgs
{
String Window { get; };
@@ -50,6 +54,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 +111,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,5 +122,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
event Windows.Foundation.TypedEventHandler<Object, LaunchPositionRequest> RequestLaunchPosition;
event Windows.Foundation.TypedEventHandler<Object, WindowListRequest> RequestWindowList;
}
}

View File

@@ -258,6 +258,15 @@ namespace winrt::TerminalApp::implementation
AppLogic::Current()->NotifyRootInitialized();
}
WindowLayout TerminalWindow::GetWindowLayout()
{
if (_root)
{
return _root->GetWindowLayout();
}
return nullptr;
}
void TerminalWindow::PersistState()
{
if (_root)
@@ -1103,6 +1112,11 @@ namespace winrt::TerminalApp::implementation
_initialContentArgs = wil::to_vector(args);
}
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.
@@ -1223,7 +1237,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;

View File

@@ -71,6 +71,7 @@ namespace winrt::TerminalApp::implementation
void Create();
winrt::Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
void PersistState();
void UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args);
@@ -79,6 +80,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 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);
@@ -222,6 +224,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);
@@ -231,6 +234,7 @@ 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);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;

View File

@@ -56,11 +56,13 @@ namespace TerminalApp
Int32 SetStartupCommandline(CommandlineArgs args);
void SetStartupContent(String json, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
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();
@@ -128,6 +130,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;
@@ -140,6 +143,7 @@ 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;
void AttachContent(String content, UInt32 tabIndex);
void SendContentToOther(RequestReceiveContentArgs args);

View File

@@ -98,7 +98,6 @@ void Terminal::UpdateSettings(ICoreSettings settings)
_answerbackMessage = settings.AnswerbackMessage();
_wordDelimiters = settings.WordDelimiters();
_suppressApplicationTitle = settings.SuppressApplicationTitle();
_startingTitle = settings.StartingTitle();
_trimBlockSelection = settings.TrimBlockSelection();
_autoMarkPrompts = settings.AutoMarkPrompts();
_rainbowSuggestions = settings.RainbowSuggestions();
@@ -124,6 +123,11 @@ void Terminal::UpdateSettings(ICoreSettings settings)
// Save the changes made above and in UpdateAppearance as the new default render settings.
GetRenderSettings().SaveDefaultSettings();
if (!_startingTitle)
{
_startingTitle = settings.StartingTitle();
}
if (!_startingTabColor && settings.StartingTabColor())
{
_startingTabColor = settings.StartingTabColor().Value();

View File

@@ -349,7 +349,7 @@ private:
::Microsoft::Console::VirtualTerminal::TerminalInput _terminalInput;
std::optional<std::wstring> _title;
std::wstring _startingTitle;
std::optional<std::wstring> _startingTitle;
std::optional<til::color> _startingTabColor;
std::vector<til::point_span> _searchHighlights;

View File

@@ -91,8 +91,12 @@ void Terminal::SetWindowTitle(const std::wstring_view title)
_assertLocked();
if (!_suppressApplicationTitle)
{
_title.emplace(title.empty() ? _startingTitle : title);
_pfnTitleChanged(_title.value());
_title.reset();
if (!title.empty())
{
_title.emplace(title);
}
_pfnTitleChanged(GetConsoleTitle());
}
}
@@ -112,6 +116,13 @@ bool Terminal::ResizeWindow(const til::CoordType width, const til::CoordType hei
return false;
}
const auto currentDimensions = _GetMutableViewport().Dimensions();
if (width == currentDimensions.width && height == currentDimensions.height)
{
return false;
}
if (_pfnWindowSizeChanged)
{
_pfnWindowSizeChanged(width, height);

View File

@@ -184,11 +184,18 @@ void Terminal::SelectNewRegion(const til::point coordStart, const til::point coo
std::wstring_view Terminal::GetConsoleTitle() const noexcept
{
_assertLocked();
if (_title.has_value())
if (_title)
{
return *_title;
}
return _startingTitle;
if (_startingTitle)
{
return *_startingTitle;
}
return {};
}
// Method Description:

View File

@@ -7,9 +7,7 @@
#include "LibraryResources.h"
#include "../TerminalSettingsModel/AllShortcutActions.h"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Navigation;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
@@ -44,65 +42,4 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
// Builds the "view all" flyout lazily on the first click of a
// row's "..." button, then caches it on the button so subsequent clicks
// just re-show it.
void Actions::ViewAllKeyChordsButton_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/)
{
const auto button = sender.try_as<Button>();
if (!button)
{
return;
}
// Retrieve cached flyout, if possible
if (const auto existing = button.Flyout())
{
existing.ShowAt(button);
return;
}
const auto cmdVM = button.DataContext().try_as<Editor::CommandViewModel>();
if (!cmdVM)
{
return;
}
Flyout flyout;
flyout.Placement(Primitives::FlyoutPlacementMode::Bottom);
flyout.FlyoutPresenterStyle(Resources().Lookup(box_value(L"EdgeToEdgeFlyoutPresenterStyle")).as<winrt::Windows::UI::Xaml::Style>());
StackPanel content;
content.Orientation(Orientation::Vertical);
content.MinWidth(120.0);
if (cmdVM.HasNoKeyChords())
{
const auto emptyTemplate = Resources().Lookup(box_value(L"ViewAllKeyChordsFlyoutEmptyStateTemplate")).as<DataTemplate>();
content.Children().Append(emptyTemplate.LoadContent().as<UIElement>());
}
else
{
const auto separatorTemplate = Resources().Lookup(box_value(L"ViewAllKeyChordsFlyoutSeparatorTemplate")).as<DataTemplate>();
const auto itemTemplate = Resources().Lookup(box_value(L"ViewAllKeyChordsFlyoutItemTemplate")).as<DataTemplate>();
const auto chords = cmdVM.KeyChordList();
const auto count = chords.Size();
for (uint32_t i = 0; i < count; ++i)
{
if (i > 0)
{
content.Children().Append(separatorTemplate.LoadContent().as<UIElement>());
}
auto chordVisual = itemTemplate.LoadContent().as<Editor::KeyChordVisual>();
chordVisual.KeyChord(chords.GetAt(i).CurrentKeys());
content.Children().Append(chordVisual);
}
}
flyout.Content(content);
button.Flyout(flyout);
flyout.ShowAt(button);
}
}

View File

@@ -17,8 +17,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
void ViewAllKeyChordsButton_Click(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(Editor::ActionsViewModel, ViewModel, PropertyChanged.raise, nullptr);

View File

@@ -8,6 +8,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mtu="using:Microsoft.Terminal.UI"
mc:Ignorable="d">
<Page.Resources>
@@ -16,116 +17,182 @@
<ResourceDictionary Source="CommonResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Theme Dictionary -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<!-- TextBox colors ! -->
<SolidColorBrush x:Key="TextControlBackground"
Color="#333333" />
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush"
Color="#B5B5B5" />
<SolidColorBrush x:Key="TextControlForeground"
Color="#B5B5B5" />
<SolidColorBrush x:Key="TextControlBorderBrush"
Color="#404040" />
<SolidColorBrush x:Key="TextControlButtonForeground"
Color="#B5B5B5" />
<SolidColorBrush x:Key="TextControlBackgroundPointerOver"
Color="#404040" />
<SolidColorBrush x:Key="TextControlForegroundPointerOver"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver"
Color="#404040" />
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver"
Color="#FF4343" />
<SolidColorBrush x:Key="TextControlBackgroundFocused"
Color="#333333" />
<SolidColorBrush x:Key="TextControlForegroundFocused"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TextControlBorderBrushFocused"
Color="#404040" />
<SolidColorBrush x:Key="TextControlButtonForegroundPressed"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed"
Color="#FF4343" />
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- TextBox colors ! -->
<SolidColorBrush x:Key="TextControlBackground"
Color="#CCCCCC" />
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush"
Color="#636363" />
<SolidColorBrush x:Key="TextControlBorderBrush"
Color="#636363" />
<SolidColorBrush x:Key="TextControlButtonForeground"
Color="#636363" />
<SolidColorBrush x:Key="TextControlBackgroundPointerOver"
Color="#DADADA" />
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver"
Color="#636363" />
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver"
Color="#FF4343" />
<SolidColorBrush x:Key="TextControlBackgroundFocused"
Color="#CCCCCC" />
<SolidColorBrush x:Key="TextControlBorderBrushFocused"
Color="#636363" />
<SolidColorBrush x:Key="TextControlButtonForegroundPressed"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed"
Color="#FF4343" />
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border" />
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!-- Styles -->
<Style x:Key="ActionRowItemContainerStyle"
<Style x:Key="KeyBindingContainerStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
TargetType="ListViewItem">
<Setter Property="Padding" Value="{StaticResource SettingsCardPadding}" />
<Setter Property="MinHeight" Value="{StaticResource SettingsCardMinHeight}" />
<Setter Property="Margin" Value="{StaticResource SettingsCardItemMargin}" />
<Setter Property="Padding" Value="12,4,4,4" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="XYFocusKeyboardNavigation" Value="Enabled" />
<Setter Property="BorderBrush" Value="{ThemeResource ExpanderHeaderBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ExpanderHeaderBorderThickness}" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
</Style>
<Style x:Key="ActionRowNameTextStyle"
<Style x:Key="KeyBindingNameTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="TextWrapping" Value="WrapWholeWords" />
</Style>
<Style x:Key="ActionRowSubtleButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
<Setter Property="MinWidth" Value="32" />
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="Padding" Value="0" />
<Style x:Key="KeyChordEditorStyle"
TargetType="local:KeyChordListener">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
<!--
FlyoutPresenter style with no internal padding so a full-width Border
separator inside the flyout can reach the flyout's left/right edges.
-->
<Style x:Key="EdgeToEdgeFlyoutPresenterStyle"
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
TargetType="FlyoutPresenter">
<Setter Property="Padding" Value="0" />
<Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
</Style>
<!-- Converters & Misc. -->
<SolidColorBrush x:Key="ActionContainerBackgroundEditing"
Color="{ThemeResource SystemListMediumColor}" />
<SolidColorBrush x:Key="ActionContainerBackground"
Color="Transparent" />
<!-- Templates -->
<DataTemplate x:Key="ViewAllKeyChordsFlyoutSeparatorTemplate">
<Border Height="1"
Margin="0,4,0,4"
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
</DataTemplate>
<DataTemplate x:Key="ViewAllKeyChordsFlyoutEmptyStateTemplate">
<TextBlock x:Uid="Actions_NoKeyBindings"
Margin="12,8,12,8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Style="{StaticResource SecondaryTextBlockStyle}" />
</DataTemplate>
<DataTemplate x:Key="ViewAllKeyChordsFlyoutItemTemplate">
<local:KeyChordVisual Margin="{ThemeResource MenuFlyoutItemThemePaddingNarrow}"
HorizontalAlignment="Right" />
</DataTemplate>
<DataTemplate x:Key="CommandTemplate"
x:DataType="local:CommandViewModel">
<Grid AutomationProperties.Name="{x:Bind DisplayNameAndKeyChordAutomationPropName, Mode=OneWay}"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<!-- command name -->
<ColumnDefinition Width="*" />
<!-- key chord -->
<ColumnDefinition Width="Auto" />
<!-- edit button -->
<ColumnDefinition Width="Auto" />
<!-- "..." button -->
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ListViewItem AutomationProperties.Name="{x:Bind DisplayNameAndKeyChordAutomationPropName, Mode=OneWay}"
Style="{StaticResource KeyBindingContainerStyle}">
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<!-- command name -->
<ColumnDefinition Width="*" />
<!-- key chord -->
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<!-- Command Name -->
<TextBlock Grid.Column="0"
Style="{StaticResource ActionRowNameTextStyle}"
Text="{x:Bind DisplayName, Mode=OneWay}" />
<!-- Key Chord -->
<local:KeyChordVisual Grid.Column="1"
HorizontalAlignment="Right"
KeyChord="{x:Bind FirstKeyChord, Mode=OneWay}" />
<!-- Edit button -->
<Button x:Uid="Actions_EditButton"
Grid.Column="2"
AutomationProperties.Name="{x:Bind DisplayName, Mode=OneWay}"
Click="{x:Bind Edit_Click}"
Style="{StaticResource ActionRowSubtleButtonStyle}">
<FontIcon FontSize="14"
Glyph="&#xE70F;" />
</Button>
<!-- "..." button + flyout -->
<Button x:Uid="Actions_ViewAllKeyChordsButton"
Grid.Column="3"
Click="ViewAllKeyChordsButton_Click"
Style="{StaticResource ActionRowSubtleButtonStyle}">
<FontIcon FontSize="14"
Glyph="&#xE712;" />
</Button>
</Grid>
<!-- Command Name -->
<TextBlock Grid.Column="0"
FontWeight="Normal"
Style="{StaticResource KeyBindingNameTextBlockStyle}"
Text="{x:Bind DisplayName, Mode=OneWay}" />
<!-- Key Chord Text -->
<Grid Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
ColumnSpacing="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
Padding="8,4,8,4"
Style="{ThemeResource KeyChordBorderStyle}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(FirstKeyChordText)}">
<TextBlock FontSize="14"
Style="{ThemeResource KeyChordTextBlockStyle}"
Text="{x:Bind FirstKeyChordText, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</Border>
<Border Grid.Column="1"
Padding="8,4,8,4"
Style="{ThemeResource KeyChordBorderStyle}"
ToolTipService.ToolTip="{x:Bind AdditionalKeyChordTooltipText, Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(AdditionalKeyChordCountText)}">
<TextBlock FontSize="14"
Style="{ThemeResource KeyChordTextBlockStyle}"
Text="{x:Bind AdditionalKeyChordCountText, Mode=OneWay}" />
</Border>
</Grid>
</Grid>
</ListViewItem>
</DataTemplate>
</ResourceDictionary>
</Page.Resources>
@@ -141,8 +208,7 @@
<!-- Add New Button -->
<Button x:Name="AddNewButton"
Margin="0,12,0,0"
Click="{x:Bind ViewModel.AddNewCommand}"
Style="{StaticResource AccentButtonStyle}">
Click="{x:Bind ViewModel.AddNewCommand}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<FontIcon FontSize="{StaticResource StandardIconSize}"
@@ -155,42 +221,11 @@
<!-- Commands -->
<ListView x:Name="CommandsListView"
Margin="-8,0,0,0"
IsItemClickEnabled="True"
ItemClick="{x:Bind ViewModel.CmdListItemClicked}"
ItemContainerStyle="{StaticResource ActionRowItemContainerStyle}"
ItemTemplate="{StaticResource CommandTemplate}"
ItemsSource="{x:Bind ViewModel.CommandList, Mode=OneWay}"
SelectionMode="None">
<!--
The framework ListViewItemPresenter reads its per-state backgrounds
from these theme resources (not from ListViewItem.Background), so we
override them here to match the card chrome on ActionRowItemContainerStyle.
-->
<ListView.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="ListViewItemBackground"
ResourceKey="ExpanderHeaderBackground" />
<StaticResource x:Key="ListViewItemBackgroundPointerOver"
ResourceKey="ControlFillColorSecondaryBrush" />
<StaticResource x:Key="ListViewItemBackgroundPressed"
ResourceKey="ControlFillColorTertiaryBrush" />
<StaticResource x:Key="ListViewItemBackgroundSelected"
ResourceKey="ExpanderHeaderBackground" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver"
ResourceKey="ControlFillColorSecondaryBrush" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPressed"
ResourceKey="ControlFillColorTertiaryBrush" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="ListViewItemBackground"
ResourceKey="SystemColorButtonFaceColorBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</ListView.Resources>
</ListView>
ItemsSource="{x:Bind ViewModel.CommandList, Mode=OneWay}" />
</StackPanel>
</Border>
</Page>

View File

@@ -57,22 +57,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return;
}
std::vector<Editor::KeyChordViewModel> keyChordVMs;
int32_t idx = 1;
for (const auto keys : _keyChordList)
{
auto kcVM{ make_self<KeyChordViewModel>(keys) };
kcVM->Index(idx++);
_RegisterKeyChordVMEvents(*kcVM);
keyChordVMs.push_back(*kcVM);
auto kcVM{ make<KeyChordViewModel>(keys) };
_RegisterKeyChordVMEvents(kcVM);
keyChordVMs.push_back(kcVM);
}
_KeyChordList = single_threaded_observable_vector(std::move(keyChordVMs));
_KeyChordList.VectorChanged([weakThis{ get_weak() }](const auto& /*sender*/, const auto& /*args*/) {
if (auto self{ weakThis.get() })
{
self->_ReindexKeyChordList();
self->_NotifyChanges(L"FirstKeyChord", L"FirstKeyChordText", L"AdditionalKeyChordCountText", L"AdditionalKeyChordTooltipText", L"DisplayNameAndKeyChordAutomationPropName");
}
});
std::vector<hstring> shortcutActions;
for (const auto [action, name] : _availableActionsAndNamesMap)
@@ -128,7 +119,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return _cachedDisplayName;
}
winrt::hstring CommandViewModel::Name() const noexcept
winrt::hstring CommandViewModel::Name()
{
return _command.HasName() ? _command.Name() : L"";
}
@@ -154,7 +145,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return result;
}
winrt::hstring CommandViewModel::FirstKeyChordText() const
winrt::hstring CommandViewModel::FirstKeyChordText()
{
if (_KeyChordList.Size() != 0)
{
@@ -163,21 +154,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return L"";
}
Control::KeyChord CommandViewModel::FirstKeyChord() const noexcept
{
if (_KeyChordList.Size() != 0)
{
return _KeyChordList.GetAt(0).CurrentKeys();
}
return nullptr;
}
bool CommandViewModel::HasNoKeyChords() const noexcept
{
return _KeyChordList.Size() == 0;
}
winrt::hstring CommandViewModel::AdditionalKeyChordCountText() const
winrt::hstring CommandViewModel::AdditionalKeyChordCountText()
{
const auto size = _KeyChordList.Size();
if (size > 1)
@@ -187,7 +164,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return L"";
}
winrt::hstring CommandViewModel::AdditionalKeyChordTooltipText() const
winrt::hstring CommandViewModel::AdditionalKeyChordTooltipText()
{
const auto size = _KeyChordList.Size();
if (size <= 1)
@@ -206,12 +183,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return winrt::hstring{ result };
}
winrt::hstring CommandViewModel::ID() const noexcept
winrt::hstring CommandViewModel::ID()
{
return _command.ID();
}
bool CommandViewModel::IsUserAction() const noexcept
bool CommandViewModel::IsUserAction()
{
return _command.Origin() == OriginTag::User;
}
@@ -229,40 +206,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void CommandViewModel::AddKeybinding_Click()
{
auto kbdVM{ make_self<KeyChordViewModel>(nullptr) };
kbdVM->Index(gsl::narrow_cast<int32_t>(_KeyChordList.Size()) + 1);
kbdVM->IsInEditMode(true);
_RegisterKeyChordVMEvents(*kbdVM);
KeyChordList().Append(*kbdVM);
FocusContainer.raise(*this, *kbdVM);
}
// Reassigns 1-based Index values for every KeyChordViewModel in the list. Called
// whenever the list changes shape so the per-row "Key Binding #N" label stays in sync.
void CommandViewModel::_ReindexKeyChordList()
{
const auto size = _KeyChordList.Size();
for (uint32_t i = 0; i < size; ++i)
{
auto kcVM{ _KeyChordList.GetAt(i) };
const auto newIdx = gsl::narrow_cast<int32_t>(i) + 1;
if (kcVM.Index() != newIdx)
{
kcVM.Index(newIdx);
}
}
}
winrt::hstring CommandViewModel::ActionNameTextBoxAutomationPropName() const
winrt::hstring CommandViewModel::ActionNameTextBoxAutomationPropName()
{
return RS_(L"Actions_Name/Text");
}
winrt::hstring CommandViewModel::ShortcutActionComboBoxAutomationPropName() const
winrt::hstring CommandViewModel::ShortcutActionComboBoxAutomationPropName()
{
return RS_(L"Actions_ShortcutAction/Text");
}
winrt::hstring CommandViewModel::AdditionalArgumentsControlAutomationPropName() const
winrt::hstring CommandViewModel::AdditionalArgumentsControlAutomationPropName()
{
return RS_(L"Actions_Arguments/Text");
}
@@ -313,19 +273,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
self->FocusContainer.raise(*self, senderVM);
}
}
else if (propertyName == L"KeyChordText")
{
// The first chord of the list is what the row visual on the Actions page binds to,
// so propagate the change up so the row updates.
if (self->_KeyChordList.Size() > 0 && self->_KeyChordList.GetAt(0) == senderVM)
{
self->_NotifyChanges(L"FirstKeyChord", L"FirstKeyChordText", L"DisplayNameAndKeyChordAutomationPropName");
}
else
{
self->_NotifyChanges(L"AdditionalKeyChordTooltipText");
}
}
}
});
}
@@ -1118,21 +1065,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
KeyChordViewModel::KeyChordViewModel(Control::KeyChord currentKeys)
{
CurrentKeys(currentKeys);
// DisplayLabel is derived from Index, so re-fire the change for it whenever Index changes.
PropertyChanged([this](const auto& /*sender*/, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) {
if (args.PropertyName() == L"Index")
{
_NotifyChanges(L"DisplayLabel");
}
});
}
void KeyChordViewModel::CurrentKeys(const Control::KeyChord& newKeys)
{
_currentKeys = newKeys;
KeyChordText(Model::KeyChordSerialization::ToString(_currentKeys));
_NotifyChanges(L"CurrentKeys");
}
Control::KeyChord KeyChordViewModel::CurrentKeys() const noexcept
@@ -1188,12 +1126,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
hstring KeyChordViewModel::CancelButtonName() const noexcept { return RS_(L"Actions_CancelButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
hstring KeyChordViewModel::AcceptButtonName() const noexcept { return RS_(L"Actions_AcceptButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
hstring KeyChordViewModel::DeleteButtonName() const noexcept { return RS_(L"Actions_DeleteButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
hstring KeyChordViewModel::EditButtonName() const noexcept { return RS_(L"Actions_EditButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); }
winrt::hstring KeyChordViewModel::DisplayLabel() const
{
return hstring{ RS_fmt(L"EditAction_KeyBindingNumberFormat", _Index) };
}
ActionsViewModel::ActionsViewModel(Model::CascadiaSettings settings) :
_Settings{ settings }

View File

@@ -69,18 +69,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Initialize();
winrt::hstring DisplayName();
winrt::hstring Name() const noexcept;
winrt::hstring Name();
void Name(const winrt::hstring& newName);
winrt::hstring DisplayNameAndKeyChordAutomationPropName();
winrt::hstring FirstKeyChordText() const;
Control::KeyChord FirstKeyChord() const noexcept;
bool HasNoKeyChords() const noexcept;
winrt::hstring AdditionalKeyChordCountText() const;
winrt::hstring AdditionalKeyChordTooltipText() const;
winrt::hstring FirstKeyChordText();
winrt::hstring AdditionalKeyChordCountText();
winrt::hstring AdditionalKeyChordTooltipText();
winrt::hstring ID() const noexcept;
bool IsUserAction() const noexcept;
winrt::hstring ID();
bool IsUserAction();
void Edit_Click();
til::typed_event<Editor::CommandViewModel, IInspectable> EditRequested;
@@ -91,9 +89,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void AddKeybinding_Click();
// UIA text
winrt::hstring ActionNameTextBoxAutomationPropName() const;
winrt::hstring ShortcutActionComboBoxAutomationPropName() const;
winrt::hstring AdditionalArgumentsControlAutomationPropName() const;
winrt::hstring ActionNameTextBoxAutomationPropName();
winrt::hstring ShortcutActionComboBoxAutomationPropName();
winrt::hstring AdditionalArgumentsControlAutomationPropName();
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateColorSchemeRequested;
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateColorSchemeNamesRequested;
@@ -117,7 +115,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void _RegisterActionArgsVMEvents(Editor::ActionArgsViewModel actionArgsVM);
void _ReplaceCommandWithUserCopy(bool reinitialize);
void _CreateAndInitializeActionArgsVMHelper();
void _ReindexKeyChordList();
};
struct ArgWrapper : ArgWrapperT<ArgWrapper>, ViewModelHelper<ArgWrapper>
@@ -233,19 +230,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void CancelChanges();
void DeleteKeyChord();
winrt::hstring DisplayLabel() const;
// UIA Text
hstring CancelButtonName() const noexcept;
hstring AcceptButtonName() const noexcept;
hstring DeleteButtonName() const noexcept;
hstring EditButtonName() const noexcept;
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false);
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys);
VIEW_MODEL_OBSERVABLE_PROPERTY(winrt::hstring, KeyChordText);
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr);
VIEW_MODEL_OBSERVABLE_PROPERTY(int32_t, Index, 0);
public:
til::typed_event<Editor::KeyChordViewModel, Terminal::Control::KeyChord> AddKeyChordRequested;

View File

@@ -55,8 +55,6 @@ namespace Microsoft.Terminal.Settings.Editor
// View-model specific
String DisplayName { get; };
String FirstKeyChordText { get; };
Microsoft.Terminal.Control.KeyChord FirstKeyChord { get; };
Boolean HasNoKeyChords { get; };
String AdditionalKeyChordCountText { get; };
String AdditionalKeyChordTooltipText { get; };
String DisplayNameAndKeyChordAutomationPropName { get; };
@@ -140,12 +138,9 @@ namespace Microsoft.Terminal.Settings.Editor
String KeyChordText { get; };
// UI side
Microsoft.Terminal.Control.KeyChord CurrentKeys { get; };
Microsoft.Terminal.Control.KeyChord ProposedKeys;
Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout;
Boolean IsInEditMode { get; };
Int32 Index;
String DisplayLabel { get; };
void ToggleEditMode();
void AcceptChanges();
void CancelChanges();
@@ -153,7 +148,6 @@ namespace Microsoft.Terminal.Settings.Editor
String CancelButtonName { get; };
String AcceptButtonName { get; };
String DeleteButtonName { get; };
String EditButtonName { get; };
event Windows.Foundation.TypedEventHandler<KeyChordViewModel, Microsoft.Terminal.Control.KeyChord> AddKeyChordRequested;
event Windows.Foundation.TypedEventHandler<KeyChordViewModel, ModifyKeyChordEventArgs> ModifyKeyChordRequested;

View File

@@ -17,11 +17,130 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="CommonResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Theme Dictionary -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Button">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
<!-- Override visual states -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<!-- Define the appearance of the button -->
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="border"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlHighlightAccentRevealBackgroundBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Button">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
<!-- Override visual states -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<!-- Define the appearance of the button -->
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="border"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlHighlightAccentRevealBackgroundBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Button" />
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<GridLength x:Key="ArgumentNameWidth">148</GridLength>
<!-- Styles -->
<Style x:Key="KeyBindingContainerStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
TargetType="ListViewItem">
<Setter Property="Padding" Value="4" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="XYFocusKeyboardNavigation" Value="Enabled" />
</Style>
<Style x:Key="KeyChordEditorStyle"
TargetType="local:KeyChordListener">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<x:Int32 x:Key="EditButtonSize">32</x:Int32>
<x:Double x:Key="EditButtonIconSize">14</x:Double>
<Style x:Key="EditButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
@@ -30,7 +149,6 @@
<Setter Property="Height" Value="{StaticResource EditButtonSize}" />
<Setter Property="Width" Value="{StaticResource EditButtonSize}" />
</Style>
<Style x:Key="AccentEditButtonStyle"
BasedOn="{StaticResource AccentButtonStyle}"
TargetType="Button">
@@ -39,63 +157,42 @@
<Setter Property="Height" Value="{StaticResource EditButtonSize}" />
<Setter Property="Width" Value="{StaticResource EditButtonSize}" />
</Style>
<Style x:Key="KeyChordEditorStyle"
TargetType="local:KeyChordListener">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="MinWidth" Value="160" />
<Style x:Key="TextBlockGroupingStyle"
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="MaxWidth" Value="{StaticResource StandardControlMaxWidth}" />
<Setter Property="Margin" Value="0,0,0,4" />
<Setter Property="FontSize" Value="16" />
</Style>
<!--
Item container for the per-chord ListView. We're hosting a SettingContainer
inside each item, so strip the default ListViewItem visuals (padding, border,
hover/selection background) so they don't double up.
-->
<Style x:Key="KeyChordListViewItemStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
TargetType="ListViewItem">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
<!-- "Key Binding #N" template -->
<!-- Templates -->
<DataTemplate x:Key="KeyChordTemplate"
x:DataType="local:KeyChordViewModel">
<local:SettingContainer Header="{x:Bind DisplayLabel, Mode=OneWay}">
<Grid VerticalAlignment="Center"
ColumnSpacing="8">
<ListViewItem IsTabStop="False"
Style="{StaticResource KeyBindingContainerStyle}">
<Grid Padding="-4,0,0,0"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<!-- Key visual / key chord listener -->
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
<!-- Cancel button (visible only in edit mode) -->
<ColumnDefinition Width="Auto" />
<!-- Accept button (visible only in edit mode) -->
<ColumnDefinition Width="Auto" />
<!-- Edit (pencil) button (visible only NOT in edit mode) -->
<ColumnDefinition Width="Auto" />
<!-- Delete button -->
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Read-only key chord visual -->
<local:KeyChordVisual Grid.Column="0"
KeyChord="{x:Bind CurrentKeys, Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}" />
<!-- Editable key chord listener -->
<Button Grid.Column="0"
Background="{ThemeResource AppBarItemBackgroundThemeBrush}"
Click="{x:Bind ToggleEditMode}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}">
<TextBlock FontSize="14"
Text="{x:Bind KeyChordText, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</Button>
<local:KeyChordListener Grid.Column="0"
Keys="{x:Bind ProposedKeys, Mode=TwoWay}"
Style="{StaticResource KeyChordEditorStyle}"
Visibility="{x:Bind IsInEditMode, Mode=OneWay}" />
<!-- Cancel changes (edit mode only) -->
<Button x:Uid="Actions_CancelButton"
Grid.Column="1"
Margin="8,0,0,0"
AutomationProperties.Name="{x:Bind CancelButtonName}"
Click="{x:Bind CancelChanges}"
Style="{StaticResource EditButtonStyle}"
@@ -104,9 +201,9 @@
Glyph="&#xE711;" />
</Button>
<!-- Accept changes (edit mode only) -->
<Button x:Uid="Actions_AcceptButton"
Grid.Column="2"
Margin="8,0,8,0"
AutomationProperties.Name="{x:Bind AcceptButtonName}"
Click="{x:Bind AcceptChanges}"
Flyout="{x:Bind AcceptChangesFlyout, Mode=OneWay}"
@@ -116,19 +213,8 @@
Glyph="&#xE8FB;" />
</Button>
<!-- Edit button -->
<Button x:Uid="Actions_EditButton"
Grid.Column="3"
AutomationProperties.Name="{x:Bind EditButtonName}"
Click="{x:Bind ToggleEditMode}"
Style="{StaticResource EditButtonStyle}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}">
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
Glyph="&#xE70F;" />
</Button>
<!-- Delete button -->
<Button Grid.Column="4"
<Button Grid.Column="3"
HorizontalAlignment="Left"
AutomationProperties.Name="{x:Bind DeleteButtonName}"
Style="{StaticResource DeleteSmallButtonStyle}">
<Button.Content>
@@ -147,23 +233,25 @@
</Button.Flyout>
</Button>
</Grid>
</local:SettingContainer>
</ListViewItem>
</DataTemplate>
<!--
BODGY: Each ArgWrapper DataTemplate below wraps its editor control
in a <local:SettingContainer Header="{x:Bind Name}"> rather than sharing a
single outer template. This is because a bug in WinUI 2 prevents a
ContentPresenter + ContentTemplateSelector pattern from working correctly,
resulting in "Microsoft.Terminal.Settings.Editor.ArgWrapper" being shown.
-->
<!-- Example shortcut action to test this template: Adjust Opacity -->
<!-- Currently that is the only Int32 arg, so just clamp the min/max values according to that -->
<DataTemplate x:Key="Int32Template"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<muxc:NumberBox MinWidth="160"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<muxc:NumberBox Grid.Column="1"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name}"
LargeChange="1"
Maximum="100"
@@ -171,14 +259,24 @@
SmallChange="10"
Style="{StaticResource NumberBoxSettingStyle}"
Value="{x:Bind UnboxInt32(Value), Mode=TwoWay, BindBack=Int32BindBack}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Switch To Tab -->
<DataTemplate x:Key="UInt32Template"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<muxc:NumberBox MinWidth="160"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<muxc:NumberBox Grid.Column="1"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name}"
LargeChange="1"
Maximum="999"
@@ -186,14 +284,24 @@
SmallChange="1"
Style="{StaticResource NumberBoxSettingStyle}"
Value="{x:Bind UnboxUInt32(Value), Mode=TwoWay, BindBack=UInt32BindBack}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Close Other Tabs -->
<DataTemplate x:Key="UInt32OptionalTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<muxc:NumberBox MinWidth="160"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<muxc:NumberBox Grid.Column="1"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name}"
LargeChange="1"
Maximum="999"
@@ -201,14 +309,24 @@
SmallChange="1"
Style="{StaticResource NumberBoxSettingStyle}"
Value="{x:Bind UnboxUInt32Optional(Value), Mode=TwoWay, BindBack=UInt32OptionalBindBack}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Split Pane -->
<DataTemplate x:Key="Int32OptionalTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<muxc:NumberBox MinWidth="160"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<muxc:NumberBox Grid.Column="1"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name}"
LargeChange="1"
Maximum="999"
@@ -216,14 +334,24 @@
SmallChange="1"
Style="{StaticResource NumberBoxSettingStyle}"
Value="{x:Bind UnboxInt32Optional(Value), Mode=TwoWay, BindBack=Int32OptionalBindBack}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Adjust Font Size -->
<DataTemplate x:Key="FloatTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<muxc:NumberBox MinWidth="160"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<muxc:NumberBox Grid.Column="1"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name}"
LargeChange="1"
Maximum="999"
@@ -231,14 +359,24 @@
SmallChange="1"
Style="{StaticResource NumberBoxSettingStyle}"
Value="{x:Bind UnboxFloat(Value), Mode=TwoWay, BindBack=FloatBindBack}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Split Pane -->
<DataTemplate x:Key="SplitSizeTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<muxc:NumberBox MinWidth="160"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<muxc:NumberBox Grid.Column="1"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name}"
LargeChange="0.2"
Maximum="1"
@@ -246,95 +384,144 @@
SmallChange="0.1"
Style="{StaticResource NumberBoxSettingStyle}"
Value="{x:Bind UnboxFloat(Value), Mode=TwoWay, BindBack=FloatBindBack}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Send Input -->
<DataTemplate x:Key="StringTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<TextBox MinWidth="248"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*"
MinWidth="196" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<TextBox Grid.Column="1"
AutomationProperties.Name="{x:Bind Name}"
Text="{x:Bind UnboxString(Value), Mode=TwoWay, BindBack=StringBindBack}"
TextWrapping="Wrap" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Set Color Scheme -->
<DataTemplate x:Key="ColorSchemeTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<ComboBox MinWidth="248"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<ComboBox Grid.Column="1"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name}"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind EnumList, Mode=OneWay}"
SelectedItem="{x:Bind EnumValue, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Export Buffer -->
<DataTemplate x:Key="FilePickerTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<Grid ColumnSpacing="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"
MinWidth="196" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
AutomationProperties.Name="{x:Bind Name}"
Text="{x:Bind UnboxString(Value), Mode=TwoWay, BindBack=StringBindBack}"
TextWrapping="Wrap" />
<Button x:Uid="Actions_Browse"
Grid.Column="1"
Click="{x:Bind BrowseForFile_Click}"
Style="{StaticResource BrowseButtonStyle}" />
</Grid>
</local:SettingContainer>
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*"
MinWidth="196" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<TextBox Grid.Column="1"
AutomationProperties.Name="{x:Bind Name}"
Text="{x:Bind UnboxString(Value), Mode=TwoWay, BindBack=StringBindBack}"
TextWrapping="Wrap" />
<Button x:Uid="Actions_Browse"
Grid.Column="2"
Click="{x:Bind BrowseForFile_Click}"
Style="{StaticResource BrowseButtonStyle}" />
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: New Tab -->
<DataTemplate x:Key="FolderPickerTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<Grid ColumnSpacing="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"
MinWidth="196" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
AutomationProperties.Name="{x:Bind Name}"
Text="{x:Bind UnboxString(Value), Mode=TwoWay, BindBack=StringBindBack}"
TextWrapping="Wrap" />
<Button x:Uid="Actions_Browse"
Grid.Column="1"
Click="{x:Bind BrowseForFolder_Click}"
Style="{StaticResource BrowseButtonStyle}" />
</Grid>
</local:SettingContainer>
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*"
MinWidth="196" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<TextBox Grid.Column="1"
AutomationProperties.Name="{x:Bind Name}"
Text="{x:Bind UnboxString(Value), Mode=TwoWay, BindBack=StringBindBack}"
TextWrapping="Wrap" />
<Button x:Uid="Actions_Browse"
Grid.Column="2"
Click="{x:Bind BrowseForFolder_Click}"
Style="{StaticResource BrowseButtonStyle}" />
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Set Focus Mode -->
<DataTemplate x:Key="BoolTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<ToggleSwitch AutomationProperties.Name="{x:Bind Name}"
IsOn="{x:Bind UnboxBool(Value), Mode=TwoWay, BindBack=BoolOptionalBindBack}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<ToggleSwitch Grid.Column="1"
HorizontalAlignment="Left"
AutomationProperties.Name="{x:Bind Name}"
IsOn="{x:Bind UnboxBool(Value), Mode=TwoWay, BindBack=BoolOptionalBindBack}" />
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Split Pane -->
<DataTemplate x:Key="BoolOptionalTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<CheckBox AutomationProperties.Name="{x:Bind Name}"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<CheckBox Grid.Column="1"
HorizontalAlignment="Left"
AutomationProperties.Name="{x:Bind Name}"
IsChecked="{x:Bind UnboxBoolOptional(Value), Mode=TwoWay, BindBack=BoolOptionalBindBack}"
IsThreeState="True" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Resize Pane -->
@@ -345,14 +532,24 @@
<DataTemplate x:Key="EnumTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<ComboBox MinWidth="248"
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<ComboBox Grid.Column="1"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name}"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind EnumList, Mode=OneWay}"
SelectedItem="{x:Bind EnumValue, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Copy Text -->
@@ -375,35 +572,66 @@
<DataTemplate x:Key="FlagTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<ItemsControl AutomationProperties.Name="{x:Bind Name}"
<Grid Margin="0,4,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<ItemsControl Grid.Column="1"
Margin="0"
HorizontalAlignment="Left"
AutomationProperties.Name="{x:Bind Name}"
ItemTemplate="{StaticResource FlagItemTemplate}"
ItemsSource="{x:Bind FlagList, Mode=OneWay}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Add Mark -->
<DataTemplate x:Key="TerminalCoreColorOptionalTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<local:NullableColorPicker x:Uid="Actions_NullableColorPicker"
Grid.Column="1"
AutomationProperties.Name="{x:Bind Name}"
ColorSchemeVM="{x:Bind DefaultColorScheme, Mode=OneWay}"
CurrentColor="{x:Bind UnboxTerminalCoreColorOptional(Value), Mode=TwoWay, BindBack=TerminalCoreColorBindBack}"
NullColorPreview="{x:Bind DefaultColorScheme.ForegroundColor.Color, Mode=OneWay}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<!-- Example shortcut action to test this template: Set Tab Color -->
<DataTemplate x:Key="WindowsUIColorOptionalTemplate"
x:DataType="local:ArgWrapper">
<local:SettingContainer Header="{x:Bind Name}">
<Grid Margin="0,4,0,4"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextWrapping="WrapWholeWords" />
<local:NullableColorPicker x:Uid="Actions_NullableColorPicker"
Grid.Column="1"
AutomationProperties.Name="{x:Bind Name}"
ColorSchemeVM="{x:Bind DefaultColorScheme, Mode=OneWay}"
CurrentColor="{x:Bind UnboxWindowsUIColorOptional(Value), Mode=TwoWay, BindBack=WindowsUIColorBindBack}"
NullColorPreview="{x:Bind DefaultColorScheme.ForegroundColor.Color, Mode=OneWay}" />
</local:SettingContainer>
</Grid>
</DataTemplate>
<local:ArgsTemplateSelectors x:Key="ArgsTemplateSelector"
@@ -427,106 +655,122 @@
</ResourceDictionary>
</Page.Resources>
<Border MaxWidth="{StaticResource StandardControlMaxWidth}">
<StackPanel HorizontalAlignment="Stretch"
Style="{StaticResource SettingsStackStyle}">
<!-- Action type (top-most setting on the page) -->
<local:SettingContainer x:Name="ActionType"
x:Uid="EditAction_ActionType">
<AutoSuggestBox x:Name="ShortcutActionBox"
MinWidth="248"
AutomationProperties.Name="{x:Bind ViewModel.ShortcutActionComboBoxAutomationPropName}"
GotFocus="ShortcutActionBox_GotFocus"
LostFocus="ShortcutActionBox_LostFocus"
QuerySubmitted="ShortcutActionBox_QuerySubmitted"
TextChanged="ShortcutActionBox_TextChanged" />
</local:SettingContainer>
<!-- Key bindings expander -->
<local:SettingContainer x:Name="KeyBindingsContainer"
x:Uid="EditAction_KeyBindings"
StartExpanded="{x:Bind mtu:Converters.InvertBoolean(ViewModel.HasNoKeyChords), Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<StackPanel>
<!-- "New key binding" button -->
<local:SettingContainer x:Name="NewKeyBinding"
x:Uid="EditAction_NewKeyBinding">
<Button x:Uid="EditAction_AddKeyBinding"
Click="{x:Bind ViewModel.AddKeybinding_Click}"
Style="{StaticResource AccentButtonStyle}" />
</local:SettingContainer>
<!-- Existing key bindings, one container per chord -->
<ListView x:Name="KeyChordListView"
x:Uid="Actions_KeyBindingsListView"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsItemClickEnabled="False"
ItemContainerStyle="{StaticResource KeyChordListViewItemStyle}"
ItemTemplate="{StaticResource KeyChordTemplate}"
ItemsSource="{x:Bind ViewModel.KeyChordList, Mode=OneWay}"
SelectionMode="None" />
</StackPanel>
</local:SettingContainer>
<!-- Additional customizations expander -->
<local:SettingContainer x:Name="AdditionalCustomizations"
x:Uid="EditAction_AdditionalCustomizations"
Style="{StaticResource ExpanderSettingContainerStyle}">
<StackPanel>
<!-- Action name -->
<local:SettingContainer x:Name="ActionName"
x:Uid="EditAction_ActionName">
<TextBox x:Name="CommandNameTextBox"
MinWidth="248"
AutomationProperties.Name="{x:Bind ViewModel.ActionNameTextBoxAutomationPropName}"
PlaceholderText="{x:Bind ViewModel.DisplayName, Mode=OneWay}"
Text="{x:Bind ViewModel.Name, Mode=TwoWay}" />
</local:SettingContainer>
<!-- Action argument controls -->
<ItemsControl HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
AutomationProperties.Name="{x:Bind ViewModel.AdditionalArgumentsControlAutomationPropName}"
IsTabStop="False"
ItemTemplateSelector="{StaticResource ArgsTemplateSelector}"
ItemsSource="{x:Bind ViewModel.ActionArgsVM.ArgValues, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</local:SettingContainer>
<!-- Delete command button -->
<local:SettingContainer x:Name="DeleteCommand"
x:Uid="EditAction_DeleteCommand">
<Button IsEnabled="{x:Bind ViewModel.IsUserAction, Mode=OneWay}"
Style="{StaticResource DeleteButtonStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<FontIcon FontSize="{StaticResource StandardIconSize}"
Glyph="&#xE74D;" />
<TextBlock x:Uid="Actions_DeleteButton2"
Style="{StaticResource IconButtonTextBlockStyle}" />
<Border MaxWidth="{StaticResource StandardControlMaxWidth}"
Margin="{StaticResource SettingStackMargin}">
<Grid Margin="{StaticResource SettingStackMargin}"
HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="Actions_CommandDetails"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,0,0,12"
VerticalAlignment="Center"
Style="{StaticResource TextBlockGroupingStyle}" />
<TextBlock x:Uid="Actions_Name"
Grid.Row="1"
Grid.Column="0"
Margin="0,0,0,8"
VerticalAlignment="Center" />
<TextBox x:Name="CommandNameTextBox"
Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,8"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind ViewModel.ActionNameTextBoxAutomationPropName}"
PlaceholderText="{x:Bind ViewModel.DisplayName, Mode=OneWay}"
Text="{x:Bind ViewModel.Name, Mode=TwoWay}" />
<TextBlock x:Uid="Actions_ShortcutAction"
Grid.Row="2"
Grid.Column="0"
Margin="0,0,0,12"
VerticalAlignment="Center" />
<AutoSuggestBox x:Name="ShortcutActionBox"
Grid.Row="2"
Grid.Column="1"
Margin="0,0,0,12"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind ViewModel.ShortcutActionComboBoxAutomationPropName}"
GotFocus="ShortcutActionBox_GotFocus"
LostFocus="ShortcutActionBox_LostFocus"
QuerySubmitted="ShortcutActionBox_QuerySubmitted"
TextChanged="ShortcutActionBox_TextChanged" />
<TextBlock x:Uid="Actions_Keybindings"
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,0,0,12"
VerticalAlignment="Center"
Style="{StaticResource TextBlockGroupingStyle}" />
<ListView x:Name="KeyChordListView"
x:Uid="Actions_KeyBindingsListView"
Grid.Row="4"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,0,0,12"
ItemTemplate="{StaticResource KeyChordTemplate}"
ItemsSource="{x:Bind ViewModel.KeyChordList, Mode=OneWay}"
SelectionMode="None">
<ListView.Footer>
<Button Margin="0,4,0,0"
Click="{x:Bind ViewModel.AddKeybinding_Click}">
<TextBlock x:Uid="Actions_AddKeyChord" />
</Button>
</ListView.Footer>
</ListView>
<TextBlock x:Uid="Actions_Arguments"
Grid.Row="5"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,0,0,12"
VerticalAlignment="Center"
Style="{StaticResource TextBlockGroupingStyle}"
Visibility="{x:Bind ViewModel.ActionArgsVM.HasArgs, Mode=OneWay}" />
<ItemsControl Grid.Row="6"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,0,0,12"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Bind ViewModel.AdditionalArgumentsControlAutomationPropName}"
IsTabStop="False"
ItemTemplateSelector="{StaticResource ArgsTemplateSelector}"
ItemsSource="{x:Bind ViewModel.ActionArgsVM.ArgValues, Mode=OneWay}" />
<Button Grid.Row="7"
Grid.Column="0"
IsEnabled="{x:Bind ViewModel.IsUserAction, Mode=OneWay}"
Style="{StaticResource DeleteButtonStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<FontIcon FontSize="{StaticResource StandardIconSize}"
Glyph="&#xE74D;" />
<TextBlock x:Uid="Actions_DeleteButton2"
Style="{StaticResource IconButtonTextBlockStyle}" />
</StackPanel>
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBlock x:Uid="Actions_CommandDeleteConfirmationMessage"
Style="{StaticResource CustomFlyoutTextStyle}" />
<Button x:Uid="Actions_CommandDeleteConfirmationButton"
Click="{x:Bind ViewModel.Delete_Click}" />
</StackPanel>
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBlock x:Uid="Actions_CommandDeleteConfirmationMessage"
Style="{StaticResource CustomFlyoutTextStyle}" />
<Button x:Uid="Actions_CommandDeleteConfirmationButton"
Click="{x:Bind ViewModel.Delete_Click}" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</local:SettingContainer>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
</Border>
</Page>

View File

@@ -1,131 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "KeyChordVisual.h"
#include "KeyChordVisual.g.cpp"
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::Foundation;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
DependencyProperty KeyChordVisual::_KeyChordProperty{ nullptr };
KeyChordVisual::KeyChordVisual()
{
InitializeComponent();
_InitializeProperties();
}
void KeyChordVisual::_InitializeProperties()
{
if (!_KeyChordProperty)
{
_KeyChordProperty =
DependencyProperty::Register(
L"KeyChord",
xaml_typename<Control::KeyChord>(),
xaml_typename<Editor::KeyChordVisual>(),
PropertyMetadata{ nullptr, PropertyChangedCallback{ &KeyChordVisual::_OnKeyChordChanged } });
}
}
void KeyChordVisual::_OnKeyChordChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
{
if (const auto control{ d.try_as<Editor::KeyChordVisual>() })
{
const auto controlImpl{ get_self<KeyChordVisual>(control) };
controlImpl->_UpdateKeyVisuals();
}
}
// Capitalizes the first character of the provided string.
// Examples: "enter" -> "Enter", "f1" -> "F1", "v" -> "V"
static winrt::hstring _formatMainKeyName(std::wstring_view part)
{
if (part.empty())
{
return {};
}
std::wstring buffer{ part };
buffer[0] = til::toupper_ascii(buffer[0]);
return winrt::hstring{ buffer };
}
void KeyChordVisual::_UpdateKeyVisuals()
{
auto panel{ KeysPanel() };
if (!panel)
{
return;
}
panel.Children().Clear();
const auto kc{ KeyChord() };
if (!kc)
{
return;
}
// Reuse the canonical serialization so the key naming stays in sync with the
// rest of the app. Then split on '+' (no key name in the table contains a literal
// '+'; VK_OEM_PLUS serializes as "plus") and render each part as its own visual.
const auto serialized{ Model::KeyChordSerialization::ToString(kc) };
if (serialized.empty())
{
return;
}
const std::wstring_view full{ serialized };
for (const auto part : til::split_iterator{ full, L'+' })
{
if (til::equals_insensitive_ascii(part, L"win"))
{
_AddGlyphKey();
}
else if (til::equals_insensitive_ascii(part, L"ctrl"))
{
_AddTextKey(L"Ctrl");
}
else if (til::equals_insensitive_ascii(part, L"alt"))
{
_AddTextKey(L"Alt");
}
else if (til::equals_insensitive_ascii(part, L"shift"))
{
_AddTextKey(L"Shift");
}
else
{
_AddTextKey(_formatMainKeyName(part));
}
}
}
void KeyChordVisual::_AddTextKey(const winrt::hstring& text)
{
const auto tmpl{ Resources().Lookup(box_value(L"KeyChordVisualTextKeyTemplate")).as<DataTemplate>() };
const auto border{ tmpl.LoadContent().as<Border>() };
if (const auto tb{ border.Child().try_as<TextBlock>() })
{
tb.Text(text);
}
KeysPanel().Children().Append(border);
}
void KeyChordVisual::_AddGlyphKey()
{
const auto tmpl{ Resources().Lookup(box_value(L"KeyChordVisualWindowsKeyTemplate")).as<DataTemplate>() };
const auto border{ tmpl.LoadContent().as<Border>() };
// Provide an accessible name for the glyph since it has no text fallback.
if (const auto path{ border.Child() })
{
Automation::AutomationProperties::SetName(path, L"Win");
}
KeysPanel().Children().Append(border);
}
}

View File

@@ -1,31 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "KeyChordVisual.g.h"
#include "Utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct KeyChordVisual : KeyChordVisualT<KeyChordVisual>
{
public:
KeyChordVisual();
DEPENDENCY_PROPERTY(Control::KeyChord, KeyChord);
private:
static void _InitializeProperties();
static void _OnKeyChordChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
void _UpdateKeyVisuals();
void _AddTextKey(const winrt::hstring& text);
void _AddGlyphKey();
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(KeyChordVisual);
}

View File

@@ -1,13 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass KeyChordVisual : Windows.UI.Xaml.Controls.UserControl
{
KeyChordVisual();
Microsoft.Terminal.Control.KeyChord KeyChord;
static Windows.UI.Xaml.DependencyProperty KeyChordProperty { get; };
}
}

View File

@@ -1,82 +0,0 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<UserControl x:Class="Microsoft.Terminal.Settings.Editor.KeyChordVisual"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
HorizontalAlignment="Right"
VerticalAlignment="Center"
IsTabStop="False"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<StaticResource x:Key="KeyChordVisualKeyBackground"
ResourceKey="AccentFillColorDefaultBrush" />
<StaticResource x:Key="KeyChordVisualKeyForeground"
ResourceKey="TextOnAccentFillColorPrimaryBrush" />
<StaticResource x:Key="KeyChordVisualKeyBorderBrush"
ResourceKey="AccentControlElevationBorderBrush" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<StaticResource x:Key="KeyChordVisualKeyBackground"
ResourceKey="SystemColorButtonFaceColorBrush" />
<StaticResource x:Key="KeyChordVisualKeyForeground"
ResourceKey="SystemColorButtonTextColorBrush" />
<StaticResource x:Key="KeyChordVisualKeyBorderBrush"
ResourceKey="SystemColorButtonTextColorBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Style x:Key="KeyChordVisualKeyBorderStyle"
TargetType="Border">
<Setter Property="MinWidth" Value="32" />
<Setter Property="MinHeight" Value="28" />
<Setter Property="Padding" Value="8,2,8,2" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Background" Value="{ThemeResource KeyChordVisualKeyBackground}" />
<Setter Property="BorderBrush" Value="{ThemeResource KeyChordVisualKeyBorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
</Style>
<Style x:Key="KeyChordVisualKeyTextStyle"
TargetType="TextBlock">
<Setter Property="FontSize" Value="13" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="Foreground" Value="{ThemeResource KeyChordVisualKeyForeground}" />
</Style>
<DataTemplate x:Key="KeyChordVisualTextKeyTemplate">
<Border Style="{StaticResource KeyChordVisualKeyBorderStyle}">
<TextBlock Style="{StaticResource KeyChordVisualKeyTextStyle}" />
</Border>
</DataTemplate>
<DataTemplate x:Key="KeyChordVisualWindowsKeyTemplate">
<Border Style="{StaticResource KeyChordVisualKeyBorderStyle}">
<Path Width="11"
Height="11"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M9 20H0V11H9V20ZM20 20H11V11H20V20ZM9 9H0V0H9V9ZM20 9H11V0H20V9Z"
Fill="{ThemeResource KeyChordVisualKeyForeground}"
Stretch="Uniform" />
</Border>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel x:Name="KeysPanel"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="2" />
</UserControl>

View File

@@ -86,9 +86,6 @@
<ClInclude Include="KeyChordListener.h">
<DependentUpon>KeyChordListener.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="KeyChordVisual.h">
<DependentUpon>KeyChordVisual.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="Launch.h">
<DependentUpon>Launch.xaml</DependentUpon>
</ClInclude>
@@ -219,9 +216,6 @@
<Page Include="KeyChordListener.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="KeyChordVisual.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="Launch.xaml">
<SubType>Designer</SubType>
</Page>
@@ -303,9 +297,6 @@
<ClCompile Include="KeyChordListener.cpp">
<DependentUpon>KeyChordListener.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="KeyChordVisual.cpp">
<DependentUpon>KeyChordVisual.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="Launch.cpp">
<DependentUpon>Launch.xaml</DependentUpon>
</ClCompile>
@@ -436,10 +427,6 @@
<DependentUpon>KeyChordListener.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="KeyChordVisual.idl">
<DependentUpon>KeyChordVisual.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="Launch.idl">
<DependentUpon>Launch.xaml</DependentUpon>
<SubType>Code</SubType>

View File

@@ -54,7 +54,6 @@
<Page Include="SettingContainerStyle.xaml" />
<Page Include="AddProfile.xaml" />
<Page Include="KeyChordListener.xaml" />
<Page Include="KeyChordVisual.xaml" />
<Page Include="NullableColorPicker.xaml" />
<Page Include="IconPicker.xaml" />
<Page Include="NewTabMenu.xaml" />

View File

@@ -289,8 +289,8 @@
<comment>Header for a control to toggle whether the terminal should automatically detect URLs and make them clickable, or not.</comment>
</data>
<data name="Globals_SearchWebDefaultQueryUrl.Header" xml:space="preserve">
<value>Default URL to use for the "Search web" shortcut</value>
<comment>Header for a control to set the query URL when using the "search web" shortcut.</comment>
<value>Default URL to use for the "Search web" action</value>
<comment>Header for a control to set the query URL when using the "search web" action.</comment>
</data>
<data name="Globals_SearchWebDefaultQueryUrl.HelpText" xml:space="preserve">
<value>The placeholder "%s" will be replaced with the search query.</value>
@@ -590,7 +590,7 @@
<comment>Additional description for what the "force vt input" setting does. Presented near "Globals_ForceVTInput.Header".</comment>
</data>
<data name="Globals_AllowHeadless.HelpText" xml:space="preserve">
<value>This allows shortcuts such as Global Summon and Quake Mode shortcuts to work even when no windows are open.</value>
<value>This allows actions such as Global Summon and Quake Mode actions to work even when no windows are open.</value>
<comment>Additional description for what the "allow headless" setting does. Presented near "Globals_AllowHeadless.Header".</comment>
</data>
<data name="Globals_TabWidthMode.Header" xml:space="preserve">
@@ -682,12 +682,12 @@
<comment>Header for the "rendering" menu item. This navigates to a page that lets you see and modify settings related to the app's rendering of text in the terminal.</comment>
</data>
<data name="Nav_Actions.Content" xml:space="preserve">
<value>Shortcuts</value>
<comment>Header for the "shortcuts" menu item. This navigates to a page that lets you see the available keyboard shortcuts in the app. (Resource key kept as "Nav_Actions" for stability.)</comment>
<value>Actions</value>
<comment>Header for the "actions" menu item. This navigates to a page that lets you see the available commands in the app.</comment>
</data>
<data name="Nav_EditAction.Content" xml:space="preserve">
<value>Edit Shortcut...</value>
<comment>Header for the "edit shortcut" page. This is the page that lets you modify a specific command and its key bindings. (Resource key kept as "Nav_EditAction" for stability.)</comment>
<value>Edit Action...</value>
<comment>Header for the "edit action" page. This is the page that lets you modify a specific command and its key bindings.</comment>
</data>
<data name="Nav_Extensions.Content" xml:space="preserve">
<value>Extensions</value>
@@ -1766,28 +1766,28 @@
<comment>A description for what the delete unfocused appearance button does.</comment>
</data>
<data name="Actions_Disclaimer.Content" xml:space="preserve">
<value>Learn more about shortcuts</value>
<comment>Disclaimer presented at the top of the shortcuts page to redirect the user to documentation regarding keyboard shortcuts.</comment>
<value>Learn more about actions</value>
<comment>Disclaimer presented at the top of the actions page to redirect the user to documentation regarding actions.</comment>
</data>
<data name="Actions_DeleteButton2.Text" xml:space="preserve">
<value>Delete shortcut</value>
<comment>Button label that deletes the selected shortcut.</comment>
<value>Delete action</value>
<comment>Button label that deletes the selected action.</comment>
</data>
<data name="Actions_Name.Text" xml:space="preserve">
<value>Shortcut name</value>
<comment>Label for the text box that edits the shortcut name.</comment>
<value>Action name</value>
<comment>Label for the text box that edits the action name.</comment>
</data>
<data name="Actions_NameEntryBox.PlaceholderText" xml:space="preserve">
<value>Shortcut name</value>
<comment>Placeholder text for the text box where the user can edit the shortcut name.</comment>
<value>Action name</value>
<comment>Placeholder text for the text box where the user can edit the action name.</comment>
</data>
<data name="Actions_ShortcutAction.Text" xml:space="preserve">
<value>Shortcut type</value>
<comment>Label for the combo box that edits the shortcut type (which action runs when this shortcut is invoked).</comment>
<value>Action type</value>
<comment>Label for the combo box that edits the action type.</comment>
</data>
<data name="Actions_KeyBindingsListView.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Shortcuts</value>
<comment>Name for a control which contains the list of keyboard shortcuts for the current command.</comment>
<value>Keybindings</value>
<comment>Name for a control which contains the list of keybindings for the current command.</comment>
</data>
<data name="Actions_CommandDetails.Text" xml:space="preserve">
<value>Command details</value>
@@ -1798,44 +1798,44 @@
<comment>Label for the list of editable arguments for the currently selected action.</comment>
</data>
<data name="Actions_Keybindings.Text" xml:space="preserve">
<value>Shortcuts</value>
<comment>Label for the list of editable keyboard shortcuts for the current command.</comment>
<value>Keybindings</value>
<comment>Label for the list of editable keybindings for the current command.</comment>
</data>
<data name="Actions_AddKeyChord.Text" xml:space="preserve">
<value>Add shortcut</value>
<comment>Button label that adds a keyboard shortcut to the current action.</comment>
<value>Add keybinding</value>
<comment>Button label that adds a keybinding to the current action.</comment>
</data>
<data name="Actions_AdditionalKeyChords" xml:space="preserve">
<value>and {} more</value>
<comment>Text that will be read out by a screen reader indicating that additional keybindings exist for this command. {} will be replaced by the number of additional keybindings.</comment>
</data>
<data name="Actions_DeleteConfirmationButton.Content" xml:space="preserve">
<value>Yes, delete shortcut</value>
<comment>Button label that confirms deletion of a keyboard shortcut entry.</comment>
<value>Yes, delete key binding</value>
<comment>Button label that confirms deletion of a key binding entry.</comment>
</data>
<data name="Actions_DeleteConfirmationMessage.Text" xml:space="preserve">
<value>Are you sure you want to delete this shortcut?</value>
<comment>Confirmation message displayed when the user attempts to delete a keyboard shortcut entry.</comment>
<value>Are you sure you want to delete this key binding?</value>
<comment>Confirmation message displayed when the user attempts to delete a key binding entry.</comment>
</data>
<data name="Actions_CommandDeleteConfirmationButton.Content" xml:space="preserve">
<value>Yes, delete shortcut</value>
<comment>Button label that confirms deletion of a shortcut entry.</comment>
<value>Yes, delete action</value>
<comment>Button label that confirms deletion of an action.</comment>
</data>
<data name="Actions_CommandDeleteConfirmationMessage.Text" xml:space="preserve">
<value>Are you sure you want to delete this shortcut?</value>
<comment>Confirmation message displayed when the user attempts to delete a shortcut entry.</comment>
<value>Are you sure you want to delete this action?</value>
<comment>Confirmation message displayed when the user attempts to delete an action.</comment>
</data>
<data name="Actions_InvalidKeyChordMessage" xml:space="preserve">
<value>Invalid shortcut. Please enter a valid keyboard shortcut.</value>
<comment>Error message displayed when an invalid keyboard shortcut is input by the user.</comment>
<value>Invalid key chord. Please enter a valid key chord.</value>
<comment>Error message displayed when an invalid key chord is input by the user.</comment>
</data>
<data name="Actions_RenameConflictConfirmationAcceptButton" xml:space="preserve">
<value>Yes</value>
<comment>Button label that confirms the deletion of a conflicting key binding to allow the current key binding to be registered.</comment>
</data>
<data name="Actions_RenameConflictConfirmationMessage" xml:space="preserve">
<value>The provided shortcut is already being used by the following action:</value>
<comment>Error message displayed when a keyboard shortcut that is already in use is input by the user. The name of the conflicting shortcut is displayed after this message.</comment>
<value>The provided key chord is already being used by the following action:</value>
<comment>Error message displayed when a key chord that is already in use is input by the user. The name of the conflicting key chord is displayed after this message.</comment>
</data>
<data name="Actions_RenameConflictConfirmationQuestion" xml:space="preserve">
<value>Would you like to overwrite it?</value>
@@ -1862,60 +1862,12 @@
<comment>Text label for a button that can be used to begin making changes to a key binding entry.</comment>
</data>
<data name="Actions_AddNewTextBlock.Text" xml:space="preserve">
<value>Add new shortcut</value>
<comment>Button label that creates a new keyboard shortcut on the shortcuts page.</comment>
<value>Add new</value>
<comment>Button label that creates a new action on the actions page.</comment>
</data>
<data name="Actions_ViewAllKeyChordsButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>View all shortcuts</value>
<comment>Tooltip for a button on each row of the Shortcuts page that opens a flyout listing every keyboard shortcut registered for that action.</comment>
</data>
<data name="Actions_NoKeyBindings.Text" xml:space="preserve">
<value>No shortcuts</value>
<comment>Empty-state message shown inside the "view all shortcuts" flyout (opened from each row's "..." button on the Shortcuts page) when the action has no keyboard shortcuts registered.</comment>
</data>
<data name="EditAction_ActionType.Header" xml:space="preserve">
<value>Shortcut type</value>
<comment>Header for the shortcut-type setting on the Edit Shortcut page (lets the user pick which action runs when this shortcut is invoked).</comment>
</data>
<data name="EditAction_ActionName.Header" xml:space="preserve">
<value>Shortcut name</value>
<comment>Header for the shortcut-name setting on the Edit Shortcut page (lets the user provide a friendly name for this shortcut).</comment>
</data>
<data name="EditAction_KeyBindings.Header" xml:space="preserve">
<value>Shortcuts</value>
<comment>Header for the shortcuts expander on the Edit Shortcut page.</comment>
</data>
<data name="EditAction_KeyBindings.HelpText" xml:space="preserve">
<value>Customize keyboard shortcuts to speed up common actions and workflows.</value>
<comment>Help text shown under the "Shortcuts" header on the Edit Shortcut page.</comment>
</data>
<data name="EditAction_NewKeyBinding.Header" xml:space="preserve">
<value>New shortcut</value>
<comment>Header for the "add a new shortcut" row on the Edit Shortcut page.</comment>
</data>
<data name="EditAction_AddKeyBinding.Content" xml:space="preserve">
<value>Add shortcut</value>
<comment>Label for the accent button that appends a new keyboard shortcut to the current action.</comment>
</data>
<data name="EditAction_AdditionalCustomizations.Header" xml:space="preserve">
<value>Additional customizations</value>
<comment>Header for the additional customizations expander on the Edit Action page (contains action name and per-action argument settings).</comment>
</data>
<data name="EditAction_AdditionalCustomizations.HelpText" xml:space="preserve">
<value>Fine tune how shortcuts behave.</value>
<comment>Help text shown under the "Additional customizations" header on the Edit Shortcut page.</comment>
</data>
<data name="EditAction_KeyBindingNumberFormat" xml:space="preserve">
<value>Shortcut #{}</value>
<comment>{Locked="#{}"} Header label for an individual keyboard shortcut inside the "Shortcuts" expander on the Edit Shortcut page. {} is replaced with the 1-based index of the shortcut (e.g. "Shortcut #1").</comment>
</data>
<data name="EditAction_DeleteCommand.Header" xml:space="preserve">
<value>Delete this shortcut</value>
<comment>Header for the setting container that holds the "Delete shortcut" button at the bottom of the Edit Shortcut page.</comment>
</data>
<data name="EditAction_DeleteCommand.HelpText" xml:space="preserve">
<value>This shortcut will be removed and any shortcuts associated with it will no longer work.</value>
<comment>Help text shown under the "Delete this shortcut" setting container on the Edit Shortcut page.</comment>
<data name="Actions_ActionComboBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Action</value>
<comment>Label for a control that sets the action of a key binding.</comment>
</data>
<data name="Actions_NullEnumValue" xml:space="preserve">
<value>Use global setting</value>

View File

@@ -121,11 +121,6 @@
<Thickness x:Key="SettingContainerIconMargin">0,4,8,4</Thickness>
<x:Double x:Key="SettingContainerIconFontSize">16</x:Double>
<!-- Shared sizing for SettingContainer-style "card" rows (used by SettingsCardStyle and Actions page rows). -->
<Thickness x:Key="SettingsCardPadding">16,8,8,8</Thickness>
<x:Double x:Key="SettingsCardMinHeight">64</x:Double>
<Thickness x:Key="SettingsCardItemMargin">0,4,0,0</Thickness>
<Style x:Key="StackPanelInExpanderStyle"
TargetType="StackPanel">
<Setter Property="VerticalAlignment" Value="Center" />
@@ -162,21 +157,6 @@
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
</Style>
<!--
Similar to a local:SettingContainer without actually being one.
Used by data-templated rows (i.e. Actions page).
-->
<Style x:Key="SettingsCardStyle"
TargetType="Grid">
<Setter Property="MinHeight" Value="{StaticResource SettingsCardMinHeight}" />
<Setter Property="Padding" Value="{StaticResource SettingsCardPadding}" />
<Setter Property="Background" Value="{ThemeResource ExpanderHeaderBackground}" />
<Setter Property="BorderBrush" Value="{ThemeResource ExpanderHeaderBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ExpanderHeaderBorderThickness}" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
<Style x:Key="SettingsPageItemHeaderStyle"
BasedOn="{StaticResource BodyTextBlockStyle}"
TargetType="TextBlock">

View File

@@ -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" };

View File

@@ -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())

View File

@@ -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);

View File

@@ -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; };

View File

@@ -162,6 +162,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") },
};
}();

View File

@@ -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

View File

@@ -20,6 +20,7 @@ static constexpr std::string_view TabLayoutKey{ "tabLayout" };
static constexpr std::string_view InitialPositionKey{ "initialPosition" };
static constexpr std::string_view InitialSizeKey{ "initialSize" };
static constexpr std::string_view LaunchModeKey{ "launchMode" };
static constexpr std::string_view PersistedWorkspacesKey{ "persistedWorkspaces" };
namespace Microsoft::Terminal::Settings::Model::JsonUtils
{
@@ -276,6 +277,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
// Manually handled because IMap<K,V> has a comma that breaks the X-macro.
if (WI_IsFlagSet(parseSource, FileSource::Local))
state->PersistedWorkspaces = JsonUtils::GetValueForKey<std::optional<Windows::Foundation::Collections::IMap<hstring, Model::WindowLayout>>>(root, PersistedWorkspacesKey);
}
Json::Value ApplicationState::ToJson(FileSource parseSource) const noexcept
@@ -298,6 +303,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
// Manually handled because IMap<K,V> has a comma that breaks the X-macro.
if (WI_IsFlagSet(parseSource, FileSource::Local))
JsonUtils::SetValueForKey(root, PersistedWorkspacesKey, state->PersistedWorkspaces);
}
return root;
}
@@ -341,6 +350,114 @@ 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.
// Return Value:
// - true if an entry was renamed, false otherwise.
bool ApplicationState::RenameWorkspace(const hstring& oldName, const hstring& newName)
{
if (oldName == newName || oldName.empty() || newName.empty())
{
return false;
}
bool renamed{ false };
{
const auto state = _state.lock();
if (state->PersistedWorkspaces && *state->PersistedWorkspaces)
{
auto map = *state->PersistedWorkspaces;
if (map.HasKey(oldName))
{
const auto layout = map.Lookup(oldName);
map.Insert(newName, layout);
map.Remove(oldName);
renamed = true;
}
}
}
if (renamed)
{
_throttler();
}
return renamed;
}
// 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 \

View File

@@ -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
{
@@ -75,6 +75,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; \
@@ -88,6 +94,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
// Manually declared because IMap<K,V> has a comma that breaks the macro.
std::optional<Windows::Foundation::Collections::IMap<hstring, Model::WindowLayout>> PersistedWorkspaces;
};
til::shared_mutex<state_t> _state;
std::filesystem::path _sharedPath;

View File

@@ -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;

View File

@@ -102,6 +102,14 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_DisabledProfileSources->Append(src);
}
}
if (_SafeUriSchemes)
{
globals->_SafeUriSchemes = winrt::single_threaded_vector<hstring>();
for (const auto& src : *_SafeUriSchemes)
{
globals->_SafeUriSchemes->Append(src);
}
}
for (const auto& parent : _parents)
{

View File

@@ -114,6 +114,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_SETTING(Boolean, EnableUnfocusedAcrylic);
INHERITABLE_SETTING(Boolean, AllowHeadless);
INHERITABLE_SETTING(String, SearchWebDefaultQueryUrl);
INHERITABLE_SETTING(IVector<String>, SafeUriSchemes);
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
void AddColorScheme(ColorScheme scheme);

View File

@@ -136,7 +136,7 @@ private: \
return std::nullopt; \
} \
\
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
auto _get##name##OverrideSourceImpl() -> decltype(get_strong()) \
{ \
/*we have a value*/ \
if (_##name) \
@@ -159,7 +159,7 @@ private: \
} \
\
auto _get##name##OverrideSourceAndValueImpl() \
->std::pair<decltype(get_strong()), storageType> \
-> std::pair<decltype(get_strong()), storageType> \
{ \
/*we have a value*/ \
if (_##name) \

View File

@@ -63,6 +63,7 @@ Author(s):
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, SafeUriSchemes, "safeUriSchemes", nullptr) \
X(bool, ShowAdminShield, "showAdminShield", true) \
X(bool, TrimPaste, "trimPaste", true) \
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
@@ -160,7 +161,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, ShowWindowsButton, "showWindowsButton", true)
#define MTSM_THEME_SETTINGS_SETTINGS(X) \
X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "theme", winrt::Windows::UI::Xaml::ElementTheme::Default)

View File

@@ -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>

View File

@@ -62,6 +62,7 @@ namespace Microsoft.Terminal.Settings.Model
Windows.UI.Xaml.ElementTheme RequestedTheme { get; };
Boolean UseMica { get; };
Boolean RainbowFrame { get; };
Boolean ShowWindowsButton { get; };
ThemeColor Frame { get; };
ThemeColor UnfocusedFrame { get; };
}

View File

@@ -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.

View File

@@ -0,0 +1,127 @@
// 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"));
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"));
}
}

View File

@@ -467,6 +467,7 @@ namespace SettingsModelUnitTests
"$schema" : "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
"safeUriSchemes": [ "vscode" ],
"newTabMenu":
[
{

View File

@@ -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>

View File

@@ -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"

View File

@@ -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;
@@ -265,12 +270,14 @@ 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 });
// BODGY
// On certain builds of Windows, when Terminal is set as the default
@@ -410,6 +417,28 @@ 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);
}
}
LaunchPosition AppHost::_GetWindowLaunchPosition()
{
LaunchPosition pos{};
@@ -1065,6 +1094,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)
{

View File

@@ -91,6 +91,9 @@ 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);
@@ -133,6 +136,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
@@ -154,6 +159,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;
@@ -161,6 +167,7 @@ private:
winrt::TerminalApp::TerminalWindow::RequestMoveContent_revoker RequestMoveContent;
winrt::TerminalApp::TerminalWindow::RequestReceiveContent_revoker RequestReceiveContent;
winrt::TerminalApp::TerminalWindow::RequestLaunchPosition_revoker RequestLaunchPosition;
winrt::TerminalApp::TerminalWindow::RequestWindowList_revoker RequestWindowList;
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
winrt::TerminalApp::TerminalWindow::WindowSizeChanged_revoker WindowSizeChanged;

View File

@@ -803,6 +803,19 @@ void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs arg
{
winrt::TerminalApp::WindowRequestedArgs request{ windowId, std::move(args) };
request.WindowName(std::move(windowName));
// If we're opening a named window that doesn't exist yet, atomically
// claim any persisted workspace with that name so we restore it here
// and no subsequent window can pick up the same entry.
const auto& reqName = request.WindowName();
if (!reqName.empty())
{
if (const auto layout = ApplicationState::SharedInstance().TakeWorkspace(reqName))
{
request.PersistedLayout(layout);
}
}
CreateNewWindow(std::move(request));
}
}
@@ -1087,6 +1100,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 +1143,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))
{

View File

@@ -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;

View File

@@ -123,6 +123,7 @@ class ScreenBufferTests
TEST_METHOD(VtResizePreservingAttributes);
TEST_METHOD(VtSoftResetCursorPosition);
TEST_METHOD(VtSoftResetAltBufferCursorState);
TEST_METHOD(VtScrollMarginsNewlineColor);
@@ -1510,6 +1511,30 @@ void ScreenBufferTests::VtSoftResetCursorPosition()
VERIFY_ARE_EQUAL(til::point(1, 1), cursor.GetPosition());
}
void ScreenBufferTests::VtSoftResetAltBufferCursorState()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole(); // Lock must be taken to manipulate buffer.
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
auto& si = gci.GetActiveOutputBuffer();
auto& stateMachine = si.GetStateMachine();
Log::Comment(L"Move cursor on the main buffer.");
stateMachine.ProcessString(L"\x1b[4;7H");
VERIFY_ARE_EQUAL(til::point(6, 3), si.GetTextBuffer().GetCursor().GetPosition());
Log::Comment(L"Enter alt buffer, soft reset, and return to main buffer.");
stateMachine.ProcessString(L"\x1b[?1049h");
VERIFY_IS_TRUE(gci.GetActiveOutputBuffer()._IsAltBuffer());
stateMachine.ProcessString(L"\x1b[!p");
stateMachine.ProcessString(L"\x1b[?1049l");
VERIFY_IS_FALSE(gci.GetActiveOutputBuffer()._IsAltBuffer());
Log::Comment(L"Returning from alt buffer should restore the main cursor position.");
VERIFY_ARE_EQUAL(til::point(6, 3), gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().GetPosition());
}
void ScreenBufferTests::VtScrollMarginsNewlineColor()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();

View File

@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace til
{
namespace details

View File

@@ -90,7 +90,15 @@ std::function<bool(wchar_t)> SixelParser::DefineImage(const VTInt macroParameter
_state = States::Normal;
_parameters.clear();
return [&](const auto ch) {
_parseCommandChar(ch);
try
{
_parseCommandChar(ch);
}
catch (...)
{
// Ignore all further content.
return false;
}
return true;
};
}
@@ -234,10 +242,18 @@ void SixelParser::_executeNextLine()
_executeCarriageReturn();
_imageLineCount++;
_maybeFlushImageBuffer();
_imageCursor.y += _sixelHeight;
_availablePixelHeight -= _sixelHeight;
_resizeImageBuffer(_sixelHeight);
_fillImageBackgroundWhenScrolled();
// If we don't have any available pixel height, that means the image has
// extended beyond the bottom of the display and we haven't triggered a
// a scroll (because sixel display mode is enabled). In this state, there
// is no point in extending the image any further, because the additional
// content will never be seen, so we'll just be wasting memory.
if (_availablePixelHeight > 0)
{
_imageCursor.y += _sixelHeight;
_availablePixelHeight -= _sixelHeight;
_resizeImageBuffer(_sixelHeight);
_fillImageBackgroundWhenScrolled();
}
}
void SixelParser::_executeMoveToHome()

View File

@@ -3002,17 +3002,15 @@ void AdaptDispatch::SoftReset()
SetGraphicsRendition({}); // Normal rendition.
SetCharacterProtectionAttribute({}); // Default (unprotected)
// Reset the saved cursor state.
// Note that XTerm only resets the main buffer state, but that
// seems likely to be a bug. Most other terminals reset both.
_savedCursorState.at(0) = {}; // Main buffer
_savedCursorState.at(1) = {}; // Alt buffer
// Reset only the active saved cursor state.
// This matches xterm behavior when DECSTR is processed while using
// the alternate screen buffer (GH#19918).
_savedCursorState.at(_usingAltBuffer ? 1 : 0) = {};
// The TerminalOutput state in these buffers must be reset to
// The TerminalOutput state in this buffer must be reset to
// the same state as the _termOutput instance, which is not
// necessarily equivalent to a full reset.
_savedCursorState.at(0).TermOutput = _termOutput;
_savedCursorState.at(1).TermOutput = _termOutput;
_savedCursorState.at(_usingAltBuffer ? 1 : 0).TermOutput = _termOutput;
// Soft reset the Sixel parser if in use.
if (_sixelParser)

View File

@@ -416,7 +416,7 @@ function Invoke-CodeFormat() {
[switch]$IgnoreXaml
)
$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"
If ([String]::IsNullOrEmpty($clangFormatPath)) {
Write-Error "No Visual Studio-supplied version of clang-format could be found."
}

View File

@@ -2,4 +2,4 @@
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"