Compare commits

...

19 Commits

Author SHA1 Message Date
Michael Jolley
c7f8ac0b06 Merging main (broken) 2026-02-13 17:15:25 -06:00
Michael Jolley
12bfa13b72 Fixing spelling 2026-02-10 20:15:29 -06:00
Michael Jolley
54d1ab3b70 WinRTExtensionService 2026-02-10 20:13:56 -06:00
Michael Jolley
82671661a8 WinRTExtensionService 2026-02-09 13:21:41 -06:00
Michael Jolley
99a6c8c74e Working on DI 2026-02-05 16:33:20 -06:00
Michael Jolley
285183899f I don't know what I've done. Send help... or Jiri 2026-02-04 16:32:58 -06:00
Michael Jolley
d3d39f91dc DI 2026-02-03 20:42:22 -06:00
Michael Jolley
10f953f684 Working on DI 2026-02-03 16:48:40 -06:00
Michael Jolley
54c5d7b55d Working on DI 2026-02-03 16:48:30 -06:00
Michael Jolley
94a699d00b Working on DI 2026-01-30 22:38:03 -06:00
Michael Jolley
fa4bc0a397 Working on DI 2026-01-29 21:42:53 -06:00
Michael Jolley
303be86d86 Working on DI 2026-01-28 17:00:27 -06:00
Michael Jolley
ec423177da Refactor CmdPal to inject SettingsService instead of SettingsModel
- Update all ViewModels to receive SettingsService via DI
- Implement IDisposable pattern for proper event cleanup
- Add App.Services property for DI access where needed
- Restore GetProviderSettings/GetGlobalFallbacks to SettingsModel
- Fix CommandProviderWrapper and TopLevelCommandManager logging
- Update all Settings pages to use SettingsService
- Add DefaultAppExtensionHost to replace CommandPaletteHost.Instance
- Add parameterless constructors for XAML-instantiated types
- Fix various ILogger injection issues throughout UI layer
2026-01-27 17:33:58 -06:00
Michael Jolley
f78ec0c8e5 Migrate CoreLogger to ILogger with LoggerMessage pattern (partial)
UI.ViewModels:
- CommandSettingsViewModel: Add ILogger injection
- CommandItemViewModel: Add ILogger with LoggerMessage methods
- ContextMenuViewModel: Add ILogger injection
- ExtensionObjectViewModel: Add Logger property with NullLogger default
- ShellViewModel: Replace CoreLogger calls with LoggerMessage methods
- UpdateCommandBarMessage: Remove logging from interface default method

UI helpers/services:
- WallpaperHelper: Add ILogger injection with LoggerMessage methods
- LocalKeyboardListener: Add ILogger injection with LoggerMessage methods
- ImageProvider: Add ILogger injection with LoggerMessage methods
- ThemeService: Add ILogger injection with LoggerMessage methods

Note: UI project migration is partial - XAML code-behind files still use static Logger
2026-01-27 14:05:21 -06:00
Michael Jolley
a1e8b6aca9 Add AppStateService and SettingsService with PersistenceService
- Extract persistence logic into PersistenceService
- Add AppStateService to manage app state
- Add SettingsService to manage settings
- Add JsonSerializationContext for AOT-compatible JSON
- Add EscapeKeyBehavior and MonitorBehavior enums
2026-01-26 18:12:58 -06:00
Michael Jolley
81aeb74fda Use ILogger in AppExtensionHost instead of CoreLogger 2026-01-26 17:00:06 -06:00
Michael Jolley
b5ae2efc0d Rename CoreLogger to CmdPalLogger 2026-01-26 16:56:43 -06:00
Michael Jolley
ba9585a663 Migrate Microsoft.CmdPal.Core.ViewModels into Microsoft.CmdPal.UI.ViewModels
- Move all source files from Core.ViewModels to UI.ViewModels
- Update namespace from Microsoft.CmdPal.Core.ViewModels to Microsoft.CmdPal.UI.ViewModels
- Remove Core.ViewModels project from solution files
- Update all using statements in dependent projects
- Delete the Core folder which is now empty
2026-01-26 16:34:38 -06:00
Michael Jolley
5f273c7be6 Rename Microsoft.CmdPal.Core.Common to Microsoft.CmdPal.Common
- Move project from Core/ subfolder to cmdpal/ root
- Update namespace from Microsoft.CmdPal.Core.Common to Microsoft.CmdPal.Common
- Update all project references and using statements
- Update solution files (PowerToys.slnx, CommandPalette.slnf)
2026-01-26 15:52:52 -06:00
283 changed files with 3771 additions and 2474 deletions

View File

@@ -196,6 +196,10 @@
<Folder Name="/modules/CommandPalette/">
<Project Path="src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj" Id="5f63c743-f6ce-4dba-a200-2b3f8a14e8c2" />
<Project Path="src/modules/cmdpal/CmdPalModuleInterface/CmdPalModuleInterface.vcxproj" Id="0adeb797-c8c7-4ffa-acd5-2af6cad7ecd8" />
<Project Path="src/modules/cmdpal/Microsoft.CmdPal.Common/Microsoft.CmdPal.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/Built-in Extensions/">
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Microsoft.CmdPal.Ext.Apps.csproj">
@@ -275,16 +279,6 @@
<Deploy />
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/Core/">
<Project Path="src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Microsoft.CmdPal.Core.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/Microsoft.CmdPal.Core.ViewModels.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/Extension SDK/">
<Project Path="src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />

View File

@@ -10,8 +10,7 @@
"src\\common\\version\\version.vcxproj",
"src\\modules\\cmdpal\\CmdPalKeyboardService\\CmdPalKeyboardService.vcxproj",
"src\\modules\\cmdpal\\CmdPalModuleInterface\\CmdPalModuleInterface.vcxproj",
"src\\modules\\cmdpal\\Core\\Microsoft.CmdPal.Core.Common\\Microsoft.CmdPal.Core.Common.csproj",
"src\\modules\\cmdpal\\Core\\Microsoft.CmdPal.Core.ViewModels\\Microsoft.CmdPal.Core.ViewModels.csproj",
"src\\modules\\cmdpal\\Microsoft.CmdPal.Common\\Microsoft.CmdPal.Common.csproj",
"src\\modules\\cmdpal\\Microsoft.CmdPal.UI.ViewModels\\Microsoft.CmdPal.UI.ViewModels.csproj",
"src\\modules\\cmdpal\\Microsoft.CmdPal.UI\\Microsoft.CmdPal.UI.csproj",
"src\\modules\\cmdpal\\Microsoft.Terminal.UI\\Microsoft.Terminal.UI.vcxproj",

View File

@@ -1,62 +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;
namespace Microsoft.CmdPal.Core.Common;
public static class CoreLogger
{
public static void InitializeLogger(ILogger implementation)
{
_logger = implementation;
}
private static ILogger? _logger;
public static void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogError(message, ex, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogError(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogWarning(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogInfo(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogDebug(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogTrace(memberName, sourceFilePath, sourceLineNumber);
}
}
public interface ILogger
{
void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
}

View File

@@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\CoreCommonProps.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Core.Common</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -1,37 +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.Collections.Generic;
using System.Threading.Tasks;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.Common.Services;
public interface IExtensionService
{
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false);
// Task<IEnumerable<string>> GetInstalledHomeWidgetPackageFamilyNamesAsync(bool includeDisabledExtensions = false);
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(Microsoft.CommandPalette.Extensions.ProviderType providerType, bool includeDisabledExtensions = false);
IExtensionWrapper? GetInstalledExtension(string extensionUniqueId);
Task SignalStopExtensionsAsync();
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
void EnableExtension(string extensionUniqueId);
void DisableExtension(string extensionUniqueId);
///// <summary>
///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature
///// being absent from the machine or in an unknown state.
///// </summary>
///// <param name="extension">The out of proc extension object</param>
///// <returns>True only if the extension was disabled. False otherwise.</returns>
// public Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension);
}

View File

@@ -1,12 +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.
namespace Microsoft.CmdPal.Core.ViewModels;
/// <summary>
/// Encapsulates a navigation request within Command Palette view models.
/// </summary>
/// <param name="TargetViewModel">A view model that should be navigated to.</param>
/// <param name="NavigationToken"> A <see cref="CancellationToken"/> that can be used to cancel the pending navigation.</param>
public record AsyncNavigationRequest(object? TargetViewModel, CancellationToken NavigationToken);

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\CoreCommonProps.props" />
<PropertyGroup>
<EnableCoreMrtTooling>false</EnableCoreMrtTooling>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Common" />
<PackageReference Include="CommunityToolkit.Mvvm" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.CmdPal.Core.Common\Microsoft.CmdPal.Core.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -1,72 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CmdPal.Core.ViewModels.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Core.ViewModels.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Show details.
/// </summary>
public static string ShowDetailsCommand {
get {
return ResourceManager.GetString("ShowDetailsCommand", resourceCulture);
}
}
}
}

View File

@@ -1,124 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ShowDetailsCommand" xml:space="preserve">
<value>Show details</value>
<comment>Name for the command that shows details of an item</comment>
</data>
</root>

View File

@@ -0,0 +1,167 @@
// 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 ManagedCommon;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Common;
// Adapter implementing Microsoft.Extensions.Logging.ILogger,
// delegating to ManagedCommon.Logger.
public sealed partial class CmdPalLogger : ILogger
{
private static readonly AsyncLocal<Stack<object>> _scopeStack = new();
private readonly LogLevel _minLevel;
public string CurrentVersionLogDirectoryPath => Logger.CurrentVersionLogDirectoryPath;
public CmdPalLogger(LogLevel minLevel = LogLevel.Information)
{
_minLevel = minLevel;
// Ensure underlying logger initialized (idempotent if already done elsewhere).
Logger.InitializeLogger("\\CmdPal\\Logs\\");
}
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None && logLevel >= _minLevel;
public IDisposable? BeginScope<TState>(TState state)
where TState : notnull
{
var stack = _scopeStack.Value;
if (stack is null)
{
stack = new Stack<object>();
_scopeStack.Value = stack;
}
stack.Push(state);
return new Scope(stack);
}
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
ArgumentNullException.ThrowIfNull(formatter);
var message = formatter(state, exception);
if (string.IsNullOrEmpty(message) && exception is null)
{
return;
}
var scopeSuffix = BuildScopeSuffix();
var eventPrefix = eventId.Id != 0 ? $"[{eventId.Id}/{eventId.Name}] " : string.Empty;
var finalMessage = $"{eventPrefix}{message}{scopeSuffix}";
switch (logLevel)
{
case LogLevel.Trace:
// Existing stack: Trace logs an empty line; append message via Debug.
Logger.LogTrace();
if (!string.IsNullOrEmpty(message))
{
Logger.LogDebug(finalMessage);
}
if (exception is not null)
{
Logger.LogError(exception.Message, exception);
}
break;
case LogLevel.Debug:
Logger.LogDebug(finalMessage);
if (exception is not null)
{
Logger.LogError(exception.Message, exception);
}
break;
case LogLevel.Information:
Logger.LogInfo(finalMessage);
if (exception is not null)
{
Logger.LogError(exception.Message, exception);
}
break;
case LogLevel.Warning:
Logger.LogWarning(finalMessage);
if (exception is not null)
{
Logger.LogError(exception.Message, exception);
}
break;
case LogLevel.Error:
case LogLevel.Critical:
if (exception is not null)
{
Logger.LogError(finalMessage, exception);
}
else
{
Logger.LogError(finalMessage);
}
break;
case LogLevel.None:
default:
break;
}
}
private static string BuildScopeSuffix()
{
var stack = _scopeStack.Value;
if (stack is null || stack.Count == 0)
{
return string.Empty;
}
// Show most-recent first.
return $" [Scopes: {string.Join(" => ", stack.ToArray())}]";
}
private sealed partial class Scope : IDisposable
{
private readonly Stack<object> _stack;
private bool _disposed;
public Scope(Stack<object> stack) => _stack = stack;
public void Dispose()
{
if (_disposed)
{
return;
}
if (_stack.Count > 0)
{
_stack.Pop();
}
_disposed = true;
}
}
}

View File

@@ -5,7 +5,7 @@
using System;
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Provides utility methods for building diagnostic and error messages.

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.Common;
namespace Microsoft.CmdPal.Common;
public partial class ExtensionHostInstance
{

View File

@@ -4,7 +4,7 @@
using System.Threading;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Thread-safe boolean implementation using atomic operations

View File

@@ -7,7 +7,7 @@ using System.Threading;
using Microsoft.UI.Dispatching;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
public static partial class NativeEventWaiter
{

View File

@@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
using Windows.Win32;
using Windows.Win32.Storage.FileSystem;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
public static class PathHelper
{

View File

@@ -6,7 +6,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// An async gate that ensures only one operation runs at a time.

View File

@@ -2,7 +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.
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// An async gate that ensures only one value computation runs at a time.

View File

@@ -3,9 +3,10 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Windows.ApplicationModel;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Helper class for retrieving application version information safely.
@@ -37,7 +38,7 @@ internal static class VersionHelper
/// </summary>
/// <param name="version">The version string if successful, or an empty string if unsuccessful.</param>
/// <returns>True if the version was retrieved successfully; otherwise, false.</returns>
private static bool TryGetPackagedVersion(out string version)
private static bool TryGetPackagedVersion(out string version, ILogger logger)
{
version = string.Empty;
try
@@ -53,7 +54,7 @@ internal static class VersionHelper
}
catch (Exception ex)
{
CoreLogger.LogError("Failed to get version from the package", ex);
Log_FailedToGetVersion(logger, ex);
return false;
}
}
@@ -63,7 +64,7 @@ internal static class VersionHelper
/// </summary>
/// <param name="version">The version string if successful, or an empty string if unsuccessful.</param>
/// <returns>True if the version was retrieved successfully; otherwise, false.</returns>
private static bool TryGetAssemblyVersion(out string version)
private static bool TryGetAssemblyVersion(out string version, ILogger logger)
{
version = string.Empty;
try
@@ -80,8 +81,14 @@ internal static class VersionHelper
}
catch (Exception ex)
{
CoreLogger.LogError("Failed to get version from the executable", ex);
Log_FailedToGetVersionFromExe(logger, ex);
return false;
}
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to get version from the package")]
static partial void Log_FailedToGetVersion(ILogger logger, Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to get version from the executable")]
static partial void Log_FailedToGetVersionFromExe(ILogger logger, Exception ex);
}

View File

@@ -7,7 +7,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Well-known key chords used in the Command Palette and extensions.

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\CoreCommonProps.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Common</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,164 @@
// 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.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Common;
public partial class PersistenceService
{
private static bool TryParseJsonObject(string json, ILogger logger, [NotNullWhen(true)] out JsonObject? obj)
{
obj = null;
try
{
obj = JsonNode.Parse(json) as JsonObject;
return obj is not null;
}
catch (Exception ex)
{
Log_PersistenceParseFailure(logger, ex);
return false;
}
}
private static bool TryReadSavedObject(string filePath, ILogger logger, [NotNullWhen(true)] out JsonObject? saved)
{
saved = null;
string oldContent;
try
{
if (!File.Exists(filePath))
{
saved = new JsonObject();
return true;
}
oldContent = File.ReadAllText(filePath);
}
catch (Exception ex)
{
Log_PersistenceReadFileFailure(logger, filePath, ex);
return false;
}
if (string.IsNullOrWhiteSpace(oldContent))
{
Log_FileEmpty(logger, filePath);
return false;
}
return TryParseJsonObject(oldContent, logger, out saved);
}
public static T LoadObject<T>(string filePath, JsonTypeInfo<T> typeInfo, ILogger logger)
where T : new()
{
if (string.IsNullOrEmpty(filePath))
{
throw new InvalidOperationException($"You must set a valid file path before loading {typeof(T).Name}");
}
if (!File.Exists(filePath))
{
Log_FileDoesntExist(logger, typeof(T).Name, filePath);
return new T();
}
try
{
var jsonContent = File.ReadAllText(filePath);
var loaded = JsonSerializer.Deserialize(jsonContent, typeInfo);
return loaded ?? new T();
}
catch (Exception ex)
{
Log_PersistenceReadFailure(logger, typeof(T).Name, filePath, ex);
return new T();
}
}
public static void SaveObject<T>(
T model,
string filePath,
JsonTypeInfo<T> typeInfo,
JsonSerializerOptions optionsForWrite,
Action<JsonObject>? beforeWriteMutation,
Action<T>? afterWriteCallback,
ILogger logger)
{
if (string.IsNullOrEmpty(filePath))
{
throw new InvalidOperationException($"You must set a valid file path before saving {typeof(T).Name}");
}
try
{
var json = JsonSerializer.Serialize(model, typeInfo);
if (!TryParseJsonObject(json, logger, out var newObj))
{
Log_SerializationError(logger, typeof(T).Name);
return;
}
if (!TryReadSavedObject(filePath, logger, out var savedObj))
{
savedObj = new JsonObject();
}
foreach (var kvp in newObj)
{
savedObj[kvp.Key] = kvp.Value?.DeepClone();
}
beforeWriteMutation?.Invoke(savedObj);
var serialized = savedObj.ToJsonString(optionsForWrite);
File.WriteAllText(filePath, serialized);
afterWriteCallback?.Invoke(model);
}
catch (Exception ex)
{
Log_PersistenceSaveFailure(logger, typeof(T).Name, filePath, ex);
}
}
public static string SettingsJsonPath(string fileName)
{
var directory = Utilities.BaseSettingsPath("Microsoft.CommandPalette");
Directory.CreateDirectory(directory);
// now, the settings is just next to the exe
return Path.Combine(directory, fileName);
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to save {typeName} to '{filePath}'.")]
static partial void Log_PersistenceSaveFailure(ILogger logger, string typeName, string filePath, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to read {typeName} from '{filePath}'.")]
static partial void Log_PersistenceReadFailure(ILogger logger, string typeName, string filePath, Exception exception);
[LoggerMessage(Level = LogLevel.Debug, Message = "Failed to serialize {typeName} to JsonObject.")]
static partial void Log_SerializationError(ILogger logger, string typeName);
[LoggerMessage(Level = LogLevel.Debug, Message = "The provided {typeName} file does not exist ({filePath})")]
static partial void Log_FileDoesntExist(ILogger logger, string typeName, string filePath);
[LoggerMessage(Level = LogLevel.Debug, Message = "The file at '{filePath}' is empty.")]
static partial void Log_FileEmpty(ILogger logger, string filePath);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to read file at '{filePath}'.")]
static partial void Log_PersistenceReadFileFailure(ILogger logger, string filePath, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to parse persisted JSON.")]
static partial void Log_PersistenceParseFailure(ILogger logger, Exception exception);
}

View File

@@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CmdPal.Core.Common.Properties {
namespace Microsoft.CmdPal.Common.Properties {
using System;
@@ -22,7 +22,7 @@ namespace Microsoft.CmdPal.Core.Common.Properties {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
@@ -36,10 +36,10 @@ namespace Microsoft.CmdPal.Core.Common.Properties {
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Core.Common.Properties.Resources", typeof(Resources).Assembly);
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Common.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -51,7 +51,7 @@ namespace Microsoft.CmdPal.Core.Common.Properties {
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -67,7 +67,7 @@ namespace Microsoft.CmdPal.Core.Common.Properties {
///
///(While youre at it, give the details below a quick skim — just to make sure theres nothing personal youd prefer not to share. Its rare, but sometimes little surprises sneak in.).
/// </summary>
internal static string ErrorReport_Global_Preamble {
public static string ErrorReport_Global_Preamble {
get {
return ResourceManager.GetString("ErrorReport_Global_Preamble", resourceCulture);
}

View File

@@ -5,8 +5,8 @@
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.ApplicationModel;
namespace Microsoft.CmdPal.Core.Common.Services;
@@ -14,20 +14,22 @@ namespace Microsoft.CmdPal.Core.Common.Services;
/// <summary>
/// Implementation of IApplicationInfoService providing application-wide information.
/// </summary>
public sealed class ApplicationInfoService : IApplicationInfoService
public sealed partial class ApplicationInfoService : IApplicationInfoService
{
private readonly Lazy<string> _configDirectory = new(() => Utilities.BaseSettingsPath("Microsoft.CmdPal"));
private readonly Lazy<bool> _isElevated;
private readonly Lazy<string> _logDirectory;
private readonly Lazy<AppPackagingFlavor> _packagingFlavor;
private readonly ILogger _logger;
private Func<string>? _getLogDirectory;
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationInfoService"/> class.
/// The log directory delegate can be set later via <see cref="SetLogDirectory(Func{string})"/>.
/// </summary>
public ApplicationInfoService()
public ApplicationInfoService(ILogger logger)
{
_logger = logger;
_packagingFlavor = new Lazy<AppPackagingFlavor>(DeterminePackagingFlavor);
_isElevated = new Lazy<bool>(DetermineElevationStatus);
_logDirectory = new Lazy<string>(() => _getLogDirectory?.Invoke() ?? "Not available");
@@ -37,10 +39,13 @@ public sealed class ApplicationInfoService : IApplicationInfoService
/// Initializes a new instance of the <see cref="ApplicationInfoService"/> class with an optional log directory provider.
/// </summary>
/// <param name="getLogDirectory">Optional delegate to retrieve the log directory path. If not provided, the log directory will be unavailable.</param>
public ApplicationInfoService(Func<string>? getLogDirectory)
: this()
public ApplicationInfoService(
Func<string>? getLogDirectory,
ILogger logger)
: this(logger)
{
_getLogDirectory = getLogDirectory;
_logger = logger;
}
/// <summary>
@@ -87,7 +92,7 @@ public sealed class ApplicationInfoService : IApplicationInfoService
""";
}
private static AppPackagingFlavor DeterminePackagingFlavor()
private AppPackagingFlavor DeterminePackagingFlavor()
{
// Try to determine if running as packaged
try
@@ -105,7 +110,7 @@ public sealed class ApplicationInfoService : IApplicationInfoService
}
catch (Exception ex)
{
CoreLogger.LogError("Failed to determine packaging flavor", ex);
Log_FailedToDeterminePackagingFlavor(ex);
return AppPackagingFlavor.Unpackaged;
}
}
@@ -122,4 +127,7 @@ public sealed class ApplicationInfoService : IApplicationInfoService
return false;
}
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to determine packaging flavor")]
partial void Log_FailedToDeterminePackagingFlavor(Exception ex);
}

View File

@@ -2,7 +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.
namespace Microsoft.CmdPal.Core.Common.Services;
namespace Microsoft.CmdPal.Common.Services;
public interface IRunHistoryService
{

View File

@@ -3,45 +3,44 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class AliasManager : ObservableObject
{
private readonly TopLevelCommandManager _topLevelCommandManager;
// private readonly TopLevelCommandManager _topLevelCommandManager;
private readonly SettingsService _settingsService;
// REMEMBER, CommandAlias.SearchPrefix is what we use as keys
private readonly Dictionary<string, CommandAlias> _aliases;
private Dictionary<string, CommandAlias> Aliases => _settingsService.CurrentSettings.Aliases;
public AliasManager(TopLevelCommandManager tlcManager, SettingsModel settings)
// TopLevelCommandManager tlcManager,
public AliasManager(SettingsService settingsService)
{
_topLevelCommandManager = tlcManager;
_aliases = settings.Aliases;
// _topLevelCommandManager = tlcManager;
_settingsService = settingsService;
if (_aliases.Count == 0)
if (Aliases.Count == 0)
{
PopulateDefaultAliases();
}
}
private void AddAlias(CommandAlias a) => _aliases.Add(a.SearchPrefix, a);
private void AddAlias(CommandAlias a) => Aliases.Add(a.SearchPrefix, a);
public bool CheckAlias(string searchText)
{
if (_aliases.TryGetValue(searchText, out var alias))
if (Aliases.TryGetValue(searchText, out var alias))
{
try
{
var topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
if (topLevelCommand is not null)
{
WeakReferenceMessenger.Default.Send<ClearSearchMessage>();
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(topLevelCommand.GetPerformCommandMessage());
return true;
}
// var topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
// if (topLevelCommand is not null)
// {
// WeakReferenceMessenger.Default.Send<ClearSearchMessage>();
// WeakReferenceMessenger.Default.Send<PerformCommandMessage>(topLevelCommand.GetPerformCommandMessage());
// return true;
// }
}
catch
{
@@ -65,7 +64,7 @@ public partial class AliasManager : ObservableObject
public string? KeysFromId(string commandId)
{
return _aliases
return Aliases
.Where(kv => kv.Value.CommandId == commandId)
.Select(kv => kv.Value.Alias)
.FirstOrDefault();
@@ -73,7 +72,7 @@ public partial class AliasManager : ObservableObject
public CommandAlias? AliasFromId(string commandId)
{
return _aliases
return Aliases
.Where(kv => kv.Value.CommandId == commandId)
.Select(kv => kv.Value)
.FirstOrDefault();
@@ -89,7 +88,7 @@ public partial class AliasManager : ObservableObject
// If we already have _this exact alias_, do nothing
if (newAlias is not null &&
_aliases.TryGetValue(newAlias.SearchPrefix, out var existingAlias))
Aliases.TryGetValue(newAlias.SearchPrefix, out var existingAlias))
{
if (existingAlias.CommandId == commandId)
{
@@ -98,7 +97,7 @@ public partial class AliasManager : ObservableObject
}
List<CommandAlias> toRemove = [];
foreach (var kv in _aliases)
foreach (var kv in Aliases)
{
// Look for the old aliases for the command, and remove it
if (kv.Value.CommandId == commandId)
@@ -112,18 +111,18 @@ public partial class AliasManager : ObservableObject
toRemove.Add(kv.Value);
// Remove alias from other TopLevelViewModels it may be assigned to
var topLevelCommand = _topLevelCommandManager.LookupCommand(kv.Value.CommandId);
if (topLevelCommand is not null)
{
topLevelCommand.AliasText = string.Empty;
}
// var topLevelCommand = _topLevelCommandManager.LookupCommand(kv.Value.CommandId);
// if (topLevelCommand is not null)
// {
// topLevelCommand.AliasText = string.Empty;
// }
}
}
foreach (var alias in toRemove)
{
// REMEMBER, SearchPrefix is what we use as keys
_aliases.Remove(alias.SearchPrefix);
Aliases.Remove(alias.SearchPrefix);
}
if (newAlias is not null)

View File

@@ -1,18 +1,20 @@
// Copyright (c) Microsoft Corporation
// 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.Collections.ObjectModel;
using System.Diagnostics;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public abstract partial class AppExtensionHost : IExtensionHost
{
private readonly ILogger _logger;
private static readonly GlobalLogPageContext _globalLogPageContext = new();
private static ulong _hostingHwnd;
@@ -27,6 +29,11 @@ public abstract partial class AppExtensionHost : IExtensionHost
public static void SetHostHwnd(ulong hostHwnd) => _hostingHwnd = hostHwnd;
public AppExtensionHost(ILogger logger)
{
_logger = logger;
}
public void DebugLog(string message)
{
#if DEBUG
@@ -60,7 +67,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return Task.CompletedTask.AsAsyncAction();
}
CoreLogger.LogDebug(message.Message);
Log_Message(message.Message);
_ = Task.Run(() =>
{
@@ -158,6 +165,9 @@ public abstract partial class AppExtensionHost : IExtensionHost
}
public abstract string? GetExtensionDisplayName();
[LoggerMessage(Level = LogLevel.Debug, Message = "{message}")]
partial void Log_Message(string message);
}
public interface IAppHostService

View File

@@ -2,25 +2,12 @@
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class AppStateModel : ObservableObject
{
[JsonIgnore]
public static readonly string FilePath;
public event TypedEventHandler<AppStateModel, object?>? StateChanged;
///////////////////////////////////////////////////////////////////////////
// STATE HERE
// Make sure that you make the setters public (JsonSerializer.Deserialize will fail silently otherwise)!
@@ -28,144 +15,4 @@ public partial class AppStateModel : ObservableObject
public RecentCommandsManager RecentCommands { get; set; } = new();
public List<string> RunHistory { get; set; } = [];
// END SETTINGS
///////////////////////////////////////////////////////////////////////////
static AppStateModel()
{
FilePath = StateJsonPath();
}
public static AppStateModel LoadState()
{
if (string.IsNullOrEmpty(FilePath))
{
throw new InvalidOperationException($"You must set a valid {nameof(FilePath)} before calling {nameof(LoadState)}");
}
if (!File.Exists(FilePath))
{
Debug.WriteLine("The provided settings file does not exist");
return new();
}
try
{
// Read the JSON content from the file
var jsonContent = File.ReadAllText(FilePath);
var loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, JsonSerializationContext.Default.AppStateModel);
Debug.WriteLine(loaded is not null ? "Loaded settings file" : "Failed to parse");
return loaded ?? new();
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
return new();
}
public static void SaveState(AppStateModel model)
{
if (string.IsNullOrEmpty(FilePath))
{
throw new InvalidOperationException($"You must set a valid {nameof(FilePath)} before calling {nameof(SaveState)}");
}
try
{
// Serialize the main dictionary to JSON and save it to the file
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel!);
// validate JSON
if (JsonNode.Parse(settingsJson) is not JsonObject newSettings)
{
Logger.LogError("Failed to parse app state as a JsonObject.");
return;
}
// read previous settings
if (!TryReadSavedState(out var savedSettings))
{
savedSettings = new JsonObject();
}
// merge new settings into old ones
foreach (var item in newSettings)
{
savedSettings[item.Key] = item.Value?.DeepClone();
}
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel!.Options);
File.WriteAllText(FilePath, serialized);
// TODO: Instead of just raising the event here, we should
// have a file change watcher on the settings file, and
// reload the settings then
model.StateChanged?.Invoke(model, null);
}
catch (Exception ex)
{
Logger.LogError($"Failed to save application state to {FilePath}:", ex);
}
}
private static bool TryReadSavedState([NotNullWhen(true)] out JsonObject? savedSettings)
{
savedSettings = null;
// read existing content from the file
string oldContent;
try
{
if (File.Exists(FilePath))
{
oldContent = File.ReadAllText(FilePath);
}
else
{
// file doesn't exist (might not have been created yet), so consider this a success
// and return empty settings
savedSettings = new JsonObject();
return true;
}
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to read app state file {FilePath}:\n{ex}");
return false;
}
// detect empty file, just for sake of logging
if (string.IsNullOrWhiteSpace(oldContent))
{
Logger.LogInfo($"App state file is empty: {FilePath}");
return false;
}
// is it valid JSON?
try
{
savedSettings = JsonNode.Parse(oldContent) as JsonObject;
return savedSettings != null;
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to parse app state from {FilePath}:\n{ex}");
return false;
}
}
internal static string StateJsonPath()
{
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
Directory.CreateDirectory(directory);
// now, the settings is just next to the exe
return Path.Combine(directory, "state.json");
}
}

View File

@@ -0,0 +1,50 @@
// 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 Microsoft.CmdPal.Common;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class AppStateService
{
private readonly ILogger _logger;
private readonly string _filePath;
private AppStateModel _appStateModel;
public event TypedEventHandler<AppStateModel, object?>? StateChanged;
public AppStateModel CurrentSettings => _appStateModel;
public AppStateService(ILogger logger)
{
_logger = logger;
_filePath = PersistenceService.SettingsJsonPath("state.json");
_appStateModel = LoadState();
}
private AppStateModel LoadState()
{
return PersistenceService.LoadObject<AppStateModel>(_filePath, JsonSerializationContext.Default.AppStateModel!, _logger);
}
public void SaveSettings(AppStateModel model)
{
PersistenceService.SaveObject(
model,
_filePath,
JsonSerializationContext.Default.AppStateModel,
JsonSerializationContext.Default.Options,
null,
afterWriteCallback: m => FinalizeStateSave(m),
_logger);
}
private void FinalizeStateSave(AppStateModel model)
{
_appStateModel = model;
StateChanged?.Invoke(model, null);
}
}

View File

@@ -87,7 +87,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
Color.FromArgb(255, 126, 115, 95), // #7e735f
];
private readonly SettingsModel _settings;
private readonly SettingsService _settingsService;
private readonly UISettings _uiSettings;
private readonly IThemeService _themeService;
private readonly DispatcherQueueTimer _saveTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
@@ -96,22 +96,24 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
private ElementTheme? _elementThemeOverride;
private Color _currentSystemAccentColor;
private SettingsModel Settings => _settingsService.CurrentSettings;
public ObservableCollection<Color> Swatches => WindowsColorSwatches;
public int ThemeIndex
{
get => (int)_settings.Theme;
get => (int)Settings.Theme;
set => Theme = (UserTheme)value;
}
public UserTheme Theme
{
get => _settings.Theme;
get => Settings.Theme;
set
{
if (_settings.Theme != value)
if (Settings.Theme != value)
{
_settings.Theme = value;
Settings.Theme = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ThemeIndex));
Save();
@@ -121,12 +123,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public ColorizationMode ColorizationMode
{
get => _settings.ColorizationMode;
get => Settings.ColorizationMode;
set
{
if (_settings.ColorizationMode != value)
if (Settings.ColorizationMode != value)
{
_settings.ColorizationMode = value;
Settings.ColorizationMode = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ColorizationModeIndex));
OnPropertyChanged(nameof(IsCustomTintVisible));
@@ -152,18 +154,18 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int ColorizationModeIndex
{
get => (int)_settings.ColorizationMode;
get => (int)Settings.ColorizationMode;
set => ColorizationMode = (ColorizationMode)value;
}
public Color ThemeColor
{
get => _settings.CustomThemeColor;
get => Settings.CustomThemeColor;
set
{
if (_settings.CustomThemeColor != value)
if (Settings.CustomThemeColor != value)
{
_settings.CustomThemeColor = value;
Settings.CustomThemeColor = value;
OnPropertyChanged();
@@ -179,10 +181,10 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int ColorIntensity
{
get => _settings.CustomThemeColorIntensity;
get => Settings.CustomThemeColorIntensity;
set
{
_settings.CustomThemeColorIntensity = value;
Settings.CustomThemeColorIntensity = value;
OnPropertyChanged();
OnPropertyChanged(nameof(EffectiveTintIntensity));
Save();
@@ -203,12 +205,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public string BackgroundImagePath
{
get => _settings.BackgroundImagePath ?? string.Empty;
get => Settings.BackgroundImagePath ?? string.Empty;
set
{
if (_settings.BackgroundImagePath != value)
if (Settings.BackgroundImagePath != value)
{
_settings.BackgroundImagePath = value;
Settings.BackgroundImagePath = value;
OnPropertyChanged();
if (BackgroundImageOpacity == 0)
@@ -223,12 +225,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int BackgroundImageOpacity
{
get => _settings.BackgroundImageOpacity;
get => Settings.BackgroundImageOpacity;
set
{
if (_settings.BackgroundImageOpacity != value)
if (Settings.BackgroundImageOpacity != value)
{
_settings.BackgroundImageOpacity = value;
Settings.BackgroundImageOpacity = value;
OnPropertyChanged();
Save();
}
@@ -237,12 +239,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int BackgroundImageBrightness
{
get => _settings.BackgroundImageBrightness;
get => Settings.BackgroundImageBrightness;
set
{
if (_settings.BackgroundImageBrightness != value)
if (Settings.BackgroundImageBrightness != value)
{
_settings.BackgroundImageBrightness = value;
Settings.BackgroundImageBrightness = value;
OnPropertyChanged();
Save();
}
@@ -251,12 +253,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int BackgroundImageBlurAmount
{
get => _settings.BackgroundImageBlurAmount;
get => Settings.BackgroundImageBlurAmount;
set
{
if (_settings.BackgroundImageBlurAmount != value)
if (Settings.BackgroundImageBlurAmount != value)
{
_settings.BackgroundImageBlurAmount = value;
Settings.BackgroundImageBlurAmount = value;
OnPropertyChanged();
Save();
}
@@ -265,12 +267,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public BackgroundImageFit BackgroundImageFit
{
get => _settings.BackgroundImageFit;
get => Settings.BackgroundImageFit;
set
{
if (_settings.BackgroundImageFit != value)
if (Settings.BackgroundImageFit != value)
{
_settings.BackgroundImageFit = value;
Settings.BackgroundImageFit = value;
OnPropertyChanged();
OnPropertyChanged(nameof(BackgroundImageFitIndex));
Save();
@@ -387,7 +389,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
[ObservableProperty]
public partial bool IsColorizationDetailsExpanded { get; set; }
public bool IsCustomTintVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.Image;
public bool IsCustomTintVisible => Settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.Image;
public bool IsColorIntensityVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor;
@@ -399,12 +401,13 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int EffectiveTintIntensity => _settings.ColorizationMode is ColorizationMode.Image
? _settings.BackgroundImageTintIntensity
: _settings.CustomThemeColorIntensity;
public bool IsCustomTintIntensityVisible => Settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image;
public bool IsBackgroundControlsVisible => _settings.ColorizationMode is ColorizationMode.Image;
public bool IsBackgroundControlsVisible => Settings.ColorizationMode is ColorizationMode.Image;
public bool IsNoBackgroundVisible => _settings.ColorizationMode is ColorizationMode.None;
public bool IsNoBackgroundVisible => Settings.ColorizationMode is ColorizationMode.None;
public bool IsAccentColorControlsVisible => _settings.ColorizationMode is ColorizationMode.WindowsAccentColor;
public bool IsAccentColorControlsVisible => Settings.ColorizationMode is ColorizationMode.WindowsAccentColor;
public bool IsResetButtonVisible => _settings.ColorizationMode is ColorizationMode.Image;
@@ -436,11 +439,11 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri)
: null;
public AppearanceSettingsViewModel(IThemeService themeService, SettingsModel settings)
public AppearanceSettingsViewModel(IThemeService themeService, SettingsService settingsService)
{
_themeService = themeService;
_themeService.ThemeChanged += ThemeServiceOnThemeChanged;
_settings = settings;
_settingsService = settingsService;
_uiSettings = new UISettings();
_uiSettings.ColorValuesChanged += UiSettingsOnColorValuesChanged;
@@ -448,7 +451,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
Reapply();
IsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None && IsBackgroundSettingsEnabled;
SettingsIsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None && IsBackgroundSettingsEnabled;
}
private void UiSettingsOnColorValuesChanged(UISettings sender, object args) => _uiDispatcher.TryEnqueue(() => UpdateAccentColor(sender));
@@ -469,7 +472,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
private void Save()
{
SettingsModel.SaveSettings(_settings);
_settingsService.SaveSettings(Settings);
_saveTimer.Debounce(Reapply, TimeSpan.FromMilliseconds(200));
}

View File

@@ -0,0 +1,24 @@
// 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 Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
/// <summary>
/// Encapsulates a navigation request within Command Palette view models.
/// </summary>
/// <param name="TargetViewModel">A view model that should be navigated to.</param>
/// <param name="NavigationToken"> A <see cref="CancellationToken"/> that can be used to cancel the pending navigation.</param>
public record AsyncNavigationRequest(object? TargetViewModel, CancellationToken NavigationToken);
#pragma warning disable SA1402 // File may only contain a single type
public record AsyncListPageNavigationRequest(object? TargetViewModel, SettingsService SettingsService, ILogger Logger, CancellationToken NavigationToken)
: AsyncNavigationRequest(TargetViewModel, NavigationToken);
public record AsyncContentPageNavigationRequest(object? TargetViewModel, object? ImageProvider, CancellationToken NavigationToken)
: AsyncNavigationRequest(TargetViewModel, NavigationToken);
#pragma warning restore SA1402 // File may only contain a single type

View File

@@ -4,15 +4,18 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.System;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandBarViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>
{
private readonly ILogger _logger;
public ICommandBarContext? SelectedItem
{
get => field;
@@ -48,8 +51,9 @@ public partial class CommandBarViewModel : ObservableObject,
[ObservableProperty]
public partial PageViewModel? CurrentPage { get; set; }
public CommandBarViewModel()
public CommandBarViewModel(ILogger logger)
{
_logger = logger;
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
}

View File

@@ -3,11 +3,11 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel

View File

@@ -3,20 +3,22 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Text;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Windows.ApplicationModel.DataTransfer;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext, IPrecomputedListItem
{
// Local logger field required for [LoggerMessage] source generator
private readonly ILogger _logger;
public ExtensionObject<ICommandItem> Model => _commandItemModel;
private ExtensionObject<IExtendedAttributesProvider>? ExtendedAttributesProvider { get; set; }
@@ -96,9 +98,10 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
_errorIcon.InitializeProperties();
}
public CommandItemViewModel(ExtensionObject<ICommandItem> item, WeakReference<IPageContext> errorContext)
public CommandItemViewModel(ExtensionObject<ICommandItem> item, WeakReference<IPageContext> errorContext, ILogger? logger = null)
: base(errorContext)
{
_logger = logger ?? NullLogger.Instance;
_commandItemModel = item;
Command = new(null, errorContext);
}
@@ -250,7 +253,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
catch (Exception ex)
{
CoreLogger.LogError("error fast initializing CommandItemViewModel", ex);
Log_FastInitError(ex);
Command = new(null, PageContext);
_itemTitle = "Error";
Subtitle = "Item failed to load";
@@ -274,7 +277,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
catch (Exception ex)
{
Initialized |= InitializedState.Error;
CoreLogger.LogError("error slow initializing CommandItemViewModel", ex);
Log_SlowInitError(ex);
}
return false;
@@ -289,7 +292,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
catch (Exception ex)
{
CoreLogger.LogError("error initializing CommandItemViewModel", ex);
Log_InitError(ex);
Command = new(null, PageContext);
_itemTitle = "Error";
Subtitle = "Item failed to load";
@@ -510,6 +513,15 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
base.SafeCleanup();
Initialized |= InitializedState.CleanedUp;
}
[LoggerMessage(Level = LogLevel.Error, Message = "Error fast initializing CommandItemViewModel")]
partial void Log_FastInitError(Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Error slow initializing CommandItemViewModel")]
partial void Log_SlowInitError(Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Error initializing CommandItemViewModel")]
partial void Log_InitError(Exception ex);
}
[Flags]

View File

@@ -2,7 +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 Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;

View File

@@ -2,32 +2,31 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public sealed partial class CommandPaletteHost : AppExtensionHost, IExtensionHost
{
// Static singleton, so that we can access this from anywhere
// Post MVVM - this should probably be like, a dependency injection thing.
public static CommandPaletteHost Instance { get; } = new();
public IExtensionWrapper? Extension { get; }
private readonly ICommandProvider? _builtInProvider;
private CommandPaletteHost()
public CommandPaletteHost(ILogger logger)
: base(logger)
{
}
public CommandPaletteHost(IExtensionWrapper source)
public CommandPaletteHost(IExtensionWrapper source, ILogger logger)
: base(logger)
{
Extension = source;
}
public CommandPaletteHost(ICommandProvider builtInProvider)
public CommandPaletteHost(ICommandProvider builtInProvider, ILogger logger)
: base(logger)
{
_builtInProvider = builtInProvider;
}

View File

@@ -2,8 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -11,17 +11,19 @@ public class CommandPalettePageViewModelFactory
: IPageViewModelFactoryService
{
private readonly TaskScheduler _scheduler;
private readonly ILogger _logger;
public CommandPalettePageViewModelFactory(TaskScheduler scheduler)
public CommandPalettePageViewModelFactory(TaskScheduler scheduler, ILogger logger)
{
_scheduler = scheduler;
_logger = logger;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host) { IsNested = nested },
IListPage listPage => new ListViewModel(listPage, _scheduler, host, _logger) { IsNested = nested },
IContentPage contentPage => new CommandPaletteContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};

View File

@@ -1,28 +1,27 @@
// Copyright (c) Microsoft Corporation
// 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 ManagedCommon;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public sealed class CommandProviderWrapper
public sealed partial class CommandProviderWrapper
{
public bool IsExtension => Extension is not null;
private readonly bool isValid;
private readonly ExtensionObject<ICommandProvider> _commandProvider;
private readonly TaskScheduler _taskScheduler;
private readonly ILogger _logger;
private readonly HotkeyManager _hotkeyManager;
private readonly AliasManager _aliasManager;
private readonly ICommandProviderCache? _commandProviderCache;
@@ -48,15 +47,24 @@ public sealed class CommandProviderWrapper
public string ProviderId => string.IsNullOrEmpty(Extension?.ExtensionUniqueId) ? Id : Extension.ExtensionUniqueId;
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread)
public CommandProviderWrapper(
ICommandProvider provider,
TaskScheduler mainThread,
HotkeyManager hotkeyManager,
AliasManager aliasManager,
ILogger logger)
{
_hotkeyManager = hotkeyManager;
_aliasManager = aliasManager;
// This ctor is only used for in-proc builtin commands. So the Unsafe!
// calls are pretty dang safe actually.
_commandProvider = new(provider);
_taskScheduler = mainThread;
_logger = logger;
// Hook the extension back into us
ExtensionHost = new CommandPaletteHost(provider);
ExtensionHost = new CommandPaletteHost(provider, logger);
_commandProvider.Unsafe!.InitializeWithHost(ExtensionHost);
_commandProvider.Unsafe!.ItemsChanged += CommandProvider_ItemsChanged;
@@ -71,16 +79,24 @@ public sealed class CommandProviderWrapper
// we do that, then we'd regress GH #38321
Settings = new(provider.Settings, this, _taskScheduler);
Logger.LogDebug($"Initialized command provider {ProviderId}");
Log_InitializedCommandProvider(ProviderId);
}
public CommandProviderWrapper(IExtensionWrapper extension, TaskScheduler mainThread, ICommandProviderCache commandProviderCache)
public CommandProviderWrapper(
IExtensionWrapper extension,
TaskScheduler mainThread,
HotkeyManager hotkeyManager,
AliasManager aliasManager,
ICommandProviderCache commandProviderCache,
ILogger logger)
{
_taskScheduler = mainThread;
_logger = logger;
_hotkeyManager = hotkeyManager;
_aliasManager = aliasManager;
_commandProviderCache = commandProviderCache;
Extension = extension;
ExtensionHost = new CommandPaletteHost(extension);
ExtensionHost = new CommandPaletteHost(extension, logger);
if (!Extension.IsRunning())
{
throw new ArgumentException("You forgot to start the extension. This is a CmdPal error - we need to make sure to call StartExtensionAsync");
@@ -105,13 +121,11 @@ public sealed class CommandProviderWrapper
isValid = true;
Logger.LogDebug($"Initialized extension command provider {Extension.PackageFamilyName}:{Extension.ExtensionUniqueId}");
Log_InitializedExtensionCommandProvider(Extension.PackageFamilyName, Extension.ExtensionUniqueId);
}
catch (Exception e)
{
Logger.LogError("Failed to initialize CommandProvider for extension.");
Logger.LogError($"Extension was {Extension!.PackageFamilyName}");
Logger.LogError(e.ToString());
Log_FailedToInitializeCommandProvider(Extension!.PackageFamilyName, e);
}
isValid = true;
@@ -122,7 +136,7 @@ public sealed class CommandProviderWrapper
return settings.GetProviderSettings(this);
}
public async Task LoadTopLevelCommands(IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
public async Task LoadTopLevelCommands(SettingsService settingsService, WeakReference<IPageContext> pageContext)
{
if (!isValid)
{
@@ -131,7 +145,7 @@ public sealed class CommandProviderWrapper
return;
}
var settings = serviceProvider.GetService<SettingsModel>()!;
var settings = settingsService.CurrentSettings;
var providerSettings = GetProviderSettings(settings);
IsActive = providerSettings.IsEnabled;
@@ -175,15 +189,13 @@ public sealed class CommandProviderWrapper
Settings = new(model.Settings, this, _taskScheduler);
// We do need to explicitly initialize commands though
InitializeCommands(commands, fallbacks, serviceProvider, pageContext);
InitializeCommands(commands, fallbacks, settingsService, pageContext);
Logger.LogDebug($"Loaded commands from {DisplayName} ({ProviderId})");
Log_LoadedCommands(DisplayName, ProviderId);
}
catch (Exception e)
{
Logger.LogError("Failed to load commands from extension");
Logger.LogError($"Extension was {Extension!.PackageFamilyName}");
Logger.LogError(e.ToString());
Log_FailedToLoadCommands(Extension!.PackageFamilyName, e);
if (!displayInfoInitialized)
{
@@ -206,15 +218,15 @@ public sealed class CommandProviderWrapper
}
}
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, SettingsService settingsService, WeakReference<IPageContext> pageContext)
{
var settings = serviceProvider.GetService<SettingsModel>()!;
var settings = settingsService.CurrentSettings;
var providerSettings = GetProviderSettings(settings);
var makeAndAdd = (ICommandItem? i, bool fallback) =>
{
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, providerSettings, serviceProvider, i);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settingsService, providerSettings, _hotkeyManager, _aliasManager, i);
topLevelViewModel.InitializeProperties();
return topLevelViewModel;
@@ -238,12 +250,12 @@ public sealed class CommandProviderWrapper
private void UnsafePreCacheApiAdditions(ICommandProvider2 provider)
{
var apiExtensions = provider.GetApiExtensionStubs();
Logger.LogDebug($"Provider supports {apiExtensions.Length} extensions");
Log_ProviderSupportsExtensions(apiExtensions.Length);
foreach (var a in apiExtensions)
{
if (a is IExtendedAttributesProvider command2)
{
Logger.LogDebug($"{ProviderId}: Found an IExtendedAttributesProvider");
Log_FoundExtendedAttributesProvider(ProviderId);
}
}
}
@@ -261,4 +273,25 @@ public sealed class CommandProviderWrapper
// In handling this, a call will be made to `LoadTopLevelCommands` to
// retrieve the new items.
this.CommandsChanged?.Invoke(this, args);
[LoggerMessage(Level = LogLevel.Debug, Message = "Initialized command provider {providerId}")]
private partial void Log_InitializedCommandProvider(string providerId);
[LoggerMessage(Level = LogLevel.Debug, Message = "Initialized extension command provider {packageFamilyName}:{extensionUniqueId}")]
private partial void Log_InitializedExtensionCommandProvider(string? packageFamilyName, string? extensionUniqueId);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to initialize CommandProvider for extension {packageFamilyName}")]
private partial void Log_FailedToInitializeCommandProvider(string? packageFamilyName, Exception ex);
[LoggerMessage(Level = LogLevel.Debug, Message = "Loaded commands from {displayName} ({providerId})")]
private partial void Log_LoadedCommands(string displayName, string providerId);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to load commands from extension {packageFamilyName}")]
private partial void Log_FailedToLoadCommands(string? packageFamilyName, Exception ex);
[LoggerMessage(Level = LogLevel.Debug, Message = "Provider supports {extensionCount} extensions")]
private partial void Log_ProviderSupportsExtensions(int extensionCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "{providerId}: Found an IExtendedAttributesProvider")]
private partial void Log_FoundExtendedAttributesProvider(string providerId);
}

View File

@@ -1,17 +1,29 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
public partial class CommandSettingsViewModel
{
private readonly ExtensionObject<ICommandSettings> _model = new(_unsafeSettings);
private readonly ILogger _logger;
private readonly ExtensionObject<ICommandSettings> _model;
private readonly CommandProviderWrapper _provider;
private readonly TaskScheduler _mainThread;
public CommandSettingsViewModel(ICommandSettings? unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread, ILogger? logger = null)
{
_model = new(unsafeSettings);
_provider = provider;
_mainThread = mainThread;
_logger = logger ?? NullLogger.Instance;
}
public ContentPageViewModel? SettingsPage { get; private set; }
@@ -31,7 +43,7 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
if (model.SettingsPage is not null)
{
SettingsPage = new CommandPaletteContentPageViewModel(model.SettingsPage, mainThread, provider.ExtensionHost);
SettingsPage = new CommandPaletteContentPageViewModel(model.SettingsPage, _mainThread, _provider.ExtensionHost);
SettingsPage.InitializeProperties();
}
}
@@ -44,7 +56,7 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
}
catch (Exception ex)
{
CoreLogger.LogError($"Failed to load settings page", ex: ex);
Log_FailedToLoadSettingsPage(ex);
}
Initialized = true;
@@ -56,6 +68,9 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
action,
CancellationToken.None,
TaskCreationOptions.None,
mainThread);
_mainThread);
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to load settings page")]
partial void Log_FailedToLoadSettingsPage(Exception ex);
}

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandViewModel : ExtensionObjectViewModel
{

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Common;
namespace Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;

View File

@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation
// 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.Collections.Specialized;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -6,10 +6,7 @@ using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Diagnostics;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Text;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Ext.Apps;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.State;
@@ -18,6 +15,7 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels.MainPage;
@@ -29,10 +27,11 @@ public sealed partial class MainListPage : DynamicListPage,
IRecipient<ClearSearchMessage>,
IRecipient<UpdateFallbackItemsMessage>, IDisposable
{
private readonly ILogger _logger;
private readonly TopLevelCommandManager _tlcManager;
private readonly AliasManager _aliasManager;
private readonly SettingsModel _settings;
private readonly AppStateModel _appStateModel;
private readonly SettingsService _settingsService;
private readonly AppStateService _appStateService;
private readonly ScoringFunction<IListItem> _scoringFunction;
private readonly ScoringFunction<IListItem> _fallbackScoringFunction;
private readonly IFuzzyMatcherProvider _fuzzyMatcherProvider;
@@ -40,6 +39,8 @@ public sealed partial class MainListPage : DynamicListPage,
private RoScored<IListItem>[]? _filteredItems;
private RoScored<IListItem>[]? _filteredApps;
private SettingsModel Settings => _settingsService.CurrentSettings;
// Keep as IEnumerable for deferred execution. Fallback item titles are updated
// asynchronously, so scoring must happen lazily when GetItems is called.
private IEnumerable<RoScored<IListItem>>? _scoredFallbackItems;
@@ -54,20 +55,24 @@ public sealed partial class MainListPage : DynamicListPage,
private CancellationTokenSource? _cancellationTokenSource;
private AppStateModel AppState => _appStateService.CurrentSettings;
public MainListPage(
TopLevelCommandManager topLevelCommandManager,
SettingsModel settings,
SettingsService settingsService,
AliasManager aliasManager,
AppStateModel appStateModel,
IFuzzyMatcherProvider fuzzyMatcherProvider)
AppStateService appStateService,
IFuzzyMatcherProvider fuzzyMatcherProvider,
ILogger logger)
{
Title = Resources.builtin_home_name;
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
_settings = settings;
_logger = logger;
_settingsService = settingsService;
_aliasManager = aliasManager;
_appStateModel = appStateModel;
_appStateService = appStateService;
_tlcManager = topLevelCommandManager;
_fuzzyMatcherProvider = fuzzyMatcherProvider;
_scoringFunction = (in query, item) => ScoreTopLevelItem(in query, item, _appStateModel.RecentCommands, _fuzzyMatcherProvider.Current);
@@ -90,8 +95,8 @@ public sealed partial class MainListPage : DynamicListPage,
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
WeakReferenceMessenger.Default.Register<UpdateFallbackItemsMessage>(this);
settings.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(settings);
_settingsService.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(Settings);
_includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);
IsLoading = true;
@@ -151,7 +156,7 @@ public sealed partial class MainListPage : DynamicListPage,
}
catch (Exception e)
{
Logger.LogError("Failed to reload search", e);
Log_SearchReloadFailed(e);
}
finally
{
@@ -173,6 +178,7 @@ public sealed partial class MainListPage : DynamicListPage,
{
return _tlcManager.TopLevelCommands
.Where(tlc => !tlc.IsFallback && !string.IsNullOrEmpty(tlc.Title))
.OrderByDescending(tlc => _appStateService.CurrentSettings.RecentCommands.GetCommandHistoryWeight(tlc.Id))
.ToArray();
}
else
@@ -256,7 +262,7 @@ public sealed partial class MainListPage : DynamicListPage,
}
// prefilter fallbacks
var globalFallbacks = _settings.GetGlobalFallbacks();
var globalFallbacks = Settings.GetGlobalFallbacks();
var specialFallbacks = new List<TopLevelViewModel>(globalFallbacks.Length);
var commonFallbacks = new List<TopLevelViewModel>();
@@ -431,8 +437,7 @@ public sealed partial class MainListPage : DynamicListPage,
RaiseItemsChanged();
var listPageUpdatedTimestamp = stopwatch.ElapsedMilliseconds;
Logger.LogDebug($"Render items with '{newSearch}' in {listPageUpdatedTimestamp}ms /d {listPageUpdatedTimestamp - filterDoneTimestamp}ms");
Log_FilterCompleted(newSearch, listPageUpdatedTimestamp, listPageUpdatedTimestamp - filterDoneTimestamp);
stopwatch.Stop();
}
}
@@ -583,9 +588,9 @@ public sealed partial class MainListPage : DynamicListPage,
public void UpdateHistory(IListItem topLevelOrAppItem)
{
var id = IdForTopLevelOrAppItem(topLevelOrAppItem);
var history = _appStateModel.RecentCommands;
var history = AppState.RecentCommands;
history.AddHistoryItem(id);
AppStateModel.SaveState(_appStateModel);
_appStateService.SaveSettings(AppState);
}
private static string IdForTopLevelOrAppItem(IListItem topLevelOrAppItem)
@@ -617,12 +622,15 @@ public sealed partial class MainListPage : DynamicListPage,
_tlcManager.PropertyChanged -= TlcManager_PropertyChanged;
_tlcManager.TopLevelCommands.CollectionChanged -= Commands_CollectionChanged;
if (_settings is not null)
{
_settings.SettingsChanged -= SettingsChangedHandler;
}
_settingsService.SettingsChanged -= SettingsChangedHandler;
WeakReferenceMessenger.Default.UnregisterAll(this);
GC.SuppressFinalize(this);
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to reload search results")]
partial void Log_SearchReloadFailed(Exception ex);
[LoggerMessage(Level = LogLevel.Debug, Message = ""Render items with '{newSearch}' in {listPageUpdatedTimestamp}ms /d {difference}ms"")]
partial void Log_FilterCompleted(string searchText, long listPageUpdatedTimestamp, long difference);
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -6,7 +6,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;

View File

@@ -1,9 +1,8 @@
// Copyright (c) Microsoft Corporation
// 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 CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReference<IPageContext> context) :
ExtensionObjectViewModel(context)

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -7,10 +7,9 @@ using System.Text.Json;
using AdaptiveCards.ObjectModel.WinUI3;
using AdaptiveCards.Templating;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Windows.Data.Json;
@@ -52,7 +51,7 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
}
catch (Exception ex)
{
Logger.LogError("Error building card from template", ex);
// Error is returned via output parameter for caller to handle
error = ex;
return false;
}

View File

@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;

View File

@@ -7,12 +7,12 @@ using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{

View File

@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation
// 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.Collections.ObjectModel;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;

View File

@@ -2,7 +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.
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public abstract partial class ContentViewModel(WeakReference<IPageContext> context) :
ExtensionObjectViewModel(context)

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -6,19 +6,19 @@ using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Text;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.System;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ContextMenuViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>
{
private readonly ILogger _logger;
private readonly IFuzzyMatcherProvider _fuzzyMatcherProvider;
public ICommandBarContext? SelectedItem
@@ -44,9 +44,11 @@ public partial class ContextMenuViewModel : ObservableObject,
private string _lastSearchText = string.Empty;
public ContextMenuViewModel(IFuzzyMatcherProvider fuzzyMatcherProvider)
public ContextMenuViewModel(IFuzzyMatcherProvider fuzzyMatcherProvider, ILogger logger)
{
_logger = logger;
_fuzzyMatcherProvider = fuzzyMatcherProvider;
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
}
@@ -158,7 +160,7 @@ public partial class ContextMenuViewModel : ObservableObject,
var added = result.TryAdd(key, cmd);
if (!added)
{
CoreLogger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
Log_DuplicateKeyboardShortcut(KeyChordHelpers.FormatForDebug(key), cmd.Title ?? cmd.Name ?? "(unknown)");
}
}
}
@@ -240,4 +242,7 @@ public partial class ContextMenuViewModel : ObservableObject,
return ContextKeybindingResult.Hide;
}
}
[LoggerMessage(Level = LogLevel.Warning, Message = "Ignoring duplicate keyboard shortcut {KeyChord} on command '{CommandName}'")]
partial void Log_DuplicateKeyboardShortcut(string keyChord, string commandName);
}

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class DetailsCommandsViewModel(
IDetailsElement _detailsElement,

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public abstract partial class DetailsDataViewModel(IPageContext context) : ExtensionObjectViewModel(context)
{

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public abstract partial class DetailsElementViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
{

View File

@@ -3,11 +3,11 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Input;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class DetailsLinkViewModel(
IDetailsElement _detailsElement,

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class DetailsSeparatorViewModel(
IDetailsElement _detailsElement,

View File

@@ -2,10 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class DetailsTagsViewModel(
IDetailsElement _detailsElement,

Some files were not shown because too many files have changed in this diff Show More