From de717211543303d9880fff4f00953e0ff9fd4d11 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 17 Jun 2026 13:02:36 -0700 Subject: [PATCH] fix search index + deep link navigation --- .../TerminalSettingsEditor/Appearances.cpp | 3 +- src/cascadia/TerminalSettingsEditor/Utils.cpp | 29 ++++++++++++ src/cascadia/TerminalSettingsEditor/Utils.h | 7 ++- tools/GenerateSettingsIndex.ps1 | 44 ++++++++++++++----- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index a652138a65..73fa1e5f8e 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -1151,7 +1151,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { if (const auto& controlToFocus{ strongThis->FindName(elementToFocus).try_as() }) { - winrt::Microsoft::Terminal::Settings::ExpandAncestorsAndBringIntoView(strongThis.as(), controlToFocus); + const auto& target{ winrt::Microsoft::Terminal::Settings::ResolveFocusTarget(controlToFocus) }; + winrt::Microsoft::Terminal::Settings::ExpandAncestorsAndBringIntoView(strongThis.as(), target); } strongThis->_loadedRevoker.revoke(); } diff --git a/src/cascadia/TerminalSettingsEditor/Utils.cpp b/src/cascadia/TerminalSettingsEditor/Utils.cpp index 81adcb3910..42d3dc0086 100644 --- a/src/cascadia/TerminalSettingsEditor/Utils.cpp +++ b/src/cascadia/TerminalSettingsEditor/Utils.cpp @@ -101,6 +101,35 @@ namespace winrt::Microsoft::Terminal::Settings return GetLibraryResourceString(fmtKey); } + // Returns the control that should actually receive focus for a resolved + // search-navigation target. + Controls::Control ResolveFocusTarget(const Controls::Control& element) + { + if (!element) + { + return element; + } + + winrt::Windows::Foundation::IInspectable content{ nullptr }; + if (const auto expander = element.try_as()) + { + content = expander.Content(); + } + else if (const auto card = element.try_as()) + { + content = card.Content(); + } + + if (content) + { + if (const auto contentControl = content.try_as()) + { + return contentControl; + } + } + return element; + } + void ExpandAncestorsAndBringIntoView(const FrameworkElement& root, const Controls::Control& control) { if (!control) diff --git a/src/cascadia/TerminalSettingsEditor/Utils.h b/src/cascadia/TerminalSettingsEditor/Utils.h index 424bc717de..5a74ecdfa4 100644 --- a/src/cascadia/TerminalSettingsEditor/Utils.h +++ b/src/cascadia/TerminalSettingsEditor/Utils.h @@ -79,6 +79,7 @@ namespace winrt::Microsoft::Terminal::Settings winrt::hstring GetSelectedItemTag(const winrt::Windows::Foundation::IInspectable& comboBoxAsInspectable); winrt::hstring LocalizedNameForEnumName(const std::wstring_view sectionAndType, const std::wstring_view enumValue, const std::wstring_view propertyType); void ExpandAncestorsAndBringIntoView(const winrt::Windows::UI::Xaml::FrameworkElement& root, const winrt::Windows::UI::Xaml::Controls::Control& control); + winrt::Windows::UI::Xaml::Controls::Control ResolveFocusTarget(const winrt::Windows::UI::Xaml::Controls::Control& element); } // BODGY! @@ -132,10 +133,14 @@ struct HasScrollViewer { if (const auto& controlToFocus{ page->FindName(elementName).try_as() }) { + // If the named element is a SettingsExpander/SettingsCard with an + // interactive control in its Content, focus that inner control instead. + const auto& target{ winrt::Microsoft::Terminal::Settings::ResolveFocusTarget(controlToFocus) }; + // We need to wait for the page to be loaded // or else the call to StartBringIntoView() // will end up doing nothing. - winrt::Microsoft::Terminal::Settings::ExpandAncestorsAndBringIntoView(page.template as(), controlToFocus); + winrt::Microsoft::Terminal::Settings::ExpandAncestorsAndBringIntoView(page.template as(), target); } page->_loadedRevoker.revoke(); } diff --git a/tools/GenerateSettingsIndex.ps1 b/tools/GenerateSettingsIndex.ps1 index fd98d6b6a7..02af7795fd 100644 --- a/tools/GenerateSettingsIndex.ps1 +++ b/tools/GenerateSettingsIndex.ps1 @@ -193,20 +193,40 @@ foreach ($xamlFile in Get-ChildItem -Path $SourceDir -Filter *.xaml) # Iterate over all local:SettingsCard and local:SettingsExpander nodes foreach ($settingContainer in ($xml.SelectNodes("//local:SettingsCard", $xm) + $xml.SelectNodes("//local:SettingsExpander", $xm))) { - # Extract Uid - if ($null -eq $settingContainer.Uid) + # Determine what to index for this container. A SettingContainer is indexable + # either via its own x:Uid (its label comes from the Header, resource suffix + # "/Header") OR, when it has none, via a content-labeled child control + # (CheckBox/ToggleSwitch/etc.) that carries its own x:Uid (its label comes from + # its Content, resource suffix "/Content"). The latter is wrapped in a SettingsCard + # that usually has no x:Uid of its own, so without this it would never be indexed. + $suffix = "Header" + $uid = $settingContainer.Uid + $name = $settingContainer.GetAttribute("x:Name") + + if ([string]::IsNullOrEmpty($uid)) { - # SettingsCard/SettingsExpander without x:Uid are not indexable — skip silently - continue + # No x:Uid on the container itself, look for a child control. + $child = $settingContainer.SelectNodes("*") | + Where-Object { @("CheckBox", "ToggleSwitch", "RadioButton", "ToggleButton") -contains $_.LocalName -and -not [string]::IsNullOrEmpty($_.GetAttribute("x:Uid")) } | + Select-Object -First 1 + if ($null -ne $child) + { + $suffix = "Content" + $uid = $child.GetAttribute("x:Uid") + # Prefer the control's own x:Name; otherwise fall back to the container's. + $childName = $child.GetAttribute("x:Name") + if (-not [string]::IsNullOrEmpty($childName)) + { + $name = $childName + } + } } - elseif ($ProhibitedUids -contains $settingContainer.Uid) + + if ([string]::IsNullOrEmpty($uid) -or ($ProhibitedUids -contains $uid)) { continue } - # Extract Name via GetAttribute to avoid PowerShell's XML integration - # returning the element name (e.g. "local:SettingsCard") when x:Name is absent. - $name = $settingContainer.GetAttribute("x:Name") if ([string]::IsNullOrEmpty($name)) { $name = "" @@ -227,7 +247,7 @@ foreach ($xamlFile in Get-ChildItem -Path $SourceDir -Filter *.xaml) $subPage = $ClassMap[$pageClass].SubPage ?? "BreadcrumbSubPage::None" if ($pageClass -match "Editor::NewTabMenu") { - if ($settingContainer.Uid -match "NewTabMenu_CurrentFolder") + if ($uid -match "NewTabMenu_CurrentFolder") { $navigationParam = $null # VM param at runtime $subPage = "BreadcrumbSubPage::NewTabMenu_Folder" @@ -254,7 +274,7 @@ foreach ($xamlFile in Get-ChildItem -Path $SourceDir -Filter *.xaml) if ($includeInBuildIndex) { $entries += [pscustomobject]@{ - ResourceName = "$($settingContainer.Uid)/Header" + ResourceName = "$uid/$suffix" ParentPage = $pageClass NavigationParam = $navigationParam SubPage = $subPage @@ -266,7 +286,7 @@ foreach ($xamlFile in Get-ChildItem -Path $SourceDir -Filter *.xaml) if ($includeInPartialIndex) { $entries += [pscustomobject]@{ - ResourceName = "$($settingContainer.Uid)/Header" + ResourceName = "$uid/$suffix" ParentPage = $pageClass NavigationParam = $null # VM param at runtime SubPage = $pageClass -match "Editor::NewTabMenu" ? "BreadcrumbSubPage::NewTabMenu_Folder" : $subPage @@ -455,4 +475,4 @@ Set-Content -LiteralPath $cppPath -Value $cpp -NoNewline Write-Host "Generated:" Write-Host " $headerPath" -Write-Host " $cppPath" \ No newline at end of file +Write-Host " $cppPath"