mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 17:56:44 +02:00
CmdPal: Update WebSearch extension history immediately (#41398)
## Summary of the Pull Request This PR ensures that the list of recent searches in the Web Search extension is updated immediately after a new item is added or when settings controlling the number of items are changed. - Refactors the Web Search extension history to keep it in memory after being loaded at startup - Adds an event to notify subscribers when the history changes - Implements `IDisposable` to ensure that `WebSearchListPage` unsubscribes from the event - Moves responsibility for creating all list items to single class (`WebSearchListPage`) - Updated unit tests - ## PR Checklist - [x] Closes: #40548 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** nothing - [x] **New binaries:** none - [x] **Documentation updated:** nope <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed
This commit is contained in:
@@ -2,10 +2,9 @@
|
||||
// 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 Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
|
||||
|
||||
@@ -13,34 +12,22 @@ public class MockSettingsInterface : ISettingsInterface
|
||||
{
|
||||
private readonly List<HistoryItem> _historyItems;
|
||||
|
||||
public event EventHandler HistoryChanged;
|
||||
|
||||
public bool GlobalIfURI { get; set; }
|
||||
|
||||
public uint HistoryItemCount { get; set; }
|
||||
public int HistoryItemCount { get; set; }
|
||||
|
||||
public MockSettingsInterface(uint historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)
|
||||
public IReadOnlyList<HistoryItem> HistoryItems => _historyItems;
|
||||
|
||||
public MockSettingsInterface(int historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)
|
||||
{
|
||||
_historyItems = mockHistory ?? new List<HistoryItem>();
|
||||
GlobalIfURI = globalIfUri;
|
||||
HistoryItemCount = historyItemCount;
|
||||
}
|
||||
|
||||
public List<ListItem> LoadHistory()
|
||||
{
|
||||
var listItems = new List<ListItem>();
|
||||
foreach (var historyItem in _historyItems)
|
||||
{
|
||||
listItems.Add(new ListItem(new SearchWebCommand(historyItem.SearchString, this))
|
||||
{
|
||||
Title = historyItem.SearchString,
|
||||
Subtitle = historyItem.Timestamp.ToString("g", System.Globalization.CultureInfo.InvariantCulture),
|
||||
});
|
||||
}
|
||||
|
||||
listItems.Reverse();
|
||||
return listItems;
|
||||
}
|
||||
|
||||
public void SaveHistory(HistoryItem historyItem)
|
||||
public void AddHistoryItem(HistoryItem historyItem)
|
||||
{
|
||||
if (historyItem is null)
|
||||
{
|
||||
@@ -54,15 +41,18 @@ public class MockSettingsInterface : ISettingsInterface
|
||||
{
|
||||
while (_historyItems.Count > HistoryItemCount)
|
||||
{
|
||||
_historyItems.RemoveAt(0); // Remove the oldest item
|
||||
_historyItems.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
// Helper method for testing
|
||||
public void ClearHistory()
|
||||
{
|
||||
_historyItems.Clear();
|
||||
HistoryChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
// Helper method for testing
|
||||
|
||||
@@ -45,7 +45,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LoadHistoryReturnsExpectedItems()
|
||||
public async Task HistoryReturnsExpectedItems()
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryItems = new List<HistoryItem>
|
||||
@@ -77,7 +77,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LoadHistoryMoreThanLimitation()
|
||||
public async Task HistoryExceedingLimitReturnsMaxItems()
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryItems = new List<HistoryItem>
|
||||
@@ -109,7 +109,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LoadHistoryWithDisableSetting()
|
||||
public async Task HistoryWhenSetToNoneReturnEmptyList()
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryItems = new List<HistoryItem>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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.Threading.Tasks;
|
||||
|
||||
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Pages;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class SettingsManagerTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task HistoryChangedEventIsRaisedWhenItemIsAdded()
|
||||
{
|
||||
// Setup
|
||||
var settings = new MockSettingsInterface(historyItemCount: 5);
|
||||
var page = new WebSearchListPage(settings);
|
||||
|
||||
var eventRaised = false;
|
||||
|
||||
try
|
||||
{
|
||||
settings.HistoryChanged += Handler;
|
||||
|
||||
// Act
|
||||
settings.AddHistoryItem(new HistoryItem("test event", DateTime.UtcNow));
|
||||
await Task.Delay(50);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(eventRaised, "Expected HistoryChanged to be raised when saving history.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
settings.HistoryChanged -= Handler;
|
||||
page.Dispose();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void Handler(object s, EventArgs e) => eventRaised = true;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ internal sealed partial class SearchWebCommand : InvokableCommand
|
||||
|
||||
if (_settingsManager.HistoryItemCount != 0)
|
||||
{
|
||||
_settingsManager.SaveHistory(new HistoryItem(Arguments, DateTime.Now));
|
||||
_settingsManager.AddHistoryItem(new HistoryItem(Arguments, DateTime.Now));
|
||||
}
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// 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.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
|
||||
internal sealed class HistoryStore
|
||||
{
|
||||
private readonly string _filePath;
|
||||
private readonly List<HistoryItem> _items = [];
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private int _capacity;
|
||||
|
||||
public event EventHandler? Changed;
|
||||
|
||||
public HistoryStore(string filePath, int capacity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filePath);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(capacity);
|
||||
|
||||
_filePath = filePath;
|
||||
_capacity = capacity;
|
||||
|
||||
_items.AddRange(LoadFromDiskSafe());
|
||||
TrimNoLock();
|
||||
}
|
||||
|
||||
public IReadOnlyList<HistoryItem> HistoryItems
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return [.. _items];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(HistoryItem item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_items.Add(item);
|
||||
_ = TrimNoLock();
|
||||
SaveNoLock();
|
||||
}
|
||||
|
||||
Changed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SetCapacity(int capacity)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(capacity);
|
||||
|
||||
bool trimmed;
|
||||
lock (_lock)
|
||||
{
|
||||
_capacity = capacity;
|
||||
trimmed = TrimNoLock();
|
||||
if (trimmed)
|
||||
{
|
||||
SaveNoLock();
|
||||
}
|
||||
}
|
||||
|
||||
if (trimmed)
|
||||
{
|
||||
Changed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrimNoLock()
|
||||
{
|
||||
var max = _capacity;
|
||||
if (_items.Count > max)
|
||||
{
|
||||
_items.RemoveRange(0, _items.Count - max);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<HistoryItem> LoadFromDiskSafe()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var fileContent = File.ReadAllText(_filePath);
|
||||
var historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(fileContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
|
||||
return historyItems;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Unable to load history", ex);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveNoLock()
|
||||
{
|
||||
var json = JsonSerializer.Serialize(_items, WebSearchJsonSerializationContext.Default.ListHistoryItem);
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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 Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -9,11 +10,13 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
|
||||
public interface ISettingsInterface
|
||||
{
|
||||
event EventHandler? HistoryChanged;
|
||||
|
||||
public bool GlobalIfURI { get; }
|
||||
|
||||
public uint HistoryItemCount { get; }
|
||||
public int HistoryItemCount { get; }
|
||||
|
||||
public List<ListItem> LoadHistory();
|
||||
public IReadOnlyList<HistoryItem> HistoryItems { get; }
|
||||
|
||||
public void SaveHistory(HistoryItem historyItem);
|
||||
public void AddHistoryItem(HistoryItem historyItem);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -17,10 +14,16 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
{
|
||||
private const string HistoryItemCountLegacySettingsKey = "ShowHistory";
|
||||
private readonly string _historyPath;
|
||||
|
||||
private static readonly string _namespace = "websearch";
|
||||
|
||||
public event EventHandler? HistoryChanged
|
||||
{
|
||||
add => _history.Changed += value;
|
||||
remove => _history.Changed -= value;
|
||||
}
|
||||
|
||||
private readonly HistoryStore _history;
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
|
||||
|
||||
private static readonly List<ChoiceSetSetting.Choice> _choices =
|
||||
@@ -46,9 +49,26 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
|
||||
public bool GlobalIfURI => _globalIfURI.Value;
|
||||
|
||||
public uint HistoryItemCount => uint.TryParse(_historyItemCount.Value, out var value) ? value : 0;
|
||||
public int HistoryItemCount => int.TryParse(_historyItemCount.Value, out var value) && value >= 0 ? value : 0;
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
public IReadOnlyList<HistoryItem> HistoryItems => _history.HistoryItems;
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
|
||||
Settings.Add(_globalIfURI);
|
||||
Settings.Add(_historyItemCount);
|
||||
|
||||
LoadSettings();
|
||||
|
||||
// Initialize history store after loading settings to get the correct capacity
|
||||
_history = new HistoryStore(HistoryStateJsonPath(), HistoryItemCount);
|
||||
|
||||
Settings.SettingsChanged += (_, _) => SaveSettings();
|
||||
}
|
||||
|
||||
private static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
@@ -57,7 +77,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
internal static string HistoryStateJsonPath()
|
||||
private static string HistoryStateJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
@@ -66,156 +86,30 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
return Path.Combine(directory, "websearch_history.json");
|
||||
}
|
||||
|
||||
public void SaveHistory(HistoryItem historyItem)
|
||||
public void AddHistoryItem(HistoryItem historyItem)
|
||||
{
|
||||
if (historyItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<HistoryItem> historyItems;
|
||||
|
||||
// Check if the file exists and load existing history
|
||||
if (File.Exists(_historyPath))
|
||||
{
|
||||
var existingContent = File.ReadAllText(_historyPath);
|
||||
historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(existingContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
|
||||
}
|
||||
else
|
||||
{
|
||||
historyItems = [];
|
||||
}
|
||||
|
||||
// Add the new history item
|
||||
historyItems.Add(historyItem);
|
||||
|
||||
// Determine the maximum number of items to keep based on HistoryItemCount
|
||||
if (HistoryItemCount > 0)
|
||||
{
|
||||
// Keep only the most recent `maxHistoryItems` items
|
||||
while (historyItems.Count > HistoryItemCount)
|
||||
{
|
||||
historyItems.RemoveAt(0); // Remove the oldest item
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the updated list back to JSON and save it
|
||||
var historyJson = JsonSerializer.Serialize(historyItems, WebSearchJsonSerializationContext.Default.ListHistoryItem);
|
||||
File.WriteAllText(_historyPath, historyJson);
|
||||
_history.Add(historyItem);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to add item to the search history", ex);
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
public List<ListItem> LoadHistory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_historyPath))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// Read and deserialize JSON into a list of HistoryItem objects
|
||||
var fileContent = File.ReadAllText(_historyPath);
|
||||
var historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(fileContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
|
||||
|
||||
// Convert each HistoryItem to a ListItem
|
||||
var listItems = new List<ListItem>();
|
||||
foreach (var historyItem in historyItems)
|
||||
{
|
||||
listItems.Add(new ListItem(new SearchWebCommand(historyItem.SearchString, this))
|
||||
{
|
||||
Title = historyItem.SearchString,
|
||||
Subtitle = historyItem.Timestamp.ToString("g", CultureInfo.InvariantCulture), // Ensures consistent formatting
|
||||
});
|
||||
}
|
||||
|
||||
listItems.Reverse();
|
||||
return listItems;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
_historyPath = HistoryStateJsonPath();
|
||||
|
||||
Settings.Add(_globalIfURI);
|
||||
Settings.Add(_historyItemCount);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (s, a) => this.SaveSettings();
|
||||
}
|
||||
|
||||
private void ClearHistory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_historyPath))
|
||||
{
|
||||
// Delete the history file
|
||||
File.Delete(_historyPath);
|
||||
|
||||
// Log that the history was successfully cleared
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "History cleared successfully." });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log that there was no history file to delete
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "No history file found to clear." });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log any exception that occurs
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = $"Failed to clear history: {ex}" });
|
||||
}
|
||||
}
|
||||
|
||||
public override void SaveSettings()
|
||||
{
|
||||
base.SaveSettings();
|
||||
|
||||
try
|
||||
{
|
||||
if (HistoryItemCount == 0)
|
||||
{
|
||||
ClearHistory();
|
||||
}
|
||||
else if (HistoryItemCount > 0)
|
||||
{
|
||||
// Trim the history file if there are more items than the new limit
|
||||
if (File.Exists(_historyPath))
|
||||
{
|
||||
var existingContent = File.ReadAllText(_historyPath);
|
||||
var historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(existingContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
|
||||
|
||||
// Check if trimming is needed
|
||||
if (historyItems.Count > HistoryItemCount)
|
||||
{
|
||||
// Trim the list to keep only the most recent `HistoryItemCount` items
|
||||
historyItems = historyItems.Skip((int)(historyItems.Count - HistoryItemCount)).ToList();
|
||||
|
||||
// Save the trimmed history back to the file
|
||||
var trimmedHistoryJson = JsonSerializer.Serialize(historyItems, WebSearchJsonSerializationContext.Default.ListHistoryItem);
|
||||
File.WriteAllText(_historyPath, trimmedHistoryJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
_history.SetCapacity(HistoryItemCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to save the search history", ex);
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
@@ -16,31 +16,30 @@ using BrowserInfo = Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.Pages;
|
||||
|
||||
internal sealed partial class WebSearchListPage : DynamicListPage
|
||||
internal sealed partial class WebSearchListPage : DynamicListPage, IDisposable
|
||||
{
|
||||
private readonly string _iconPath = string.Empty;
|
||||
private readonly List<ListItem>? _historyItems;
|
||||
private readonly IconInfo _newSearchIcon = new(string.Empty);
|
||||
private readonly ISettingsInterface _settingsManager;
|
||||
private readonly Lock _sync = new();
|
||||
private static readonly CompositeFormat PluginInBrowserName = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_in_browser_name);
|
||||
private static readonly CompositeFormat PluginOpen = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open);
|
||||
private List<ListItem> _allItems;
|
||||
private IListItem[] _allItems = [];
|
||||
private List<ListItem> _historyItems = [];
|
||||
|
||||
public WebSearchListPage(ISettingsInterface settingsManager)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsManager);
|
||||
|
||||
Name = Resources.command_item_title;
|
||||
Title = Resources.command_item_title;
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
|
||||
_allItems = [];
|
||||
Id = "com.microsoft.cmdpal.websearch";
|
||||
|
||||
_settingsManager = settingsManager;
|
||||
_historyItems = _settingsManager.HistoryItemCount != 0 ? _settingsManager.LoadHistory() : null;
|
||||
if (_historyItems is not null)
|
||||
{
|
||||
_allItems.AddRange(_historyItems);
|
||||
}
|
||||
_settingsManager.HistoryChanged += SettingsManagerOnHistoryChanged;
|
||||
|
||||
// It just looks viewer to have string twice on the page, and default placeholder is good enough
|
||||
PlaceholderText = _allItems.Count > 0 ? Resources.plugin_description : string.Empty;
|
||||
PlaceholderText = _allItems.Length > 0 ? Resources.plugin_description : string.Empty;
|
||||
|
||||
EmptyContent = new CommandItem(new NoOpCommand())
|
||||
{
|
||||
@@ -48,45 +47,102 @@ internal sealed partial class WebSearchListPage : DynamicListPage
|
||||
Title = Properties.Resources.plugin_description,
|
||||
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginInBrowserName, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
|
||||
};
|
||||
|
||||
UpdateHistory();
|
||||
RequeryAndUpdateItems(SearchText);
|
||||
}
|
||||
|
||||
public List<ListItem> Query(string query)
|
||||
private void SettingsManagerOnHistoryChanged(object? sender, EventArgs e)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
IEnumerable<ListItem>? filteredHistoryItems = null;
|
||||
UpdateHistory();
|
||||
RequeryAndUpdateItems(SearchText);
|
||||
}
|
||||
|
||||
if (_historyItems is not null)
|
||||
private void UpdateHistory()
|
||||
{
|
||||
List<ListItem> history = [];
|
||||
|
||||
if (_settingsManager.HistoryItemCount > 0)
|
||||
{
|
||||
filteredHistoryItems = _settingsManager.HistoryItemCount != 0 ? ListHelpers.FilterList(_historyItems, query).OfType<ListItem>() : null;
|
||||
var items = _settingsManager.HistoryItems;
|
||||
for (var index = items.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var historyItem = items[index];
|
||||
history.Add(new ListItem(new SearchWebCommand(historyItem.SearchString, _settingsManager))
|
||||
{
|
||||
Title = historyItem.SearchString,
|
||||
Subtitle = historyItem.Timestamp.ToString("g", CultureInfo.InvariantCulture),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var results = new List<ListItem>();
|
||||
lock (_sync)
|
||||
{
|
||||
_historyItems = history;
|
||||
}
|
||||
}
|
||||
|
||||
private static IListItem[] Query(string query, List<ListItem> historySnapshot, ISettingsInterface settingsManager, IconInfo newSearchIcon)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
|
||||
var filteredHistoryItems = settingsManager.HistoryItemCount > 0
|
||||
? ListHelpers.FilterList(historySnapshot, query)
|
||||
: [];
|
||||
|
||||
var results = new List<IListItem>();
|
||||
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
var searchTerm = query;
|
||||
var result = new ListItem(new SearchWebCommand(searchTerm, _settingsManager))
|
||||
var result = new ListItem(new SearchWebCommand(searchTerm, settingsManager))
|
||||
{
|
||||
Title = searchTerm,
|
||||
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginOpen, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
|
||||
Icon = new IconInfo(_iconPath),
|
||||
Icon = newSearchIcon,
|
||||
};
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
if (filteredHistoryItems is not null)
|
||||
results.AddRange(filteredHistoryItems);
|
||||
|
||||
return [.. results];
|
||||
}
|
||||
|
||||
private void RequeryAndUpdateItems(string search)
|
||||
{
|
||||
List<ListItem> historySnapshot;
|
||||
lock (_sync)
|
||||
{
|
||||
results.AddRange(filteredHistoryItems);
|
||||
historySnapshot = _historyItems;
|
||||
}
|
||||
|
||||
return results;
|
||||
var items = Query(search ?? string.Empty, historySnapshot, _settingsManager, _newSearchIcon);
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
_allItems = items;
|
||||
}
|
||||
|
||||
RaiseItemsChanged();
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
_allItems = [.. Query(newSearch)];
|
||||
RaiseItemsChanged(0);
|
||||
RequeryAndUpdateItems(newSearch);
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => [.. _allItems];
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _allItems;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_settingsManager.HistoryChanged -= SettingsManagerOnHistoryChanged;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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 Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
@@ -15,6 +16,9 @@ public partial class WebSearchCommandsProvider : CommandProvider
|
||||
private readonly SettingsManager _settingsManager = new();
|
||||
private readonly FallbackExecuteSearchItem _fallbackItem;
|
||||
private readonly FallbackOpenURLItem _openUrlFallbackItem;
|
||||
private readonly WebSearchTopLevelCommandItem _webSearchTopLevelItem;
|
||||
private readonly ICommandItem[] _topLevelItems;
|
||||
private readonly IFallbackCommandItem[] _fallbackCommands;
|
||||
|
||||
public WebSearchCommandsProvider()
|
||||
{
|
||||
@@ -25,18 +29,27 @@ public partial class WebSearchCommandsProvider : CommandProvider
|
||||
|
||||
_fallbackItem = new FallbackExecuteSearchItem(_settingsManager);
|
||||
_openUrlFallbackItem = new FallbackOpenURLItem(_settingsManager);
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands()
|
||||
{
|
||||
return [new WebSearchTopLevelCommandItem(_settingsManager)
|
||||
_webSearchTopLevelItem = new WebSearchTopLevelCommandItem(_settingsManager)
|
||||
{
|
||||
MoreCommands = [
|
||||
MoreCommands =
|
||||
[
|
||||
new CommandContextItem(Settings!.SettingsPage),
|
||||
],
|
||||
}
|
||||
];
|
||||
};
|
||||
_topLevelItems = [_webSearchTopLevelItem];
|
||||
_fallbackCommands = [_openUrlFallbackItem, _fallbackItem];
|
||||
}
|
||||
|
||||
public override IFallbackCommandItem[]? FallbackCommands() => [_openUrlFallbackItem, _fallbackItem];
|
||||
public override ICommandItem[] TopLevelCommands() => _topLevelItems;
|
||||
|
||||
public override IFallbackCommandItem[]? FallbackCommands() => _fallbackCommands;
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_webSearchTopLevelItem?.Dispose();
|
||||
|
||||
base.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Pages;
|
||||
@@ -13,7 +12,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch;
|
||||
|
||||
public partial class WebSearchTopLevelCommandItem : CommandItem, IFallbackHandler
|
||||
public partial class WebSearchTopLevelCommandItem : CommandItem, IFallbackHandler, IDisposable
|
||||
{
|
||||
private readonly SettingsManager _settingsManager;
|
||||
|
||||
@@ -27,17 +26,29 @@ public partial class WebSearchTopLevelCommandItem : CommandItem, IFallbackHandle
|
||||
|
||||
private void SetDefaultTitle() => Title = Resources.command_item_title;
|
||||
|
||||
private void ReplaceCommand(ICommand newCommand)
|
||||
{
|
||||
(Command as IDisposable)?.Dispose();
|
||||
Command = newCommand;
|
||||
}
|
||||
|
||||
public void UpdateQuery(string query)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
SetDefaultTitle();
|
||||
Command = new WebSearchListPage(_settingsManager);
|
||||
ReplaceCommand(new WebSearchListPage(_settingsManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = query;
|
||||
Command = new SearchWebCommand(query, _settingsManager);
|
||||
ReplaceCommand(new SearchWebCommand(query, _settingsManager));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(Command as IDisposable)?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user