Compare commits

...

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
a769fdffb5 Implement search box re-focus fix to restore cached results
Co-authored-by: yeelam-gordon <73506701+yeelam-gordon@users.noreply.github.com>
2025-08-26 02:51:00 +00:00
copilot-swe-agent[bot]
3c42e262bf Initial plan 2025-08-26 02:41:05 +00:00
PesBandi
8258f2ab6f [CmdPal] Eliminate CopyTextCommand duplicates (#41347)
## Summary of the Pull Request
Removes redundant CopyCommands and replaces their usage with
`CopyTextCommand` from the toolkit. This is actually recommend by the
[docs](https://learn.microsoft.com/en-us/windows/powertoys/command-palette/command-results#showtoast-command-result),
we should probably lead by example 😄
> Consider the CopyTextCommand in the helpers - this command will show a
toast with the text "Copied to clipboard", then dismiss the palette.

Only functionality change is that they now display *Copied to
clipboard!* after copying.
## PR Checklist

- [x] Closes: #41346
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** All pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** No need
- [x] **New binaries:** None
- [x] **Documentation updated:** No need
## Detailed Description of the Pull Request / Additional comments
Custom command names (e.g. *Copy path* instead of *Copy*) are preserved.
Strings put out of use have been deleted.
CopyCommands from the Registry and Clipboard plugins are left untouched,
as they contain special logic.
The error handling in `CopyPathCommand` from Apps was unnecessary, since
it's already taken care of by `ClipboardHelper`.
## Validation Steps Performed
Manual testing
2025-08-25 14:48:48 -05:00
8 changed files with 38 additions and 139 deletions

View File

@@ -1,47 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Text;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class CopyPathCommand : InvokableCommand
{
private readonly string _target;
public CopyPathCommand(string target)
{
Name = Resources.copy_path;
Icon = Icons.CopyIcon;
_target = target;
}
private static readonly CompositeFormat CopyFailedFormat = CompositeFormat.Parse(Resources.copy_failed);
public override CommandResult Invoke()
{
try
{
ClipboardHelper.SetText(_target);
}
catch (Exception ex)
{
Logger.LogError("Copy failed: " + ex.Message);
return CommandResult.ShowToast(
new ToastArgs
{
Message = string.Format(CultureInfo.CurrentCulture, CopyFailedFormat, ex.Message),
Result = CommandResult.KeepOpen(),
});
}
return CommandResult.ShowToast(Resources.copied_to_clipboard);
}
}

View File

@@ -95,7 +95,7 @@ public class UWPApplication : IUWPApplication
commands.Add(
new CommandContextItem(
new Commands.CopyPathCommand(Location))
new CopyTextCommand(Location) { Name = Resources.copy_path })
{
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
});

View File

@@ -202,7 +202,7 @@ public class Win32Program : IProgram
}
commands.Add(new CommandContextItem(
new Commands.CopyPathCommand(FullPath))
new CopyTextCommand(FullPath) { Name = Resources.copy_path })
{
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
});

View File

@@ -78,24 +78,6 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Copied to clipboard!.
/// </summary>
internal static string copied_to_clipboard {
get {
return ResourceManager.GetString("copied_to_clipboard", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy failed ({0}). Please try again..
/// </summary>
internal static string copy_failed {
get {
return ResourceManager.GetString("copy_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy path.
/// </summary>

View File

@@ -172,13 +172,6 @@
<data name="run_as_different_user" xml:space="preserve">
<value>Run as different user</value>
</data>
<data name="copy_failed" xml:space="preserve">
<value>Copy failed ({0}). Please try again.</value>
<comment>{0} is the error message</comment>
</data>
<data name="copied_to_clipboard" xml:space="preserve">
<value>Copied to clipboard!</value>
</data>
<data name="enable_start_menu_source" xml:space="preserve">
<value>Include apps found in the Start Menu</value>
</data>

View File

@@ -1,39 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.WindowsSettings.Classes;
using Microsoft.CmdPal.Ext.WindowsSettings.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.ApplicationModel.DataTransfer;
using Windows.Networking.NetworkOperators;
using Windows.UI;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Commands;
internal sealed partial class CopySettingCommand : InvokableCommand
{
private readonly WindowsSetting _entry;
internal CopySettingCommand(WindowsSetting entry)
{
Name = Resources.CopyCommand;
Icon = Icons.CopyIcon;
_entry = entry;
}
public override CommandResult Invoke()
{
ClipboardHelper.SetText(_entry.Command);
return CommandResult.Dismiss();
}
}

View File

@@ -23,7 +23,7 @@ internal static class ContextMenuHelper
{
var list = new List<CommandContextItem>(1)
{
new(new CopySettingCommand(entry)),
new(new CopyTextCommand(entry.Command) { Name = Resources.CopyCommand }),
};
return list;

View File

@@ -574,7 +574,21 @@ namespace Microsoft.PowerToys.Settings.UI.Views
_lastSearchResults = results;
_lastQueryText = query;
List<SuggestionItem> top;
var suggestions = BuildSuggestionItems(results, query);
Logger.LogDebug($"[Search][TextChanged][{traceId}] built {suggestions.Count} suggestions");
var swUi = Stopwatch.StartNew();
sender.ItemsSource = suggestions;
sender.IsSuggestionListOpen = suggestions.Count > 0;
swUi.Stop();
swOverall.Stop();
Logger.LogDebug($"[Search][TextChanged][{traceId}] UI update took {swUi.ElapsedMilliseconds} ms. total={swOverall.ElapsedMilliseconds} ms");
}
private List<SuggestionItem> BuildSuggestionItems(List<SettingEntry> results, string query)
{
List<SuggestionItem> suggestions;
if (results.Count == 0)
{
// Explicit no-results row
@@ -586,7 +600,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
}
var headerText = $"{noResultsPrefix} '{query}'";
top =
suggestions =
[
new()
{
@@ -594,35 +608,25 @@ namespace Microsoft.PowerToys.Settings.UI.Views
IsNoResults = true,
},
];
Logger.LogDebug($"[Search][TextChanged][{traceId}] no results -> added placeholder item (count={top.Count})");
}
else
{
// Project top 5 suggestions
var swProject = Stopwatch.StartNew();
top = [.. results.Take(5)
suggestions = [.. results.Take(5)
.Select(e =>
{
string subtitle = string.Empty;
if (e.Type != EntryType.SettingsPage)
{
var swSubtitle = Stopwatch.StartNew();
subtitle = SearchIndexService.GetLocalizedPageName(e.PageTypeName);
if (string.IsNullOrEmpty(subtitle))
{
// Fallback: look up the module title from the in-memory index
var swFallback = Stopwatch.StartNew();
subtitle = SearchIndexService.Index
.Where(x => x.Type == EntryType.SettingsPage && x.PageTypeName == e.PageTypeName)
.Select(x => x.Header)
.FirstOrDefault() ?? string.Empty;
swFallback.Stop();
Logger.LogDebug($"[Search][TextChanged][{traceId}] fallback subtitle for '{e.PageTypeName}' took {swFallback.ElapsedMilliseconds} ms");
}
swSubtitle.Stop();
Logger.LogDebug($"[Search][TextChanged][{traceId}] subtitle for '{e.PageTypeName}' took {swSubtitle.ElapsedMilliseconds} ms");
}
return new SuggestionItem
@@ -636,23 +640,15 @@ namespace Microsoft.PowerToys.Settings.UI.Views
IsShowAll = false,
};
})];
swProject.Stop();
Logger.LogDebug($"[Search][TextChanged][{traceId}] project suggestions took {swProject.ElapsedMilliseconds} ms. topCount={top.Count}");
if (results.Count > 5)
{
// Add a tail item to show all results if there are more than 5
top.Add(new SuggestionItem { IsShowAll = true });
Logger.LogDebug($"[Search][TextChanged][{traceId}] added 'Show all results' item");
suggestions.Add(new SuggestionItem { IsShowAll = true });
}
}
var swUi = Stopwatch.StartNew();
sender.ItemsSource = top;
sender.IsSuggestionListOpen = top.Count > 0;
swUi.Stop();
swOverall.Stop();
Logger.LogDebug($"[Search][TextChanged][{traceId}] UI update took {swUi.ElapsedMilliseconds} ms. total={swOverall.ElapsedMilliseconds} ms");
return suggestions;
}
private void SearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
@@ -713,8 +709,22 @@ namespace Microsoft.PowerToys.Settings.UI.Views
private void SearchBox_GotFocus(object sender, RoutedEventArgs e)
{
// do not prompt unless search for text.
return;
if (sender is AutoSuggestBox searchBox)
{
var currentText = searchBox.Text?.Trim() ?? string.Empty;
// If there's text in the search box and we have cached results for that text, restore the suggestions
if (!string.IsNullOrWhiteSpace(currentText) &&
_lastSearchResults?.Count > 0 &&
string.Equals(_lastQueryText, currentText, StringComparison.Ordinal))
{
Logger.LogDebug($"[Search][GotFocus] restoring cached results for '{currentText}'. results={_lastSearchResults.Count}");
var suggestions = BuildSuggestionItems(_lastSearchResults, currentText);
searchBox.ItemsSource = suggestions;
searchBox.IsSuggestionListOpen = suggestions.Count > 0;
}
}
}
private async void SearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)