load runtime index for search

This commit is contained in:
Carlos Zamora 2025-10-23 11:47:20 -07:00
parent 1b8c99dff8
commit 915f085b60
5 changed files with 394 additions and 48 deletions

View File

@ -63,7 +63,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
MainPage::MainPage(const CascadiaSettings& settings) :
_settingsSource{ settings },
_settingsClone{ settings.Copy() }
_settingsClone{ settings.Copy() },
_profileVMs{ single_threaded_observable_vector<Editor::ProfileViewModel>() }
{
InitializeComponent();
_UpdateBackgroundForMica();
@ -152,6 +153,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Automation::AutomationProperties::SetHelpText(OpenJsonNavItem(), RS_(L"Nav_OpenJSON/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
_breadcrumbs = single_threaded_observable_vector<IInspectable>();
_UpdateSearchIndex();
}
// Method Description:
@ -266,6 +268,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto& firstItem{ _menuItemSource.GetAt(0).as<MUX::Controls::NavigationViewItem>() };
SettingsNav().SelectedItem(firstItem);
_Navigate(unbox_value<hstring>(firstItem.Tag()), BreadcrumbSubPage::None);
_UpdateSearchIndex();
}
void MainPage::SetHostingWindow(uint64_t hostingWindow) noexcept
@ -465,7 +469,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
if (clickedItemTag == launchTag)
{
_newTabMenuPageVM.CurrentFolder(nullptr);
contentFrame().Navigate(xaml_typename<Editor::Launch>(), winrt::make<NavigateToLaunchArgs>(winrt::make<LaunchViewModel>(_settingsClone)));
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Launch/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
@ -870,6 +873,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// Add an event handler for when the user wants to delete a profile.
profile.DeleteProfileRequested({ this, &MainPage::_DeleteProfile });
// Register the VM so that it appears in the search index
_profileVMs.Append(profile);
return profileNavItem;
}
@ -895,6 +901,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_menuItemSource.IndexOf(selectedItem, index);
_menuItemSource.RemoveAt(index);
// Remove it from the list of VMs
auto profileVM = selectedItem.as<MUX::Controls::NavigationViewItem>().Tag().as<Editor::ProfileViewModel>();
uint32_t vmIndex;
if (_menuItemSource.IndexOf(profileVM, vmIndex))
{
_profileVMs.RemoveAt(vmIndex);
}
// navigate to the profile next to this one
const auto newSelectedItem{ _menuItemSource.GetAt(index < _menuItemSource.Size() - 1 ? index : index - 1) };
SettingsNav().SelectedItem(newSelectedItem);
@ -1008,4 +1022,49 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
void MainPage::_UpdateSearchIndex()
{
_searchIndex = {};
const auto buildIndex = LoadBuildTimeIndex();
_searchIndex.reserve(buildIndex.size());
_searchIndex.assign(buildIndex.begin(), buildIndex.end());
// Load profiles
for (const auto& profile : _profileVMs)
{
const auto& profileIndex = LoadProfileIndex(profile);
_searchIndex.reserve(_searchIndex.size() + profileIndex.size());
_searchIndex.insert(_searchIndex.end(), profileIndex.begin(), profileIndex.end());
}
// Load new tab menu
for (const auto& folderVM : get_self<implementation::NewTabMenuViewModel>(_newTabMenuPageVM)->FolderTreeFlatList())
{
const auto& folderIndex = LoadNTMFolderIndex(folderVM);
_searchIndex.reserve(_searchIndex.size() + folderIndex.size());
_searchIndex.insert(_searchIndex.end(), folderIndex.begin(), folderIndex.end());
}
// Load extensions
// TODO CARLOS: annoying that we have to load the extensions to build the index. Can we defer this until the user searches?
get_self<ExtensionsViewModel>(_extensionsVM)->LazyLoadExtensions();
for (const auto& extPkg : _extensionsVM.ExtensionPackages())
{
const auto& extPkgIndex = LoadExtensionIndex(extPkg);
_searchIndex.reserve(_searchIndex.size() + extPkgIndex.size());
_searchIndex.insert(_searchIndex.end(), extPkgIndex.begin(), extPkgIndex.end());
}
// Load color schemes
for (const auto& schemeVM : _colorSchemesPageVM.AllColorSchemes())
{
const auto& schemeIndex = LoadColorSchemeIndex(schemeVM.Name());
_searchIndex.reserve(_searchIndex.size() + schemeIndex.size());
_searchIndex.insert(_searchIndex.end(), schemeIndex.begin(), schemeIndex.end());
}
// Load actions
// TODO CARLOS: postpone until actions page is updated
}
}

View File

@ -6,6 +6,7 @@
#include "MainPage.g.h"
#include "Breadcrumb.g.h"
#include "Utils.h"
#include "GeneratedSettingsIndex.g.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
@ -76,11 +77,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void _UpdateBackgroundForMica();
void _MoveXamlParsedNavItemsIntoItemSource();
void _UpdateSearchIndex();
Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Editor::ProfileViewModel> _profileVMs{ nullptr };
winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr };
winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr };
winrt::Microsoft::Terminal::Settings::Editor::ExtensionsViewModel _extensionsVM{ nullptr };
std::vector<IndexEntry> _searchIndex;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ntmViewModelChangedRevoker;

View File

@ -452,6 +452,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return _folderTreeCache;
}
Collections::IObservableVector<Editor::FolderEntryViewModel> NewTabMenuViewModel::FolderTreeFlatList() const
{
std::vector<Editor::FolderEntryViewModel> flatList;
std::function<void(const IVector<Editor::NewTabMenuEntryViewModel>&)> addFoldersRecursively;
addFoldersRecursively = [&flatList, &addFoldersRecursively](const IVector<Editor::NewTabMenuEntryViewModel>& entries) {
for (const auto& entry : entries)
{
if (const auto& folderVM = entry.try_as<Editor::FolderEntryViewModel>())
{
flatList.push_back(folderVM);
addFoldersRecursively(folderVM.Entries());
}
}
};
addFoldersRecursively(_rootEntries);
return single_threaded_observable_vector<Editor::FolderEntryViewModel>(std::move(flatList));
}
// This recursively constructs the FolderTree
FolderTreeViewEntry::FolderTreeViewEntry(Editor::FolderEntryViewModel folderEntry) :
_folderEntry{ folderEntry },

View File

@ -49,6 +49,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Windows::Foundation::Collections::IObservableVector<Model::Profile> AvailableProfiles() const { return _Settings.AllProfiles(); }
Windows::Foundation::Collections::IObservableVector<Editor::FolderTreeViewEntry> FolderTree() const;
Windows::Foundation::Collections::IObservableVector<Editor::FolderEntryViewModel> FolderTreeFlatList() const;
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel> CurrentView() const;
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::FolderEntryViewModel, CurrentFolder, nullptr);
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::FolderTreeViewEntry, CurrentFolderTreeViewSelectedItem, nullptr);

View File

@ -12,18 +12,24 @@ Directory to place generated C++ files.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$SourceDir,
[Parameter(Mandatory=$true)][string]$OutputDir
# TODO CARLOS: set mandatory to true and remove default params
[Parameter(Mandatory=$false)][string]$SourceDir='D:\projects\terminal\src\cascadia\TerminalSettingsEditor\',
[Parameter(Mandatory=$false)][string]$OutputDir='D:\projects\terminal\src\cascadia\TerminalSettingsEditor\Generated Files\'
)
# Prohibited UIDs (exact match, case-insensitive by default)
$ProhibitedUids = @(
'NewTabMenu_AddRemainingProfiles'
# TODO CARLOS: AddRemainingProfiles should probably be allowed
'NewTabMenu_AddRemainingProfiles',
'Extensions_Scope'
)
# Prohibited XAML files (full paths preferred)
# Prohibited XAML files
$ProhibitedXamlFiles = @(
# 'd:\projects\terminal\src\cascadia\TerminalSettingsEditor\SomePage.xaml'
'CommonResources.xaml',
'KeyChordListener.xaml',
'NullableColorPicker.xaml',
'SettingContainerStyle.xaml'
)
if (-not (Test-Path $SourceDir)) { throw "SourceDir not found: $SourceDir" }
@ -32,66 +38,247 @@ if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir
$entries = @()
Get-ChildItem -Path $SourceDir -Recurse -Filter *.xaml | ForEach-Object {
$file = $_.FullName
# Skip whole file if prohibited
if ($ProhibitedXamlFiles -contains $file) { return }
$filename = $_.Name
if ($ProhibitedXamlFiles -contains $filename)
{
return
}
$text = Get-Content -Raw -LiteralPath $file
$text = Get-Content -Raw -LiteralPath $_.FullName
# Extract Page x:Class
$pageClass = $null
if ($text -match '<Page\b[^>]*\bx:Class="([^"]+)"') {
if ($text -match '<Page\b[^>]*\bx:Class="([^"]+)"')
{
$pageClass = $matches[1]
} elseif ($text -match '<UserControl\b[^>]*\bx:Class="([^"]+)"') {
# Needed for Appearances.xaml
$pageClass = $matches[1]
} else {
}
elseif ($filename -eq 'Appearances.xaml')
{
# Apperances.xaml is a UserControl that is hosted in Profiles_Appearance.xaml
$pageClass = 'Microsoft::Terminal::Settings::Editor::Profiles_Appearance'
}
else
{
return
}
# Convert XAML namespace dots to C++ scope operators
$cppPageType = ($pageClass -replace '\.', '::')
$pageClass = ($pageClass -replace '\.', '::')
# Deduce BreadcrumbSubPage
# Special cases:
# - NewTabMenu: defer to UID, see NavigationParam section below
# - Extensions: defer to UID, see NavigationParam section below
$subPage = 'BreadcrumbSubPage::'
if ($pageClass -match 'Editor::Profiles_Appearance')
{
$subPage += 'Profile_Appearance'
}
elseif ($pageClass -match 'Editor::Profiles_Terminal')
{
$subPage += 'Profile_Terminal'
}
elseif ($pageClass -match 'Editor::Profiles_Advanced')
{
$subPage += 'Profile_Advanced'
}
elseif ($pageClass -match 'Editor::EditColorScheme')
{
$subPage += 'ColorSchemes_Edit'
}
else
{
$subPage += 'None'
}
if ($filename -eq 'ColorSchemes.xaml')
{
# ColorSchemes.xaml doesn't have any SettingContainers!
# Register the page itself
$entries += [pscustomobject]@{
DisplayText = 'vm'
ParentPage = "winrt::xaml_typename<Microsoft::Terminal::Settings::Editor::ColorSchemes>()"
NavigationParam = "winrt::box_value(hstring{L`"ColorSchemes_Nav`"})"
SubPage = 'BreadcrumbSubPage::ColorSchemes_Edit'
ElementName = 'L""'
File = $filename
}
# Manually register the "add new" button
$entries += [pscustomobject]@{
DisplayText = 'RS_(L"ColorScheme_AddNewButton/Text")'
ParentPage = "winrt::xaml_typename<Microsoft::Terminal::Settings::Editor::ColorSchemes>()"
NavigationParam = "winrt::box_value(hstring{L`"ColorSchemes_Nav`"})"
SubPage = 'BreadcrumbSubPage::None'
ElementName = 'L"AddNewButton"'
File = $filename
}
return
}
elseif ($filename -eq 'Extensions.xaml')
{
# Register the main extension page
$entries += [pscustomobject]@{
DisplayText = 'RS_(L"Nav_Extensions/Content")'
ParentPage = "winrt::xaml_typename<Microsoft::Terminal::Settings::Editor::Extensions>()"
NavigationParam = "nullptr"
SubPage = 'BreadcrumbSubPage::None'
ElementName = 'L""'
File = $filename
}
# Register the extension view
$entries += [pscustomobject]@{
DisplayText = 'vm.Package().DisplayName()'
ParentPage = "winrt::xaml_typename<Microsoft::Terminal::Settings::Editor::Extensions>()"
NavigationParam = "vm"
SubPage = 'BreadcrumbSubPage::Extensions_Extension'
ElementName = 'L""'
File = $filename
}
return
}
# Find all local:SettingContainer start tags
$pattern = '<local:SettingContainer\b([^>/]*)(/?>)'
$matchesAll = [System.Text.RegularExpressions.Regex]::Matches($text, $pattern, 'IgnoreCase')
foreach ($m in $matchesAll) {
foreach ($m in $matchesAll)
{
$attrBlock = $m.Groups[1].Value
if ($attrBlock -match '\bx:Uid="([^"]+)"') {
# Extract Uid
if ($attrBlock -match '\bx:Uid="([^"]+)"')
{
$uid = $matches[1]
# Skip entry if UID prohibited
if ($ProhibitedUids -contains $uid)
{
continue
}
}
else {
else
{
continue
}
if ($attrBlock -match '\bx:Name="([^"]+)"') {
# Extract Name
if ($attrBlock -match '\bx:Name="([^"]+)"')
{
$name = $matches[1]
}
elseif ($attrBlock -match '\bName="([^"]+)"') {
elseif ($attrBlock -match '\bName="([^"]+)"')
{
$name = $matches[1]
}
else {
else
{
$name = ""
}
# Skip entry if UID prohibited
if ($ProhibitedUids -contains $uid) { continue }
# Deduce NavigationParam
# duplicateForVM is used to duplicate the entry if we need a VM param at runtime (i.e. profile vs global profile)
$duplicateForVM = $false
$navigationParam = 'nullptr'
if ($pageClass -match 'Editor::Launch')
{
$navigationParam = 'Launch_Nav'
}
elseif ($pageClass -match 'Editor::Interaction')
{
$navigationParam = 'Interaction_Nav'
}
elseif ($pageClass -match 'Editor::Rendering')
{
$navigationParam = 'Rendering_Nav'
}
elseif ($pageClass -match 'Editor::Compatibility')
{
$navigationParam = 'Compatibility_Nav'
}
elseif ($pageClass -match 'Editor::Actions')
{
$navigationParam = 'Actions_Nav'
}
elseif ($pageClass -match 'Editor::NewTabMenu')
{
if ($uid -match 'NewTabMenu_CurrentFolder')
{
$navigationParam = 'vm'
$subPage = 'BreadcrumbSubPage::NewTabMenu_Folder'
}
else
{
$navigationParam = 'NewTabMenu_Nav'
$subPage = 'BreadcrumbSubPage::None'
$duplicateForVM = $true
}
}
elseif ($pageClass -match 'Editor::Extensions')
{
# TODO CARLOS: There's actually no UIDs for extension view! But I want the page to still exist in the index at runtime for each extension.
#if ($uid -match 'NewTabMenu_CurrentFolder')
#{
# $navigationParam = 'vm'
# $subPage = 'BreadcrumbSubPage::Extensions_Extension'
#}
#else
#{
$navigationParam = 'Extensions_Nav'
$subPage = 'BreadcrumbSubPage::None'
#}
}
elseif ($pageClass -match 'Editor::Profiles_Base' -or
$pageClass -match 'Editor::Profiles_Appearance' -or
$pageClass -match 'Editor::Profiles_Terminal' -or
$pageClass -match 'Editor::Profiles_Advanced')
{
$navigationParam = 'GlobalProfile_Nav'
$duplicateForVM = $true
}
elseif ($pageClass -match 'Editor::EditColorScheme')
{
# EditColorScheme.xaml always uses a boxed vm param
$navigationParam = 'winrt::box_value(vm)'
}
elseif ($pageClass -match 'Editor::GlobalAppearance')
{
$navigationParam = 'GlobalAppearance_Nav'
}
elseif ($pageClass -match 'Editor::AddProfile')
{
$navigationParam = 'AddProfile_Nav'
}
$entries += [pscustomobject]@{
PageClass = $pageClass
CppPageType = $cppPageType
Uid = $uid
Name = $name
File = $file
DisplayText = "RS_(L`"$($uid)/Header`")"
ParentPage = "winrt::xaml_typename<$($pageClass)>()"
NavigationParam = $navigationParam -eq "vm" -or $navigationParam -eq "winrt::box_value(vm)" -or $navigationParam -eq "nullptr" ? $navigationParam : "winrt::box_value(hstring{L`"$($navigationParam)`"})"
SubPage = $subPage
ElementName = "L`"$($name)`""
File = $filename
}
# Duplicate entry for VM param if needed
if ($duplicateForVM)
{
$entries += [pscustomobject]@{
DisplayText = "RS_(L`"$($uid)/Header`")"
ParentPage = "winrt::xaml_typename<$($pageClass)>()"
NavigationParam = 'vm'
SubPage = $navigationParam -eq 'NewTabMenu_Nav' ? 'BreadcrumbSubPage::NewTabMenu_Folder' : $subPage
ElementName = "L`"$($name)`""
File = $filename
}
}
}
}
# Ensure there aren't any duplicate entries (PageClass, Uid, Name)
$entries = $entries | Sort-Object PageClass, Uid, Name -Unique
# Ensure there aren't any duplicate entries
$entries = $entries | Sort-Object DisplayText, ParentPage, NavigationParam, SubPage, ElementName, File -Unique
$headerPath = Join-Path $OutputDir 'GeneratedSettingsIndex.g.h'
$cppPath = Join-Path $OutputDir 'GeneratedSettingsIndex.g.cpp'
@ -108,20 +295,55 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct IndexEntry
{
std::wstring_view ElementName;
std::wstring_view ElementUID;
hstring DisplayText; // RS_(L"<x:uid>/Header")
winrt::Windows::UI::Xaml::Interop::TypeName ParentPage;
winrt::Windows::Foundation::IInspectable NavigationParam; // VM or hstring tag
BreadcrumbSubPage SubPage;
hstring ElementName;
};
std::span<const IndexEntry> LoadBuildTimeIndex();
std::vector<IndexEntry> LoadBuildTimeIndex();
std::vector<IndexEntry> LoadProfileIndex(const Editor::ProfileViewModel& vm);
std::vector<IndexEntry> LoadNTMFolderIndex(const Editor::FolderEntryViewModel& vm);
std::vector<IndexEntry> LoadExtensionIndex(const Editor::ExtensionPackageViewModel& vm);
std::vector<IndexEntry> LoadColorSchemeIndex(const hstring colorSchemeName);
}
"@
$entryLines = foreach ($e in $entries) {
# Ensure we emit valid wide string literals (escape backslashes and quotes)
$uidEsc = ($e.Uid -replace '\\','\\\\') -replace '"','\"'
$nameEsc = ($e.Name -replace '\\','\\\\') -replace '"','\"'
" { L`"$nameEsc`", L`"$uidEsc`", winrt::xaml_typename<$($e.CppPageType)>() }"
$buildTimeEntries = @()
$profileEntries = @()
$schemeEntries = @()
$ntmEntries = @()
$extensionEntries = @()
foreach ($e in $entries)
{
$formattedEntry = " { $($e.DisplayText), $($e.ParentPage), $($e.NavigationParam), $($e.SubPage), $($e.ElementName) }, // $($e.File)"
if ($e.NavigationParam -eq 'vm' -and
($e.ParentPage -match 'Profiles_Base' -or
$e.ParentPage -match 'Profiles_Appearance' -or
$e.ParentPage -match 'Profiles_Terminal' -or
$e.ParentPage -match 'Profiles_Advanced'))
{
$profileEntries += $formattedEntry
}
elseif ($e.SubPage -eq 'BreadcrumbSubPage::ColorSchemes_Edit')
{
$schemeEntries += $formattedEntry
}
elseif ($e.SubPage -eq 'BreadcrumbSubPage::NewTabMenu_Folder')
{
$ntmEntries += $formattedEntry
}
elseif ($e.SubPage -eq 'BreadcrumbSubPage::Extensions_Extension')
{
$extensionEntries += $formattedEntry
}
else
{
$buildTimeEntries += $formattedEntry
}
}
$cpp = @"
@ -131,21 +353,63 @@ $cpp = @"
#include "pch.h"
#include <winrt/Microsoft.Terminal.Settings.Editor.h>
#include "GeneratedSettingsIndex.g.h"
#include <LibraryResources.h>
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
namespace
std::vector<IndexEntry> LoadBuildTimeIndex()
{
static const IndexEntry s_entries[] =
static const IndexEntry entries[] =
{
$( ($entryLines -join ",`r`n") )
$( ($buildTimeEntries -join "`r`n") )
};
std::vector<IndexEntry> index;
index.assign(std::begin(entries), std::end(entries));
return index;
}
std::span<const IndexEntry> LoadBuildTimeIndex()
std::vector<IndexEntry> LoadProfileIndex(const Editor::ProfileViewModel& vm)
{
static auto entries = std::span<const IndexEntry>(s_entries, std::size(s_entries));
return entries;
const IndexEntry entries[] =
{
$( ($profileEntries -join "`r`n") )
};
std::vector<IndexEntry> index;
index.assign(std::begin(entries), std::end(entries));
return index;
}
std::vector<IndexEntry> LoadNTMFolderIndex(const Editor::FolderEntryViewModel& vm)
{
const IndexEntry entries[] =
{
$( ($ntmEntries -join "`r`n") )
};
std::vector<IndexEntry> index;
index.assign(std::begin(entries), std::end(entries));
return index;
}
std::vector<IndexEntry> LoadExtensionIndex(const Editor::ExtensionPackageViewModel& vm)
{
const IndexEntry entries[] =
{
$( ($extensionEntries -join "`r`n") )
};
std::vector<IndexEntry> index;
index.assign(std::begin(entries), std::end(entries));
return index;
}
std::vector<IndexEntry> LoadColorSchemeIndex(const hstring vm)
{
const IndexEntry entries[] =
{
$( ($schemeEntries -join "`r`n") )
};
std::vector<IndexEntry> index;
index.assign(std::begin(entries), std::end(entries));
return index;
}
}
"@
@ -155,5 +419,4 @@ Set-Content -LiteralPath $cppPath -Value $cpp -NoNewline
Write-Host "Generated:"
Write-Host " $headerPath"
Write-Host " $cppPath"
Write-Host "Entries: $($entries.Count)"
Write-Host " $cppPath"