Initial log moving

This commit is contained in:
Michael Jolley
2025-12-01 17:00:51 -06:00
parent f510be4c53
commit 4395e98cf9
58 changed files with 830 additions and 551 deletions

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

@@ -4,9 +4,9 @@
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;
@@ -14,6 +14,7 @@ namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class AppExtensionHost : IExtensionHost
{
private static readonly GlobalLogPageContext _globalLogPageContext = new();
private readonly ILogger _logger;
private static ulong _hostingHwnd;
@@ -27,6 +28,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 +66,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return Task.CompletedTask.AsAsyncAction();
}
CoreLogger.LogDebug(message.Message);
Log_DebugMessage(message.Message);
_ = Task.Run(() =>
{
@@ -96,7 +102,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public void ProcessLogMessage(ILogMessage message)
{
var vm = new LogMessageViewModel(message, _globalLogPageContext);
var vm = new LogMessageViewModel(message, _globalLogPageContext, _logger);
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
@@ -127,7 +133,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return;
}
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext), _logger);
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
@@ -158,6 +164,11 @@ public abstract partial class AppExtensionHost : IExtensionHost
}
public abstract string? GetExtensionDisplayName();
public abstract AppExtensionHost GetHostForCommand(object? context, AppExtensionHost? currentHost);
[LoggerMessage(Level = LogLevel.Debug, Message = "{Message}")]
partial void Log_DebugMessage(string message);
}
public interface IAppHostService

View File

@@ -5,12 +5,12 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context, ILogger logger) : CommandItemViewModel(new(contextItem), context, logger), IContextItemViewModel
{
private readonly KeyChord nullKeyChord = new(0, 0, 0);

View File

@@ -3,17 +3,19 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext
{
private readonly ILogger logger;
public ExtensionObject<ICommandItem> Model => _commandItemModel;
private readonly ExtensionObject<ICommandItem> _commandItemModel = new(null);
@@ -86,11 +88,12 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
_errorIcon.InitializeProperties();
}
public CommandItemViewModel(ExtensionObject<ICommandItem> item, WeakReference<IPageContext> errorContext)
: base(errorContext)
public CommandItemViewModel(ExtensionObject<ICommandItem> item, WeakReference<IPageContext> errorContext, ILogger logger)
: base(errorContext, logger)
{
_commandItemModel = item;
Command = new(null, errorContext);
this.logger = logger;
Command = new(null, errorContext, Logger);
}
public void FastInitializeProperties()
@@ -106,7 +109,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
return;
}
Command = new(model.Command, PageContext);
Command = new(model.Command, PageContext, Logger);
Command.FastInitializeProperties();
_itemTitle = model.Title;
@@ -184,7 +187,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
MoreCommands = more
.Select<IContextItem, IContextItemViewModel>(item =>
{
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel();
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext, Logger) : new SeparatorViewModel();
})
.ToList();
}
@@ -201,7 +204,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
if (!string.IsNullOrEmpty(model.Command?.Name))
{
_defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(model.Command!), PageContext)
_defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(model.Command!), PageContext, Logger)
{
_itemTitle = Name,
Subtitle = Subtitle,
@@ -231,8 +234,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
catch (Exception ex)
{
CoreLogger.LogError("error fast initializing CommandItemViewModel", ex);
Command = new(null, PageContext);
Log_ErrorFastInitializingCommandItemViewModel(ex);
Command = new(null, PageContext, Logger);
_itemTitle = "Error";
Subtitle = "Item failed to load";
MoreCommands = [];
@@ -253,7 +256,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
catch (Exception ex)
{
Initialized |= InitializedState.Error;
CoreLogger.LogError("error slow initializing CommandItemViewModel", ex);
Log_ErrorSlowInitializingCommandItemViewModel(ex);
}
return false;
@@ -268,8 +271,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
catch (Exception ex)
{
CoreLogger.LogError("error initializing CommandItemViewModel", ex);
Command = new(null, PageContext);
Log_ErrorInitializingCommandItemViewModel(ex);
Command = new(null, PageContext, Logger);
_itemTitle = "Error";
Subtitle = "Item failed to load";
MoreCommands = [];
@@ -304,7 +307,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
{
case nameof(Command):
Command.PropertyChanged -= Command_PropertyChanged;
Command = new(model.Command, PageContext);
Command = new(model.Command, PageContext, Logger);
Command.InitializeProperties();
Command.PropertyChanged += Command_PropertyChanged;
@@ -351,7 +354,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
var newContextMenu = more
.Select<IContextItem, IContextItemViewModel>(item =>
{
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel();
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext, Logger) : new SeparatorViewModel();
})
.ToList();
lock (MoreCommands)
@@ -464,6 +467,15 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
base.SafeCleanup();
Initialized |= InitializedState.CleanedUp;
}
[LoggerMessage(level: LogLevel.Error, message: "error fast initializing CommandItemViewModel")]
partial void Log_ErrorFastInitializingCommandItemViewModel(Exception ex);
[LoggerMessage(level: LogLevel.Error, message: "error slow initializing CommandItemViewModel")]
partial void Log_ErrorSlowInitializingCommandItemViewModel(Exception ex);
[LoggerMessage(level: LogLevel.Error, message: "error initializing CommandItemViewModel")]
partial void Log_ErrorInitializingCommandItemViewModel(Exception ex);
}
[Flags]

View File

@@ -4,6 +4,7 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -38,8 +39,8 @@ public partial class CommandViewModel : ExtensionObjectViewModel
public IReadOnlyDictionary<string, ExtensionObject<object>>? Properties => _properties?.AsReadOnly();
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext)
: base(pageContext)
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext, ILogger logger)
: base(pageContext, logger)
{
Model = new(command);
Icon = new(null);

View File

@@ -4,11 +4,12 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReference<IPageContext> context) :
ExtensionObjectViewModel(context)
public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReference<IPageContext> context, ILogger logger) :
ExtensionObjectViewModel(context, logger)
{
public ExtensionObject<IConfirmationArgs> Model { get; } = new(_args);
@@ -20,7 +21,7 @@ public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReferen
public bool IsPrimaryCommandCritical { get; private set; }
public CommandViewModel PrimaryCommand { get; private set; } = new(null, context);
public CommandViewModel PrimaryCommand { get; private set; } = new(null, context, logger);
public override void InitializeProperties()
{
@@ -33,7 +34,7 @@ public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReferen
Title = model.Title;
Description = model.Description;
IsPrimaryCommandCritical = model.IsPrimaryCommandCritical;
PrimaryCommand = new(model.PrimaryCommand, PageContext);
PrimaryCommand = new(model.PrimaryCommand, PageContext, Logger);
PrimaryCommand.InitializeProperties();
UpdateProperty(nameof(Title));

View File

@@ -11,6 +11,7 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -47,8 +48,8 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
// Remember - "observable" properties from the model (via PropChanged)
// cannot be marked [ObservableProperty]
public ContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
public ContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host, ILogger logger)
: base(model, scheduler, host, logger)
{
_model = new(model);
}
@@ -115,7 +116,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext);
return new CommandContextItemViewModel(contextItem, PageContext, Logger);
}
else
{
@@ -135,7 +136,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
var extensionDetails = model.Details;
if (extensionDetails is not null)
{
Details = new(extensionDetails, PageContext);
Details = new(extensionDetails, PageContext, Logger);
Details.InitializeProperties();
}
@@ -174,7 +175,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
return new CommandContextItemViewModel(contextItem, PageContext, Logger) as IContextItemViewModel;
}
else
{
@@ -216,7 +217,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
break;
case nameof(Details):
var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
Details = extensionDetails is not null ? new(extensionDetails, PageContext, Logger) : null;
UpdateDetails();
break;
}

View File

@@ -2,10 +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 Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class ContentViewModel(WeakReference<IPageContext> context) :
ExtensionObjectViewModel(context)
public abstract partial class ContentViewModel(WeakReference<IPageContext> context, ILogger logger) :
ExtensionObjectViewModel(context, logger)
{
public bool OnlyControlOnPage { get; internal set; }
}

View File

@@ -5,7 +5,6 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -139,10 +138,6 @@ public partial class ContextMenuViewModel : ObservableObject,
{
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
var added = result.TryAdd(key, cmd);
if (!added)
{
CoreLogger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
}
}
}

View File

@@ -4,12 +4,14 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsCommandsViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
WeakReference<IPageContext> context,
ILogger logger) : DetailsElementViewModel(_detailsElement, context, logger)
{
public List<CommandViewModel> Commands { get; private set; } = [];
@@ -31,7 +33,7 @@ public partial class DetailsCommandsViewModel(
.Commands?
.Select(c =>
{
var vm = new CommandViewModel(c, PageContext);
var vm = new CommandViewModel(c, PageContext, Logger);
vm.InitializeProperties();
return vm;
})

View File

@@ -2,11 +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.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class DetailsDataViewModel(IPageContext context) : ExtensionObjectViewModel(context)
public abstract partial class DetailsDataViewModel(IPageContext context, ILogger logger) : ExtensionObjectViewModel(context, logger)
{
}

View File

@@ -4,10 +4,11 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class DetailsElementViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
public abstract partial class DetailsElementViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context, ILogger logger) : ExtensionObjectViewModel(context, logger)
{
private readonly ExtensionObject<IDetailsElement> _model = new(_detailsElement);

View File

@@ -6,12 +6,14 @@ using CommunityToolkit.Mvvm.Input;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsLinkViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
WeakReference<IPageContext> context,
ILogger logger) : DetailsElementViewModel(_detailsElement, context, logger)
{
private static readonly string[] _initProperties = [
nameof(Text),

View File

@@ -4,12 +4,14 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsSeparatorViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
WeakReference<IPageContext> context,
ILogger logger) : DetailsElementViewModel(_detailsElement, context, logger)
{
private readonly ExtensionObject<IDetailsSeparator> _dataModel =
new(_detailsElement.Data as IDetailsSeparator);

View File

@@ -4,12 +4,14 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsTagsViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
WeakReference<IPageContext> context,
ILogger logger) : DetailsElementViewModel(_detailsElement, context, logger)
{
public List<TagViewModel> Tags { get; private set; } = [];
@@ -31,7 +33,7 @@ public partial class DetailsTagsViewModel(
.Tags?
.Select(t =>
{
var vm = new TagViewModel(t, PageContext);
var vm = new TagViewModel(t, PageContext, Logger);
vm.InitializeProperties();
return vm;
})

View File

@@ -4,10 +4,11 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsViewModel(IDetails _details, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
public partial class DetailsViewModel(IDetails _details, WeakReference<IPageContext> context, ILogger logger) : ExtensionObjectViewModel(context, logger)
{
private readonly ExtensionObject<IDetails> _detailsModel = new(_details);
@@ -47,10 +48,10 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
{
DetailsElementViewModel? vm = element.Data switch
{
IDetailsSeparator => new DetailsSeparatorViewModel(element, this.PageContext),
IDetailsLink => new DetailsLinkViewModel(element, this.PageContext),
IDetailsCommands => new DetailsCommandsViewModel(element, this.PageContext),
IDetailsTags => new DetailsTagsViewModel(element, this.PageContext),
IDetailsSeparator => new DetailsSeparatorViewModel(element, this.PageContext, Logger),
IDetailsLink => new DetailsLinkViewModel(element, this.PageContext, Logger),
IDetailsCommands => new DetailsCommandsViewModel(element, this.PageContext, Logger),
IDetailsTags => new DetailsTagsViewModel(element, this.PageContext, Logger),
_ => null,
};
if (vm is not null)

View File

@@ -3,23 +3,29 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.CmdPal.Core.Common;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class ExtensionObjectViewModel : ObservableObject
{
private readonly ILogger _logger;
public ILogger Logger => _logger;
public WeakReference<IPageContext> PageContext { get; set; }
internal ExtensionObjectViewModel(IPageContext? context)
internal ExtensionObjectViewModel(IPageContext? context, ILogger logger)
{
var realContext = context ?? (this is IPageContext c ? c : throw new ArgumentException("You need to pass in an IErrorContext"));
_logger = logger;
PageContext = new(realContext);
}
internal ExtensionObjectViewModel(WeakReference<IPageContext> context)
internal ExtensionObjectViewModel(WeakReference<IPageContext> context, ILogger logger)
{
PageContext = context;
_logger = logger;
}
public async virtual Task InitializePropertiesAsync()
@@ -114,7 +120,10 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
}
catch (Exception ex)
{
CoreLogger.LogDebug(ex.ToString());
Log_CleanupException(ex);
}
}
[LoggerMessage(Level = LogLevel.Debug)]
partial void Log_CleanupException(Exception exception);
}

View File

@@ -4,6 +4,7 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -23,8 +24,8 @@ public partial class FilterItemViewModel : ExtensionObjectViewModel, IFilterItem
public bool IsInErrorState => Initialized.HasFlag(InitializedState.Error);
public FilterItemViewModel(IFilter filter, WeakReference<IPageContext> context)
: base(context)
public FilterItemViewModel(IFilter filter, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
{
_model = new(filter);
}

View File

@@ -2,10 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -21,8 +20,8 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
public bool ShouldShowFilters => Filters.Length > 0;
public FiltersViewModel(ExtensionObject<IFilters> filters, WeakReference<IPageContext> context)
: base(context)
public FiltersViewModel(ExtensionObject<IFilters> filters, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
{
_filtersModel = filters;
}
@@ -71,7 +70,7 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
{
if (filter is IFilter filterItem)
{
var filterItemViewModel = new FilterItemViewModel(filterItem, PageContext);
var filterItemViewModel = new FilterItemViewModel(filterItem, PageContext, Logger);
filterItemViewModel.InitializeProperties();
if (firstFilterItem is null)

View File

@@ -7,6 +7,7 @@ using Microsoft.CmdPal.Core.ViewModels.Commands;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -59,8 +60,8 @@ public partial class ListItemViewModel : CommandItemViewModel
}
}
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
: base(new(model), context)
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context, ILogger logger)
: base(new(model), context, logger)
{
Model = new ExtensionObject<IListItem>(model);
}
@@ -102,7 +103,7 @@ public partial class ListItemViewModel : CommandItemViewModel
var extensionDetails = model.Details;
if (extensionDetails is not null)
{
Details = new(extensionDetails, PageContext);
Details = new(extensionDetails, PageContext, Logger);
Details.InitializeProperties();
UpdateProperty(nameof(Details));
UpdateProperty(nameof(HasDetails));
@@ -139,7 +140,7 @@ public partial class ListItemViewModel : CommandItemViewModel
break;
case nameof(model.Details):
var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
Details = extensionDetails is not null ? new(extensionDetails, PageContext, Logger) : null;
Details?.InitializeProperties();
UpdateProperty(nameof(Details));
UpdateProperty(nameof(HasDetails));
@@ -189,7 +190,7 @@ public partial class ListItemViewModel : CommandItemViewModel
// Create the view model for the show details command
var showDetailsCommand = new ShowDetailsCommand(Details);
var showDetailsContextItem = new CommandContextItem(showDetailsCommand);
var showDetailsContextItemViewModel = new CommandContextItemViewModel(showDetailsContextItem, PageContext);
var showDetailsContextItemViewModel = new CommandContextItemViewModel(showDetailsContextItem, PageContext, Logger);
showDetailsContextItemViewModel.SlowInitializeProperties();
MoreCommands.Add(showDetailsContextItemViewModel);
}
@@ -223,7 +224,7 @@ public partial class ListItemViewModel : CommandItemViewModel
// Create the view model for the show details command
var showDetailsCommand = new ShowDetailsCommand(Details);
var showDetailsContextItem = new CommandContextItem(showDetailsCommand);
var showDetailsContextItemViewModel = new CommandContextItemViewModel(showDetailsContextItem, PageContext);
var showDetailsContextItemViewModel = new CommandContextItemViewModel(showDetailsContextItem, PageContext, Logger);
showDetailsContextItemViewModel.SlowInitializeProperties();
MoreCommands.Add(showDetailsContextItemViewModel);
@@ -236,7 +237,7 @@ public partial class ListItemViewModel : CommandItemViewModel
{
var newTags = newTagsFromModel?.Select(t =>
{
var vm = new TagViewModel(t, PageContext);
var vm = new TagViewModel(t, PageContext, Logger);
vm.InitializeProperties();
return vm;
})

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.Common.Helpers;
@@ -11,13 +10,13 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ListViewModel : PageViewModel, IDisposable
{
// private readonly HashSet<ListItemViewModel> _itemCache = [];
private readonly TaskFactory filterTaskFactory = new(new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler);
// TODO: Do we want a base "ItemsPageViewModel" for anything that's going to have items?
@@ -89,11 +88,11 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
}
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host, ILogger logger)
: base(model, scheduler, host, logger)
{
_model = new(model);
EmptyContent = new(new(null), PageContext);
EmptyContent = new(new(null), PageContext, Logger);
}
private void FiltersPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -233,7 +232,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
return;
}
ListItemViewModel viewModel = new(item, new(this));
ListItemViewModel viewModel = new(item, new(this), Logger);
// If an item fails to load, silently ignore it.
if (viewModel.SafeFastInit())
@@ -598,11 +597,11 @@ public partial class ListViewModel : PageViewModel, IDisposable
UpdateProperty(nameof(SearchText));
UpdateProperty(nameof(InitialSearchText));
EmptyContent = new(new(model.EmptyContent), PageContext);
EmptyContent = new(new(model.EmptyContent), PageContext, Logger);
EmptyContent.SlowInitializeProperties();
Filters?.PropertyChanged -= FiltersPropertyChanged;
Filters = new(new(model.Filters), PageContext);
Filters = new(new(model.Filters), PageContext, Logger);
Filters?.PropertyChanged += FiltersPropertyChanged;
Filters?.InitializeProperties();
@@ -694,12 +693,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
SearchText = model.SearchText;
break;
case nameof(EmptyContent):
EmptyContent = new(new(model.EmptyContent), PageContext);
EmptyContent = new(new(model.EmptyContent), PageContext, Logger);
EmptyContent.SlowInitializeProperties();
break;
case nameof(Filters):
Filters?.PropertyChanged -= FiltersPropertyChanged;
Filters = new(new(model.Filters), PageContext);
Filters = new(new(model.Filters), PageContext, Logger);
Filters?.PropertyChanged += FiltersPropertyChanged;
Filters?.InitializeProperties();
break;
@@ -764,7 +763,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
base.UnsafeCleanup();
EmptyContent?.SafeCleanup();
EmptyContent = new(new(null), PageContext); // necessary?
EmptyContent = new(new(null), PageContext, Logger); // necessary?
_cancellationTokenSource?.Cancel();
filterCancellationTokenSource?.Cancel();

View File

@@ -3,13 +3,14 @@
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class LoadingPageViewModel : PageViewModel
{
public LoadingPageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
public LoadingPageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost host, ILogger logger)
: base(model, scheduler, host, logger)
{
ModelIsLoading = true;
IsInitialized = false;

View File

@@ -4,6 +4,7 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -13,8 +14,8 @@ public partial class LogMessageViewModel : ExtensionObjectViewModel
public string Message { get; private set; } = string.Empty;
public LogMessageViewModel(ILogMessage message, IPageContext context)
: base(context)
public LogMessageViewModel(ILogMessage message, IPageContext context, ILogger logger)
: base(context, logger)
{
_model = new(message);
}

View File

@@ -3,9 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
@@ -48,10 +46,6 @@ public interface IContextMenuContext : INotifyPropertyChanged
{
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
var added = result.TryAdd(key, cmd);
if (!added)
{
CoreLogger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
}
}
}

View File

@@ -2,7 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
internal sealed partial class NullPageViewModel(TaskScheduler scheduler, AppExtensionHost extensionHost)
: PageViewModel(null, scheduler, extensionHost);
internal sealed partial class NullPageViewModel(TaskScheduler scheduler, AppExtensionHost extensionHost, ILogger logger)
: PageViewModel(null, scheduler, extensionHost, logger);

View File

@@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.Input;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -76,8 +77,8 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public IconInfoViewModel Icon { get; protected set; }
public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost)
: base((IPageContext?)null)
public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost, ILogger logger)
: base((IPageContext?)null, logger)
{
_pageModel = new(model);
Scheduler = scheduler;
@@ -274,6 +275,7 @@ public interface IPageViewModelFactoryService
/// <param name="page">The page for which to create the view model.</param>
/// <param name="nested">Indicates whether the page is not the top-level page.</param>
/// <param name="host">The command palette host that will host the page (for status messages)</param>
/// <param name="logger">The logger to use for logging.</param>
/// <returns>A new instance of the page view model.</returns>
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host);
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host, ILogger logger);
}

View File

@@ -4,6 +4,7 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -15,8 +16,8 @@ public partial class ProgressViewModel : ExtensionObjectViewModel
public uint ProgressPercent { get; private set; }
public ProgressViewModel(IProgressState progress, WeakReference<IPageContext> context)
: base(context)
public ProgressViewModel(IProgressState progress, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
{
Model = new(progress);
}

View File

@@ -6,10 +6,10 @@ using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -18,10 +18,11 @@ public partial class ShellViewModel : ObservableObject,
IRecipient<HandleCommandResultMessage>
{
private readonly IRootPageService _rootPageService;
private readonly IAppHostService _appHostService;
private readonly AppExtensionHost _appHost;
private readonly TaskScheduler _scheduler;
private readonly IPageViewModelFactoryService _pageViewModelFactory;
private readonly Lock _invokeLock = new();
private readonly ILogger _logger;
private Task? _handleInvokeTask;
// Cancellation token source for page loading/navigation operations
@@ -60,7 +61,7 @@ public partial class ShellViewModel : ObservableObject,
}
catch (Exception ex)
{
CoreLogger.LogError(ex.ToString());
Log_Exception(ex);
}
}
}
@@ -87,15 +88,17 @@ public partial class ShellViewModel : ObservableObject,
TaskScheduler scheduler,
IRootPageService rootPageService,
IPageViewModelFactoryService pageViewModelFactory,
IAppHostService appHostService)
AppExtensionHost appHost,
ILogger logger)
{
_pageViewModelFactory = pageViewModelFactory;
_scheduler = scheduler;
_rootPageService = rootPageService;
_appHostService = appHostService;
_appHost = appHost;
_logger = logger;
NullPage = new NullPageViewModel(_scheduler, appHostService.GetDefaultHost());
_currentPage = new LoadingPageViewModel(null, _scheduler, appHostService.GetDefaultHost());
NullPage = new NullPageViewModel(_scheduler, appHost, _logger);
_currentPage = new LoadingPageViewModel(null, _scheduler, appHost, _logger);
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
@@ -162,7 +165,7 @@ public partial class ShellViewModel : ObservableObject,
{
if (viewModel.InitializeCommand.ExecutionTask.Exception is AggregateException ex)
{
CoreLogger.LogError(ex.ToString());
Log_Exception(ex);
}
}
else
@@ -180,7 +183,7 @@ public partial class ShellViewModel : ObservableObject,
}
catch (Exception ex)
{
CoreLogger.LogError(ex.ToString());
Log_Exception(ex);
}
}
@@ -210,7 +213,7 @@ public partial class ShellViewModel : ObservableObject,
}
catch (Exception ex)
{
CoreLogger.LogError(ex.ToString());
Log_Exception(ex);
}
}
@@ -240,7 +243,7 @@ public partial class ShellViewModel : ObservableObject,
}
catch (Exception ex)
{
CoreLogger.LogError(ex.ToString());
Log_Exception(ex);
}
finally
{
@@ -256,7 +259,7 @@ public partial class ShellViewModel : ObservableObject,
return;
}
var host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
var host = _appHost.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
_rootPageService.OnPerformCommand(message.Context, !CurrentPage.IsNested, host);
@@ -264,16 +267,16 @@ public partial class ShellViewModel : ObservableObject,
{
if (command is IPage page)
{
CoreLogger.LogDebug($"Navigating to page");
Log_NavigateToPage();
var isMainPage = command == _rootPage;
_isNested = !isMainPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host, _logger);
if (pageViewModel is null)
{
CoreLogger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
Log_FailedToCreateViewModel(page.GetType().Name);
throw new NotSupportedException();
}
@@ -303,7 +306,7 @@ public partial class ShellViewModel : ObservableObject,
}
else if (command is IInvokableCommand invokable)
{
CoreLogger.LogDebug($"Invoking command");
Log_InvokingCommand();
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
StartInvoke(message, invokable, host);
@@ -369,7 +372,7 @@ public partial class ShellViewModel : ObservableObject,
}
var kind = result.Kind;
CoreLogger.LogDebug($"handling {kind.ToString()}");
Log_HandlingCommandResult(kind.ToString());
WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
switch (kind)
@@ -460,4 +463,19 @@ public partial class ShellViewModel : ObservableObject,
{
_navigationCts?.Cancel();
}
[LoggerMessage(Level = LogLevel.Error)]
partial void Log_Exception(Exception exception);
[LoggerMessage(Level = LogLevel.Debug, Message = "Navigating to page")]
partial void Log_NavigateToPage();
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to create ViewModel for page {PageTypeName}")]
partial void Log_FailedToCreateViewModel(string pageTypeName);
[LoggerMessage(Level = LogLevel.Debug, Message = "Invoking command")]
partial void Log_InvokingCommand();
[LoggerMessage(Level = LogLevel.Debug, Message = "Handling {CommandResultKind}")]
partial void Log_HandlingCommandResult(string commandResultKind);
}

View File

@@ -4,6 +4,7 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -19,8 +20,8 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
public bool HasProgress => Progress is not null;
public StatusMessageViewModel(IStatusMessage message, WeakReference<IPageContext> context)
: base(context)
public StatusMessageViewModel(IStatusMessage message, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
{
Model = new(message);
}
@@ -38,7 +39,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
var modelProgress = model.Progress;
if (modelProgress is not null)
{
Progress = new(modelProgress, this.PageContext);
Progress = new(modelProgress, this.PageContext, this.Logger);
Progress.InitializeProperties();
UpdateProperty(nameof(HasProgress));
}
@@ -78,7 +79,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
var modelProgress = model.Progress;
if (modelProgress is not null)
{
Progress = new(modelProgress, this.PageContext);
Progress = new(modelProgress, this.PageContext, this.Logger);
Progress.InitializeProperties();
}
else

View File

@@ -4,10 +4,11 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class TagViewModel(ITag _tag, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
public partial class TagViewModel(ITag _tag, WeakReference<IPageContext> context, ILogger logger) : ExtensionObjectViewModel(context, logger)
{
private readonly ExtensionObject<ITag> _tagModel = new(_tag);

View File

@@ -4,23 +4,27 @@
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandPaletteContentPageViewModel : ContentPageViewModel
{
public CommandPaletteContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
private readonly ILogger logger;
public CommandPaletteContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host, ILogger logger)
: base(model, scheduler, host, logger)
{
this.logger = logger;
}
public override ContentViewModel? ViewModelFromContent(IContent content, WeakReference<IPageContext> context)
{
ContentViewModel? viewModel = content switch
{
IFormContent form => new ContentFormViewModel(form, context),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context),
ITreeContent tree => new ContentTreeViewModel(tree, context),
IFormContent form => new ContentFormViewModel(form, context, logger),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context, logger),
ITreeContent tree => new ContentTreeViewModel(tree, context, logger),
_ => null,
};
return viewModel;

View File

@@ -5,6 +5,7 @@
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -12,22 +13,24 @@ public sealed partial class CommandPaletteHost : AppExtensionHost, IExtensionHos
{
// 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 static CommandPaletteHost Instance { get; } = new();
public IExtensionWrapper? Extension { get; }
private readonly ICommandProvider? _builtInProvider;
private CommandPaletteHost()
private 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;
}
@@ -36,4 +39,15 @@ public sealed partial class CommandPaletteHost : AppExtensionHost, IExtensionHos
{
return Extension?.ExtensionDisplayName;
}
public override AppExtensionHost GetHostForCommand(object? context, AppExtensionHost? currentHost)
{
AppExtensionHost? topLevelHost = null;
if (context is TopLevelViewModel topLevelViewModel)
{
topLevelHost = topLevelViewModel.ExtensionHost;
}
return topLevelHost ?? currentHost ?? this;
}
}

View File

@@ -4,6 +4,7 @@
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -17,12 +18,12 @@ public class CommandPalettePageViewModelFactory
_scheduler = scheduler;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host)
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host, ILogger logger)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host) { IsNested = nested },
IContentPage contentPage => new CommandPaletteContentPageViewModel(contentPage, _scheduler, host),
IListPage listPage => new ListViewModel(listPage, _scheduler, host, logger) { IsNested = nested },
IContentPage contentPage => new CommandPaletteContentPageViewModel(contentPage, _scheduler, host, logger),
_ => null,
};
}

View File

@@ -2,19 +2,21 @@
// 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.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
{
private readonly ILogger logger;
private readonly HotkeyManager _hotkeyManager;
private readonly AliasManager _aliasManager;
public bool IsExtension => Extension is not null;
private readonly bool isValid;
@@ -51,15 +53,18 @@ public sealed class CommandProviderWrapper
}
}
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread)
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread, ILogger logger, AliasManager aliasManager, HotkeyManager hotkeyManager)
{
// This ctor is only used for in-proc builtin commands. So the Unsafe!
// calls are pretty dang safe actually.
_commandProvider = new(provider);
_taskScheduler = mainThread;
this.logger = logger;
_aliasManager = aliasManager;
_hotkeyManager = hotkeyManager;
// Hook the extension back into us
ExtensionHost = new CommandPaletteHost(provider);
ExtensionHost = new CommandPaletteHost(provider, logger);
_commandProvider.Unsafe!.InitializeWithHost(ExtensionHost);
_commandProvider.Unsafe!.ItemsChanged += CommandProvider_ItemsChanged;
@@ -72,16 +77,20 @@ public sealed class CommandProviderWrapper
// Note: explicitly not InitializeProperties()ing the settings here. If
// we do that, then we'd regress GH #38321
Settings = new(provider.Settings, this, _taskScheduler);
Settings = new(provider.Settings, this, _taskScheduler, logger);
Logger.LogDebug($"Initialized command provider {ProviderId}");
Log_CommandProviderInitialized(ProviderId);
}
public CommandProviderWrapper(IExtensionWrapper extension, TaskScheduler mainThread)
public CommandProviderWrapper(IExtensionWrapper extension, TaskScheduler mainThread, ILogger logger, AliasManager aliasManager, HotkeyManager hotkeyManager)
{
_taskScheduler = mainThread;
this.logger = logger;
_aliasManager = aliasManager;
_hotkeyManager = hotkeyManager;
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");
@@ -106,13 +115,11 @@ public sealed class CommandProviderWrapper
isValid = true;
Logger.LogDebug($"Initialized extension command provider {Extension.PackageFamilyName}:{Extension.ExtensionUniqueId}");
Log_ExtensionInitialized(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_FailedToInitializeCommandProviderForExtension(Extension!.PackageFamilyName, e);
}
isValid = true;
@@ -123,7 +130,7 @@ public sealed class CommandProviderWrapper
return settings.GetProviderSettings(this);
}
public async Task LoadTopLevelCommands(IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
public async Task LoadTopLevelCommands(SettingsModel settings, WeakReference<IPageContext> pageContext)
{
if (!isValid)
{
@@ -131,8 +138,6 @@ public sealed class CommandProviderWrapper
return;
}
var settings = serviceProvider.GetService<SettingsModel>()!;
IsActive = GetProviderSettings(settings).IsEnabled;
if (!IsActive)
{
@@ -165,30 +170,27 @@ public sealed class CommandProviderWrapper
// Note: explicitly not InitializeProperties()ing the settings here. If
// we do that, then we'd regress GH #38321
Settings = new(model.Settings, this, _taskScheduler);
Settings = new(model.Settings, this, _taskScheduler, logger);
// We do need to explicitly initialize commands though
InitializeCommands(commands, fallbacks, serviceProvider, pageContext);
InitializeCommands(commands, fallbacks, settings, pageContext);
Logger.LogDebug($"Loaded commands from {DisplayName} ({ProviderId})");
Log_LoadedCommandsFromExtension(DisplayName, ProviderId);
}
catch (Exception e)
{
Logger.LogError("Failed to load commands from extension");
Logger.LogError($"Extension was {Extension!.PackageFamilyName}");
Logger.LogError(e.ToString());
Log_FailedToLoadCommandsFromProvider(Extension!.PackageFamilyName, e);
}
}
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, SettingsModel settings, WeakReference<IPageContext> pageContext)
{
var settings = serviceProvider.GetService<SettingsModel>()!;
var providerSettings = GetProviderSettings(settings);
Func<ICommandItem?, bool, TopLevelViewModel> makeAndAdd = (ICommandItem? i, bool fallback) =>
{
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, providerSettings, serviceProvider);
CommandItemViewModel commandItemViewModel = new(new(i), pageContext, logger);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, _hotkeyManager, _aliasManager, providerSettings);
topLevelViewModel.InitializeProperties();
return topLevelViewModel;
@@ -211,12 +213,12 @@ public sealed class CommandProviderWrapper
private void UnsafePreCacheApiAdditions(ICommandProvider2 provider)
{
var apiExtensions = provider.GetApiExtensionStubs();
Logger.LogDebug($"Provider supports {apiExtensions.Length} extensions");
Log_ProviderCount(apiExtensions.Length);
foreach (var a in apiExtensions)
{
if (a is IExtendedAttributesProvider command2)
{
Logger.LogDebug($"{ProviderId}: Found an IExtendedAttributesProvider");
Log_IExtendedAttributesProviderFound(ProviderId);
}
}
}
@@ -234,4 +236,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 CommandProvider '{ProviderId}'")]
partial void Log_CommandProviderInitialized(string providerId);
[LoggerMessage(Level = LogLevel.Debug, Message = "{ProviderId}: Found an IExtendedAttributesProvider")]
partial void Log_IExtendedAttributesProviderFound(string providerId);
[LoggerMessage(Level = LogLevel.Debug, Message = "Provider exposed {Count} API extensions")]
partial void Log_ProviderCount(int count);
[LoggerMessage(Level = LogLevel.Debug, Message = "Initialized CommandProvider from extension '{PackageFamilyName}' ({ExtensionId})")]
partial void Log_ExtensionInitialized(string packageFamilyName, string extensionId);
[LoggerMessage(Level = LogLevel.Debug, Message = "Loaded commands from {DisplayName} ({ProviderId})")]
partial void Log_LoadedCommandsFromExtension(string displayName, string providerId);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to load commands from extension '{PackageFamilyName}'")]
partial void Log_FailedToLoadCommandsFromProvider(string packageFamilyName, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to initialize CommandProvider for extension '{PackageFamilyName}'")]
partial void Log_FailedToInitializeCommandProviderForExtension(string packageFamilyName, Exception exception);
}

View File

@@ -2,16 +2,17 @@
// 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.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread, ILogger logger)
{
private readonly ExtensionObject<ICommandSettings> _model = new(_unsafeSettings);
private readonly ILogger _logger = logger;
public ContentPageViewModel? SettingsPage { get; private set; }
@@ -31,7 +32,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, _logger);
SettingsPage.InitializeProperties();
}
}
@@ -44,7 +45,7 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
}
catch (Exception ex)
{
CoreLogger.LogError($"Failed to load settings page", ex: ex);
Log_FailedToLoadSettingsPage(ex);
}
Initialized = true;
@@ -58,4 +59,7 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
TaskCreationOptions.None,
mainThread);
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to load settings page")]
partial void Log_FailedToLoadSettingsPage(Exception ex);
}

View File

@@ -16,7 +16,6 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.CmdPal.UI.ViewModels.MainPage;
@@ -39,8 +38,10 @@ public partial class MainListPage : DynamicListPage,
"com.microsoft.cmdpal.builtin.remotedesktop",
];
private readonly IServiceProvider _serviceProvider;
private readonly TopLevelCommandManager _tlcManager;
private readonly SettingsModel _settingsModel;
private readonly AliasManager _aliasManager;
private readonly AppStateModel _appStateModel;
private List<Scored<IListItem>>? _filteredItems;
private List<Scored<IListItem>>? _filteredApps;
private List<Scored<IListItem>>? _fallbackItems;
@@ -54,14 +55,16 @@ public partial class MainListPage : DynamicListPage,
private CancellationTokenSource? _cancellationTokenSource;
public MainListPage(IServiceProvider serviceProvider)
public MainListPage(TopLevelCommandManager topLevelCommandManager, SettingsModel settingsModel, AliasManager aliasManager, AppStateModel appStateModel)
{
Title = Resources.builtin_home_name;
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
_serviceProvider = serviceProvider;
_tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
_aliasManager = aliasManager;
_appStateModel = appStateModel;
_tlcManager = topLevelCommandManager;
_tlcManager.PropertyChanged += TlcManager_PropertyChanged;
_tlcManager.TopLevelCommands.CollectionChanged += Commands_CollectionChanged;
@@ -79,9 +82,9 @@ public partial class MainListPage : DynamicListPage,
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
WeakReferenceMessenger.Default.Register<UpdateFallbackItemsMessage>(this);
var settings = _serviceProvider.GetService<SettingsModel>()!;
settings.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(settings);
_settingsModel = settingsModel;
_settingsModel.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(_settingsModel);
_includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);
IsLoading = true;
@@ -220,14 +223,12 @@ public partial class MainListPage : DynamicListPage,
// Handle changes to the filter text here
if (!string.IsNullOrEmpty(SearchText))
{
var aliases = _serviceProvider.GetService<AliasManager>()!;
if (token.IsCancellationRequested)
{
return;
}
if (aliases.CheckAlias(newSearch))
if (_aliasManager.CheckAlias(newSearch))
{
if (_filteredItemsIncludesApps != _includeApps)
{
@@ -389,7 +390,7 @@ public partial class MainListPage : DynamicListPage,
}
}
var history = _serviceProvider.GetService<AppStateModel>()!.RecentCommands!;
var history = _appStateModel.RecentCommands!;
Func<string, IListItem, int> scoreItem = (a, b) => { return ScoreTopLevelItem(a, b, history); };
// Produce a list of everything that matches the current filter.
@@ -484,9 +485,8 @@ public partial class MainListPage : DynamicListPage,
private bool ActuallyLoading()
{
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
var allApps = AllAppsCommandProvider.Page;
return allApps.IsLoading || tlcManager.IsLoading;
return allApps.IsLoading || _tlcManager.IsLoading;
}
// Almost verbatim ListHelpers.ScoreListItem, but also accounting for the
@@ -581,10 +581,9 @@ public partial class MainListPage : DynamicListPage,
public void UpdateHistory(IListItem topLevelOrAppItem)
{
var id = IdForTopLevelOrAppItem(topLevelOrAppItem);
var state = _serviceProvider.GetService<AppStateModel>()!;
var history = state.RecentCommands;
var history = _appStateModel.RecentCommands;
history.AddHistoryItem(id);
AppStateModel.SaveState(state);
AppStateModel.SaveState(_appStateModel);
}
private static string IdForTopLevelOrAppItem(IListItem topLevelOrAppItem)
@@ -616,10 +615,9 @@ public partial class MainListPage : DynamicListPage,
_tlcManager.PropertyChanged -= TlcManager_PropertyChanged;
_tlcManager.TopLevelCommands.CollectionChanged -= Commands_CollectionChanged;
var settings = _serviceProvider.GetService<SettingsModel>();
if (settings is not null)
if (_settingsModel is not null)
{
settings.SettingsChanged -= SettingsChangedHandler;
_settingsModel.SettingsChanged -= SettingsChangedHandler;
}
WeakReferenceMessenger.Default.UnregisterAll(this);

View File

@@ -7,19 +7,26 @@ 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.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
using Windows.Data.Json;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPageContext> context) :
ContentViewModel(context)
public partial class ContentFormViewModel : ContentViewModel
{
private readonly ExtensionObject<IFormContent> _formModel = new(_form);
private readonly ExtensionObject<IFormContent> _formModel;
private readonly ILogger _logger;
public ContentFormViewModel(IFormContent _form, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
{
_formModel = new(_form);
_logger = logger;
}
// Remember - "observable" properties from the model (via PropChanged)
// cannot be marked [ObservableProperty]
@@ -38,7 +45,8 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
string templateJson,
string dataJson,
out AdaptiveCardParseResult? card,
out Exception? error)
out Exception? error,
ILogger logger)
{
card = null;
error = null;
@@ -52,7 +60,7 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
}
catch (Exception ex)
{
Logger.LogError("Error building card from template", ex);
Log_ErrorBuildindCard(logger, ex);
error = ex;
return false;
}
@@ -70,7 +78,7 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
StateJson = model.StateJson;
DataJson = model.DataJson;
if (TryBuildCard(TemplateJson, DataJson, out var builtCard, out var renderingError))
if (TryBuildCard(TemplateJson, DataJson, out var builtCard, out var renderingError, _logger))
{
Card = builtCard;
UpdateProperty(nameof(Card));
@@ -87,7 +95,7 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
}
""";
if (TryBuildCard(ErrorCardJson, errorPayload, out var errorCard, out var _))
if (TryBuildCard(ErrorCardJson, errorPayload, out var errorCard, out var _, _logger))
{
Card = errorCard;
UpdateProperty(nameof(Card));
@@ -173,4 +181,7 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
]
}
""";
[LoggerMessage(Level = LogLevel.Error, Message = "Error building adaptive card for form.")]
static partial void Log_ErrorBuildindCard(ILogger logger, Exception ex);
}

View File

@@ -5,11 +5,12 @@
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakReference<IPageContext> context) :
ContentViewModel(context)
public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakReference<IPageContext> context, ILogger logger) :
ContentViewModel(context, logger)
{
public ExtensionObject<IMarkdownContent> Model { get; } = new(_markdown);

View File

@@ -7,11 +7,12 @@ using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPageContext> context) :
ContentViewModel(context)
public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPageContext> context, ILogger logger) :
ContentViewModel(context, logger)
{
public ExtensionObject<ITreeContent> Model { get; } = new(_tree);
@@ -55,9 +56,9 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
{
ContentViewModel? viewModel = content switch
{
IFormContent form => new ContentFormViewModel(form, context),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context),
ITreeContent tree => new ContentTreeViewModel(tree, context),
IFormContent form => new ContentFormViewModel(form, context, Logger),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context, Logger),
ITreeContent tree => new ContentTreeViewModel(tree, context, Logger),
_ => null,
};
return viewModel;

View File

@@ -2,9 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppExtensions;
using Windows.Foundation;
@@ -22,6 +22,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static readonly Lock _lock = new();
private readonly SemaphoreSlim _getInstalledExtensionsLock = new(1, 1);
private readonly SemaphoreSlim _getInstalledWidgetsLock = new(1, 1);
private readonly ILogger _logger;
// private readonly ILocalSettingsService _localSettingsService;
private bool _disposedValue;
@@ -32,8 +33,9 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static readonly List<IExtensionWrapper> _installedExtensions = [];
private static readonly List<IExtensionWrapper> _enabledExtensions = [];
public ExtensionService()
public ExtensionService(ILogger logger)
{
_logger = logger;
_catalog.PackageInstalling += Catalog_PackageInstalling;
_catalog.PackageUninstalling += Catalog_PackageUninstalling;
_catalog.PackageUpdating += Catalog_PackageUpdating;
@@ -92,14 +94,14 @@ public partial class ExtensionService : IExtensionService, IDisposable
var extension = isCmdPalExtensionResult.Extension;
if (isExtension && extension is not null)
{
CommandPaletteHost.Instance.DebugLog($"Installed new extension app {extension.DisplayName}");
Log_ExtensionInstalled(extension.DisplayName);
Task.Run(async () =>
{
await _getInstalledExtensionsLock.WaitAsync();
try
{
var wrappers = await CreateWrappersForExtension(extension);
var wrappers = await CreateWrappersForExtension(extension, _logger);
UpdateExtensionsListsFromWrappers(wrappers);
@@ -120,7 +122,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
{
if (extension.PackageFullName == package.Id.FullName)
{
CommandPaletteHost.Instance.DebugLog($"Uninstalled extension app {extension.PackageDisplayName}");
Log_ExtensionUninstalled(extension.PackageDisplayName);
removedExtensions.Add(extension);
}
@@ -199,7 +201,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
var extensions = await GetInstalledAppExtensionsAsync();
foreach (var extension in extensions)
{
var wrappers = await CreateWrappersForExtension(extension);
var wrappers = await CreateWrappersForExtension(extension, _logger);
UpdateExtensionsListsFromWrappers(wrappers);
}
}
@@ -233,7 +235,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
}
}
private static async Task<List<ExtensionWrapper>> CreateWrappersForExtension(AppExtension extension)
private static async Task<List<ExtensionWrapper>> CreateWrappersForExtension(AppExtension extension, ILogger logger)
{
var (cmdPalProvider, classIds) = await GetCmdPalExtensionPropertiesAsync(extension);
@@ -245,14 +247,14 @@ public partial class ExtensionService : IExtensionService, IDisposable
List<ExtensionWrapper> wrappers = [];
foreach (var classId in classIds)
{
var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId);
var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId, logger);
wrappers.Add(extensionWrapper);
}
return wrappers;
}
private static ExtensionWrapper CreateExtensionWrapper(AppExtension extension, IPropertySet cmdPalProvider, string classId)
private static ExtensionWrapper CreateExtensionWrapper(AppExtension extension, IPropertySet cmdPalProvider, string classId, ILogger logger)
{
var extensionWrapper = new ExtensionWrapper(extension, classId);
@@ -269,7 +271,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
else
{
// log warning that extension declared unsupported extension interface
CommandPaletteHost.Instance.DebugLog($"Extension {extension.DisplayName} declared an unsupported interface: {supportedInterface.Key}");
Log_InvalidExtensionInterface(logger, extension.DisplayName, supportedInterface.Key);
}
}
}
@@ -288,7 +290,8 @@ public partial class ExtensionService : IExtensionService, IDisposable
var installedExtensions = await GetInstalledExtensionsAsync();
foreach (var installedExtension in installedExtensions)
{
Logger.LogDebug($"Signaling dispose to {installedExtension.ExtensionUniqueId}");
Log_SignalingDispose(installedExtension.ExtensionUniqueId);
try
{
if (installedExtension.IsRunning())
@@ -298,7 +301,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
}
catch (Exception ex)
{
Logger.LogError($"Failed to send dispose signal to extension {installedExtension.ExtensionUniqueId}", ex);
Log_ErrorSignalingDispose(installedExtension.ExtensionUniqueId, ex);
}
}
}
@@ -400,23 +403,20 @@ public partial class ExtensionService : IExtensionService, IDisposable
_enabledExtensions.Remove(extension.First());
}
/*
///// <inheritdoc cref="IExtensionService.DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper)"/>
//public async Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension)
//{
// // Only attempt to disable feature if its available.
// if (IsWindowsOptionalFeatureAvailableForExtension(extension.ExtensionClassId))
// {
// return false;
// }
// _log.Warning($"Disabling extension: '{extension.ExtensionDisplayName}' because its feature is absent or unknown");
// // Remove extension from list of enabled extensions to prevent Dev Home from re-querying for this extension
// // for the rest of its process lifetime.
// DisableExtension(extension.ExtensionUniqueId);
// // Update the local settings so the next time the user launches Dev Home the extension will be disabled.
// await _localSettingsService.SaveSettingAsync(extension.ExtensionUniqueId + "-ExtensionDisabled", true);
// return true;
//} */
[LoggerMessage(Level = LogLevel.Information, Message = "Installed new extension app {ExtensionName}")]
partial void Log_ExtensionInstalled(string extensionName);
[LoggerMessage(Level = LogLevel.Information, Message = "Uninstalled extension app {ExtensionName}")]
partial void Log_ExtensionUninstalled(string extensionName);
[LoggerMessage(Level = LogLevel.Debug, Message = "Signaling dispose to {ExtensionUniqueId}")]
partial void Log_SignalingDispose(string extensionUniqueId);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to send dispose signal to extension {ExtensionUniqueId}")]
partial void Log_ErrorSignalingDispose(string extensionUniqueId, Exception exception);
[LoggerMessage(Level = LogLevel.Warning, Message = "Extension {ExtensionName} declared unsupported extension interface: {InterfaceName}")]
static partial void Log_InvalidExtensionInterface(ILogger logger, string extensionName, string interfaceName);
}
internal record struct IsExtensionResult(bool IsExtension, AppExtension? Extension)

View File

@@ -15,7 +15,7 @@ using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -24,8 +24,14 @@ public partial class TopLevelCommandManager : ObservableObject,
IPageContext,
IDisposable
{
private readonly IServiceProvider _serviceProvider;
private readonly TaskScheduler _taskScheduler;
private readonly ILogger logger;
private readonly AppExtensionHost _commandPaletteHost;
private readonly IExtensionService _extensionService;
private readonly IEnumerable<ICommandProvider> _builtInProviders;
private readonly SettingsModel _settingsModel;
private readonly AliasManager _aliasManager;
private readonly HotkeyManager _hotkeyManager;
private readonly List<CommandProviderWrapper> _builtInCommands = [];
private readonly List<CommandProviderWrapper> _extensionCommandProviders = [];
@@ -34,10 +40,24 @@ public partial class TopLevelCommandManager : ObservableObject,
TaskScheduler IPageContext.Scheduler => _taskScheduler;
public TopLevelCommandManager(IServiceProvider serviceProvider)
public TopLevelCommandManager(
TaskScheduler taskScheduler,
AppExtensionHost commandPaletteHost,
IExtensionService extensionService,
IEnumerable<ICommandProvider> builtInProviders,
SettingsModel settingsModel,
AliasManager aliasManager,
HotkeyManager hotkeyManager,
ILogger logger)
{
_serviceProvider = serviceProvider;
_taskScheduler = _serviceProvider.GetService<TaskScheduler>()!;
this.logger = logger;
_commandPaletteHost = commandPaletteHost;
_extensionService = extensionService;
_builtInProviders = builtInProviders;
_settingsModel = settingsModel;
_aliasManager = aliasManager;
_hotkeyManager = hotkeyManager;
_taskScheduler = taskScheduler;
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
_reloadCommandsGate = new(ReloadAllCommandsAsyncCore);
}
@@ -70,10 +90,9 @@ public partial class TopLevelCommandManager : ObservableObject,
// Load built-In commands first. These are all in-proc, and
// owned by our ServiceProvider.
var builtInCommands = _serviceProvider.GetServices<ICommandProvider>();
foreach (var provider in builtInCommands)
foreach (var provider in _builtInProviders)
{
CommandProviderWrapper wrapper = new(provider, _taskScheduler);
CommandProviderWrapper wrapper = new(provider, _taskScheduler, logger, _aliasManager, _hotkeyManager);
lock (_commandProvidersLock)
{
_builtInCommands.Add(wrapper);
@@ -91,7 +110,7 @@ public partial class TopLevelCommandManager : ObservableObject,
s.Stop();
Logger.LogDebug($"Loading built-ins took {s.ElapsedMilliseconds}ms");
Log_BuiltInsLoaded(s.ElapsedMilliseconds);
return true;
}
@@ -101,7 +120,7 @@ public partial class TopLevelCommandManager : ObservableObject,
{
WeakReference<IPageContext> weakSelf = new(this);
await commandProvider.LoadTopLevelCommands(_serviceProvider, weakSelf);
await commandProvider.LoadTopLevelCommands(_settingsModel, weakSelf);
var commands = await Task.Factory.StartNew(
() =>
@@ -149,7 +168,7 @@ public partial class TopLevelCommandManager : ObservableObject,
private async Task UpdateCommandsForProvider(CommandProviderWrapper sender, IItemsChangedEventArgs args)
{
WeakReference<IPageContext> weakSelf = new(this);
await sender.LoadTopLevelCommands(_serviceProvider, weakSelf);
await sender.LoadTopLevelCommands(_settingsModel, weakSelf);
List<TopLevelViewModel> newItems = [.. sender.TopLevelItems];
foreach (var i in sender.FallbackItems)
@@ -216,8 +235,7 @@ public partial class TopLevelCommandManager : ObservableObject,
private async Task ReloadAllCommandsAsyncCore(CancellationToken cancellationToken)
{
IsLoading = true;
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
await extensionService.SignalStopExtensionsAsync();
await _extensionService.SignalStopExtensionsAsync();
lock (TopLevelCommands)
{
@@ -238,12 +256,10 @@ public partial class TopLevelCommandManager : ObservableObject,
[RelayCommand]
public async Task<bool> LoadExtensionsAsync()
{
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
_extensionService.OnExtensionAdded -= ExtensionService_OnExtensionAdded;
_extensionService.OnExtensionRemoved -= ExtensionService_OnExtensionRemoved;
extensionService.OnExtensionAdded -= ExtensionService_OnExtensionAdded;
extensionService.OnExtensionRemoved -= ExtensionService_OnExtensionRemoved;
var extensions = (await extensionService.GetInstalledExtensionsAsync()).ToImmutableList();
var extensions = (await _extensionService.GetInstalledExtensionsAsync()).ToImmutableList();
lock (_commandProvidersLock)
{
_extensionCommandProviders.Clear();
@@ -254,8 +270,8 @@ public partial class TopLevelCommandManager : ObservableObject,
await StartExtensionsAndGetCommands(extensions);
}
extensionService.OnExtensionAdded += ExtensionService_OnExtensionAdded;
extensionService.OnExtensionRemoved += ExtensionService_OnExtensionRemoved;
_extensionService.OnExtensionAdded += ExtensionService_OnExtensionAdded;
_extensionService.OnExtensionRemoved += ExtensionService_OnExtensionRemoved;
IsLoading = false;
@@ -319,7 +335,7 @@ public partial class TopLevelCommandManager : ObservableObject,
try
{
await extension.StartExtensionAsync().WaitAsync(TimeSpan.FromSeconds(10));
return new CommandProviderWrapper(extension, _taskScheduler);
return new CommandProviderWrapper(extension, _taskScheduler, logger, _aliasManager, _hotkeyManager);
}
catch (Exception ex)
{
@@ -414,7 +430,7 @@ public partial class TopLevelCommandManager : ObservableObject,
void IPageContext.ShowException(Exception ex, string? extensionHint)
{
var message = DiagnosticsHelper.BuildExceptionMessage(ex, extensionHint ?? "TopLevelCommandManager");
CommandPaletteHost.Instance.Log(message);
_commandPaletteHost.Log(message);
}
internal bool IsProviderActive(string id)
@@ -431,4 +447,22 @@ public partial class TopLevelCommandManager : ObservableObject,
_reloadCommandsGate.Dispose();
GC.SuppressFinalize(this);
}
[LoggerMessage(Level = LogLevel.Debug, Message = "Loading built-ins took {ElapsedMilliseconds}ms")]
partial void Log_BuiltInsLoaded(long elapsedMilliseconds);
[LoggerMessage(Level = LogLevel.Debug, Message = "Loading extensions took {ElapsedMilliseconds}ms")]
partial void Log_ExtensionsLoaded(long elapsedMilliseconds);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to load commands for extension {ExtensionName}")]
partial void Log_FailedToLoadCommandsForExtension(string? extensionName, Exception exception);
[LoggerMessage(Level = LogLevel.Warning, Message = "Loading commands for extension {ExtensionName} timed out")]
partial void Log_LoadingCommandsTimedOut(string? extensionName);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to start extension {ExtensionName}")]
partial void Log_FailedToStartExtension(string extensionName, Exception exception);
[LoggerMessage(Level = LogLevel.Debug, Message = "Starting extension {ExtensionName}")]
partial void Log_StartingExtension(string extensionName);
}

View File

@@ -12,7 +12,6 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
using Windows.Foundation;
using WyHash;
@@ -22,8 +21,9 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
private readonly SettingsModel _settings;
private readonly ProviderSettings _providerSettings;
private readonly IServiceProvider _serviceProvider;
private readonly CommandItemViewModel _commandItemViewModel;
private readonly HotkeyManager _hotkeyManager;
private readonly AliasManager _aliasManager;
private readonly string _commandProviderId;
@@ -99,7 +99,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
get => _hotkey;
set
{
_serviceProvider.GetService<HotkeyManager>()!.UpdateHotkey(Id, value);
_hotkeyManager.UpdateHotkey(Id, value);
UpdateHotkey();
UpdateTags();
Save();
@@ -176,14 +176,16 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
CommandPaletteHost extensionHost,
string commandProviderId,
SettingsModel settings,
ProviderSettings providerSettings,
IServiceProvider serviceProvider)
HotkeyManager hotkeyManager,
AliasManager aliasManager,
ProviderSettings providerSettings)
{
_serviceProvider = serviceProvider;
_settings = settings;
_aliasManager = aliasManager;
_providerSettings = providerSettings;
_commandProviderId = commandProviderId;
_commandItemViewModel = item;
_hotkeyManager = hotkeyManager;
IsFallback = isFallback;
ExtensionHost = extensionHost;
@@ -268,16 +270,15 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
? null
: new CommandAlias(Alias.Alias, Alias.CommandId, Alias.IsDirect);
_serviceProvider.GetService<AliasManager>()!.UpdateAlias(Id, commandAlias);
_aliasManager.UpdateAlias(Id, commandAlias);
UpdateTags();
}
private void FetchAliasFromAliasManager()
{
var am = _serviceProvider.GetService<AliasManager>();
if (am is not null)
if (_aliasManager is not null)
{
var commandAlias = am.AliasFromId(Id);
var commandAlias = _aliasManager.AliasFromId(Id);
if (commandAlias is not null)
{
// Decouple from the alias manager alias object

View File

@@ -2,8 +2,6 @@
// 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;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
@@ -24,11 +22,13 @@ using Microsoft.CmdPal.Ext.WindowsTerminal;
using Microsoft.CmdPal.Ext.WindowWalker;
using Microsoft.CmdPal.Ext.WinGet;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Services;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
@@ -41,7 +41,8 @@ namespace Microsoft.CmdPal.UI;
/// </summary>
public partial class App : Application
{
private readonly GlobalErrorHandler _globalErrorHandler = new();
private readonly GlobalErrorHandler _globalErrorHandler;
private readonly ILogger _logger;
/// <summary>
/// Gets the current <see cref="App"/> instance in use.
@@ -62,8 +63,11 @@ public partial class App : Application
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
public App(ILogger logger)
{
_logger = logger;
_globalErrorHandler = new(logger);
#if !CMDPAL_DISABLE_GLOBAL_ERROR_HANDLER
_globalErrorHandler.Register(this);
#endif
@@ -82,11 +86,6 @@ public partial class App : Application
AppWindow?.Close();
Environment.Exit(0);
});
// Connect the PT logging to the core project's logging.
// This way, log statements from the core project will be captured by the PT logs
var logWrapper = new LogWrapper();
CoreLogger.InitializeLogger(logWrapper);
}
/// <summary>
@@ -104,14 +103,21 @@ public partial class App : Application
/// <summary>
/// Configures the services for the application
/// </summary>
private static ServiceProvider ConfigureServices()
private ServiceProvider ConfigureServices()
{
// TODO: It's in the Labs feed, but we can use Sergio's AOT-friendly source generator for this: https://github.com/CommunityToolkit/Labs-Windows/discussions/463
ServiceCollection services = new();
// Root services
services.AddSingleton<ILogger>(_logger);
services.AddSingleton(TaskScheduler.FromCurrentSynchronizationContext());
// Load settings and state
var sm = SettingsModel.LoadSettings();
services.AddSingleton(sm);
var state = AppStateModel.LoadState();
services.AddSingleton(state);
// Built-in Commands. Order matters - this is the order they'll be presented by default.
var allApps = new AllAppsCommandProvider();
var files = new IndexerCommandsProvider();
@@ -141,8 +147,7 @@ public partial class App : Application
}
catch (Exception ex)
{
Logger.LogError("Couldn't load winget");
Logger.LogError(ex.ToString());
Log_FailureToLoadWinget(ex);
}
services.AddSingleton<ICommandProvider, WindowsTerminalCommandsProvider>();
@@ -158,16 +163,12 @@ public partial class App : Application
services.AddSingleton<TopLevelCommandManager>();
services.AddSingleton<AliasManager>();
services.AddSingleton<HotkeyManager>();
var sm = SettingsModel.LoadSettings();
services.AddSingleton(sm);
var state = AppStateModel.LoadState();
services.AddSingleton(state);
services.AddSingleton<IExtensionService, ExtensionService>();
services.AddSingleton<TrayIconService>();
services.AddSingleton<IRunHistoryService, RunHistoryService>();
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
services.AddSingleton<AppExtensionHost, CommandPaletteHost>();
services.AddSingleton<ITelemetryService, TelemetryForwarder>();
// ViewModels
@@ -176,4 +177,7 @@ public partial class App : Application
return services.BuildServiceProvider();
}
[LoggerMessage(Level = LogLevel.Error, Message = "Couldn't load winget")]
partial void Log_FailureToLoadWinget(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 ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.Deferred;
using Microsoft.Terminal.UI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -21,6 +21,7 @@ namespace Microsoft.CmdPal.UI.Controls;
public partial class IconBox : ContentControl
{
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
private readonly ILogger logger;
/// <summary>
/// Gets or sets the <see cref="IconSource"/> to display within the <see cref="IconBox"/>. Overwritten, if <see cref="SourceKey"/> is used instead.
@@ -59,6 +60,8 @@ public partial class IconBox : ContentControl
IsTabStop = false;
HorizontalContentAlignment = HorizontalAlignment.Center;
VerticalContentAlignment = VerticalAlignment.Center;
logger = App.Current.Services.GetService<ILogger>()!;
}
private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -168,10 +171,13 @@ public partial class IconBox : ContentControl
{
// Exception from TryEnqueue bypasses the global error handler,
// and crashes the app.
Logger.LogError("Failed to set icon", ex);
Log_FailedToSetIcon(@this.logger, ex);
}
});
}
}
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to set icon")]
static partial void Log_FailedToSetIcon(ILogger logger, Exception ex);
}

View File

@@ -7,7 +7,6 @@ using CommunityToolkit.WinUI;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Commands;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
@@ -239,7 +238,6 @@ public sealed partial class SearchBar : UserControl,
FilterBox.Text = _lastText ?? string.Empty;
FilterBox.Select(FilterBox.Text.Length, 0);
// Logger.LogInfo("deleting suggestion");
_inSuggestion = false;
_lastText = null;
@@ -266,7 +264,6 @@ public sealed partial class SearchBar : UserControl,
return;
}
// Logger.LogInfo("leaving suggestion");
_inSuggestion = false;
_lastText = null;
}
@@ -283,8 +280,6 @@ public sealed partial class SearchBar : UserControl,
private void FilterBox_TextChanged(object sender, TextChangedEventArgs e)
{
// Logger.LogInfo($"FilterBox_TextChanged: {FilterBox.Text}");
// TERRIBLE HACK TODO GH #245
// There's weird wacky bugs with debounce currently. We're trying
// to get them ingested, but while we wait for the toolkit feeds to
@@ -299,7 +294,6 @@ public sealed partial class SearchBar : UserControl,
if (InSuggestion)
{
// Logger.LogInfo($"-- skipping, in suggestion --");
return;
}
@@ -321,7 +315,6 @@ public sealed partial class SearchBar : UserControl,
{
if (InSuggestion)
{
// Logger.LogInfo($"--- skipping ---");
return;
}
@@ -385,7 +378,6 @@ public sealed partial class SearchBar : UserControl,
if (clearSuggestion && _inSuggestion)
{
// Logger.LogInfo($"Cleared suggestion \"{_lastText}\" to {suggestion}");
_inSuggestion = false;
FilterBox.Text = _lastText ?? string.Empty;
_lastText = null;
@@ -411,14 +403,6 @@ public sealed partial class SearchBar : UserControl,
_lastText = currentText;
// if (_inSuggestion)
// {
// Logger.LogInfo($"Suggestion from \"{_lastText}\" to {suggestion}");
// }
// else
// {
// Logger.LogInfo($"Entering suggestion from \"{_lastText}\" to {suggestion}");
// }
_inSuggestion = true;
var matchedChars = 0;

View File

@@ -4,7 +4,6 @@
using System.Diagnostics;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Commands;
using Microsoft.CmdPal.Core.ViewModels.Messages;
@@ -12,6 +11,7 @@ using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
@@ -32,6 +32,7 @@ public sealed partial class ListPage : Page,
IRecipient<ActivateSecondaryCommandMessage>
{
private InputSource _lastInputSource;
private ILogger logger = App.Current.Services.GetService<ILogger>()!;
internal ListViewModel? ViewModel
{
@@ -456,7 +457,7 @@ public sealed partial class ListPage : Page,
}
else if (e.NewValue is null)
{
Logger.LogDebug("cleared view model");
Log_ClearedViewModel(@this.logger);
}
}
}
@@ -576,4 +577,7 @@ public sealed partial class ListPage : Page,
Keyboard,
Pointer,
}
[LoggerMessage(Level = LogLevel.Debug, Message = "Cleared view model")]
static partial void Log_ClearedViewModel(ILogger logger);
}

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 ManagedCommon;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.Extensions.Logging;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
@@ -17,6 +17,13 @@ namespace Microsoft.CmdPal.UI.Helpers;
/// </summary>
internal sealed partial class GlobalErrorHandler
{
private readonly ILogger _logger;
public GlobalErrorHandler(ILogger logger)
{
_logger = logger;
}
// GlobalErrorHandler is designed to be self-contained; it can be registered and invoked before a service provider is available.
internal void Register(App app)
{
@@ -54,9 +61,9 @@ internal sealed partial class GlobalErrorHandler
HandleException(e.Exception, Context.UnobservedTaskException);
}
private static void HandleException(Exception ex, Context context)
private void HandleException(Exception ex, Context context)
{
Logger.LogError($"Unhandled exception detected ({context})", ex);
Log_UnhandledException(ex, context);
if (context == Context.MainThreadException)
{
@@ -93,7 +100,7 @@ internal sealed partial class GlobalErrorHandler
}
}
private static string? StoreReport(string report, bool storeOnDesktop)
private string? StoreReport(string report, bool storeOnDesktop)
{
// Generate a unique name for the report file; include timestamp and a random zero-padded number to avoid collisions
// in case of crash storm.
@@ -101,15 +108,16 @@ internal sealed partial class GlobalErrorHandler
// Always store a copy in log directory, this way it is available for Bug Report Tool
string? reportPath = null;
if (Logger.CurrentVersionLogDirectoryPath != null)
{
reportPath = Save(report, name, static () => Logger.CurrentVersionLogDirectoryPath);
}
// if (!string.IsNullOrEmpty(logWrapper.CurrentVersionLogDirectoryPath))
// {
// reportPath = Save(report, name, logWrapper.CurrentVersionLogDirectoryPath);
// s}
// Optionally store a copy on the desktop for user (in)convenience
if (storeOnDesktop)
{
var path = Save(report, name, static () => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
var path = Save(report, name, Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
// show the desktop copy if both succeeded
if (path != null)
@@ -120,11 +128,10 @@ internal sealed partial class GlobalErrorHandler
return reportPath;
static string? Save(string reportContent, string reportFileName, Func<string> directory)
string? Save(string reportContent, string reportFileName, string logDirectory)
{
try
{
var logDirectory = directory();
Directory.CreateDirectory(logDirectory);
var reportFilePath = Path.Combine(logDirectory, reportFileName);
File.WriteAllText(reportFilePath, reportContent);
@@ -132,7 +139,7 @@ internal sealed partial class GlobalErrorHandler
}
catch (Exception ex)
{
Logger.LogError("Failed to store exception report", ex);
Log_FailureToStoreExceptionReport(ex);
return null;
}
}
@@ -146,4 +153,10 @@ internal sealed partial class GlobalErrorHandler
UnobservedTaskException,
AppDomainUnhandledException,
}
[LoggerMessage(level: LogLevel.Error, Message = "Failed to store exception report")]
partial void Log_FailureToStoreExceptionReport(Exception ex);
[LoggerMessage(level: LogLevel.Error, message: "Unhandled exception detected ({Context})")]
partial void Log_UnhandledException(Exception ex, Context context);
}

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 ManagedCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Windows.System;
using Windows.Win32;
using Windows.Win32.Foundation;
@@ -16,6 +16,8 @@ namespace Microsoft.CmdPal.UI.Helpers;
/// </summary>
internal sealed partial class LocalKeyboardListener : IDisposable
{
private readonly ILogger logger = App.Current.Services.GetRequiredService<ILogger>()!;
/// <summary>
/// Event that is raised when a key is pressed down.
/// </summary>
@@ -68,7 +70,7 @@ internal sealed partial class LocalKeyboardListener : IDisposable
}
catch (Exception ex)
{
Logger.LogError("Failed to register hook", ex);
Log_FailureToRegisterHook(ex);
return false;
}
}
@@ -121,7 +123,7 @@ internal sealed partial class LocalKeyboardListener : IDisposable
}
catch (Exception ex)
{
Logger.LogError("Failed when invoking key down keyboard hook event", ex);
Log_FailureOnKeyDown(ex);
}
// Call next hook in chain - pass null as first parameter for current hook
@@ -154,4 +156,10 @@ internal sealed partial class LocalKeyboardListener : IDisposable
_disposed = true;
}
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to register hook")]
private partial void Log_FailureToRegisterHook(Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed when invoking key down keyboard hook event")]
private partial void Log_FailureOnKeyDown(Exception ex);
}

View File

@@ -3,7 +3,8 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.WinUI.Controls;
using ManagedCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI.Helpers.MarkdownImageProviders;
@@ -31,7 +32,8 @@ internal sealed partial class ImageProvider : IImageProvider
}
catch (Exception ex)
{
Logger.LogError($"Failed to provide an image from URI '{url}'", ex);
var logger = App.Current.Services.GetRequiredService<ILogger>();
Log_ImageProviderError(logger, url, ex);
return null!;
}
}
@@ -40,4 +42,7 @@ internal sealed partial class ImageProvider : IImageProvider
{
return _compositeProvider.ShouldUseThisProvider(url);
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to provide an image from URI '{Url}'")]
static partial void Log_ImageProviderError(ILogger logger, string url, Exception ex);
}

View File

@@ -1,41 +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 ManagedCommon;
using Microsoft.CmdPal.Core.Common;
namespace Microsoft.CmdPal.UI;
internal sealed class LogWrapper : ILogger
{
public 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 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 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 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 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 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);
}
}

View File

@@ -6,7 +6,6 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using CmdPalKeyboardService;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels.Messages;
@@ -18,6 +17,7 @@ using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI;
using Microsoft.UI.Composition;
@@ -66,6 +66,7 @@ public sealed partial class MainWindow : WindowEx,
private readonly KeyboardListener _keyboardListener;
private readonly LocalKeyboardListener _localKeyboardListener;
private readonly HiddenOwnerWindowBehavior _hiddenOwnerBehavior = new();
private readonly ILogger logger = App.Current.Services.GetRequiredService<ILogger>();
private bool _ignoreHotKeyWhenFullScreen = true;
private DesktopAcrylicController? _acrylicController;
@@ -363,7 +364,7 @@ public sealed partial class MainWindow : WindowEx,
/// A window rectangle in physical pixels, moved to the nearest display and resized
/// if the DPI has changed.
/// </returns>
private static RectInt32 EnsureWindowIsVisible(RectInt32 windowRect, SizeInt32 originalScreen, int originalDpi)
private RectInt32 EnsureWindowIsVisible(RectInt32 windowRect, SizeInt32 originalScreen, int originalDpi)
{
var displayArea = DisplayArea.GetFromRect(windowRect, DisplayAreaFallback.Nearest);
if (displayArea is null)
@@ -435,7 +436,7 @@ public sealed partial class MainWindow : WindowEx,
return new RectInt32(targetX, targetY, targetWidth, targetHeight);
}
private static int GetEffectiveDpiFromDisplayId(DisplayArea displayArea)
private int GetEffectiveDpiFromDisplayId(DisplayArea displayArea)
{
var effectiveDpi = 96;
@@ -449,7 +450,7 @@ public sealed partial class MainWindow : WindowEx,
}
else
{
Logger.LogWarning($"GetDpiForMonitor failed with HRESULT: 0x{hr.Value:X8} on display {displayArea.DisplayId}");
LogWarning_GetDpiForMonitorFailed(hr, displayArea);
}
}
@@ -590,7 +591,7 @@ public sealed partial class MainWindow : WindowEx,
var hr = PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL));
if (hr.Failed)
{
Logger.LogWarning($"DWM cloaking of the main window failed. HRESULT: {hr.Value}.");
Log_DwmCloakingFailed(hr);
}
wasCloaked = hr.Succeeded;
@@ -780,12 +781,12 @@ public sealed partial class MainWindow : WindowEx,
var settings = App.Current.Services.GetService<SettingsModel>();
if (settings?.AllowExternalReload == true)
{
Logger.LogInfo("External Reload triggered");
Log_ExternalReloadTriggered();
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
}
else
{
Logger.LogInfo("External Reload is disabled");
Log_ExternalReloadDisabled();
}
return;
@@ -804,17 +805,11 @@ public sealed partial class MainWindow : WindowEx,
// if the args are not valid or not passed correctly.
if (ex.HResult is RPC_S_SERVER_UNAVAILABLE or RPC_S_CALL_FAILED)
{
Logger.LogWarning(
$"COM exception (HRESULT {ex.HResult}) when accessing activation arguments. " +
$"This might be due to the calling application not passing them correctly or exiting before we could read them. " +
$"The application will continue running and fall back to showing the Command Palette window.");
Log_COMExceptionAccessingActivationArguments(ex.HResult);
}
else
{
Logger.LogError(
$"COM exception (HRESULT {ex.HResult}) when activating the application. " +
$"The application will continue running and fall back to showing the Command Palette window.",
ex);
Log_COMExceptionActivationApplication(ex.HResult);
}
}
@@ -1001,4 +996,26 @@ public sealed partial class MainWindow : WindowEx,
_localKeyboardListener.Dispose();
DisposeAcrylic();
}
[LoggerMessage(level: LogLevel.Warning, Message = "GetDpiForMonitor failed with HRESULT: 0x{hr.Value:X8} on display {DisplayArea.DisplayId}")]
partial void LogWarning_GetDpiForMonitorFailed(HRESULT hr, DisplayArea displayArea);
[LoggerMessage(level: LogLevel.Warning, Message = "DWM cloaking of the main window failed. HRESULT: {hr.Value}.")]
partial void Log_DwmCloakingFailed(HRESULT hr);
[LoggerMessage(level: LogLevel.Information, Message = "External Reload triggered")]
partial void Log_ExternalReloadTriggered();
[LoggerMessage(level: LogLevel.Information, Message = "External Reload is disabled")]
partial void Log_ExternalReloadDisabled();
[LoggerMessage(
level: LogLevel.Error,
Message = "COM exception (HRESULT {HResult}) when accessing activation arguments. This might be due to the calling application not passing them correctly or exiting before we could read them. The application will continue running and fall back to showing the Command Palette window.")]
partial void Log_COMExceptionAccessingActivationArguments(int hResult);
[LoggerMessage(
level: LogLevel.Error,
Message = "COM exception (HRESULT {HResult}) when activating the application. The application will continue running and fall back to showing the Command Palette window.")]
partial void Log_COMExceptionActivationApplication(int hResult);
}

View File

@@ -7,7 +7,7 @@ using System.Globalization;
using System.Text;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Events;
@@ -17,6 +17,7 @@ using Microsoft.CmdPal.UI.Settings;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
@@ -50,6 +51,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
INotifyPropertyChanged,
IDisposable
{
private readonly ILogger logger = App.Current.Services.GetService<ILogger>()!;
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
@@ -179,7 +182,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
Log_Exception(ex);
}
});
}
@@ -200,7 +203,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
return;
}
ConfirmResultViewModel vm = new(args, new(ViewModel.CurrentPage));
ConfirmResultViewModel vm = new(args, new(ViewModel.CurrentPage), logger);
var initializeDialogTask = Task.Run(() => { InitializeConfirmationDialog(vm); });
await initializeDialogTask;
@@ -488,13 +491,13 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
break;
default:
ViewModel.CurrentPage = ViewModel.NullPage;
Logger.LogWarning($"Invalid navigation target: AsyncNavigationRequest.{nameof(AsyncNavigationRequest.TargetViewModel)} must be {nameof(PageViewModel)}");
Log_InvalidNavigationTarget(nameof(AsyncNavigationRequest.TargetViewModel), nameof(PageViewModel));
break;
}
}
else
{
Logger.LogWarning("Unrecognized target for shell navigation: " + e.Parameter);
Log_UnrecognizedNavigationTarget(e.Parameter);
}
if (e.Content is Page element)
@@ -580,7 +583,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
catch (Exception ex)
{
Logger.LogError("Error during FocusAfterLoaded async focus work", ex);
Log_ErrorDuringFocusAfterLoaded(ex);
}
},
token);
@@ -709,7 +712,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
catch (Exception ex)
{
Logger.LogError("Error handling mouse button press event", ex);
Log_ErrorHandlingMouseEvent(ex);
}
}
@@ -719,4 +722,19 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
_focusAfterLoadedCts?.Dispose();
_focusAfterLoadedCts = null;
}
[LoggerMessage(Level = LogLevel.Error)]
partial void Log_Exception(Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Error handling mouse button press event")]
partial void Log_ErrorHandlingMouseEvent(Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Error during FocusAfterLoaded async focus work")]
partial void Log_ErrorDuringFocusAfterLoaded(Exception ex);
[LoggerMessage(Level = LogLevel.Warning, Message = "Unrecognized target for shell navigation: {parameter}")]
partial void Log_UnrecognizedNavigationTarget(object? parameter);
[LoggerMessage(Level = LogLevel.Error, Message = "Invalid navigation target: AsyncNavigationRequest.{targetViewModel} must be {pageViewModel}")]
partial void Log_InvalidNavigationTarget(string targetViewModel, string pageViewModel);
}

View File

@@ -1,29 +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 Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Microsoft.CmdPal.UI;
internal sealed class PowerToysAppHostService : IAppHostService
{
public AppExtensionHost GetDefaultHost()
{
return CommandPaletteHost.Instance;
}
public AppExtensionHost GetHostForCommand(object? context, AppExtensionHost? currentHost)
{
AppExtensionHost? topLevelHost = null;
if (context is TopLevelViewModel topLevelViewModel)
{
topLevelHost = topLevelViewModel.ExtensionHost;
}
return topLevelHost ?? currentHost ?? CommandPaletteHost.Instance;
}
}

View File

@@ -2,26 +2,26 @@
// 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.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Services;
using Microsoft.Extensions.Logging;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
using Microsoft.Windows.AppLifecycle;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Microsoft.CmdPal.UI;
// cribbed heavily from
//
// https://github.com/microsoft/WindowsAppSDK-Samples/tree/main/Samples/AppLifecycle/Instancing/cs2/cs-winui-packaged/CsWinUiDesktopInstancing
internal sealed class Program
internal sealed partial class Program
{
private static DispatcherQueueSynchronizationContext? uiContext;
private static App? app;
private static ILogger logger = new LogWrapper();
// LOAD BEARING
//
@@ -37,34 +37,7 @@ internal sealed class Program
return 0;
}
try
{
Logger.InitializeLogger("\\CmdPal\\Logs\\");
}
catch (COMException e)
{
// This is unexpected. For the sake of debugging:
// pop a message box
PInvoke.MessageBox(
(HWND)IntPtr.Zero,
$"Failed to initialize the logger. COMException: \r{e.Message}",
"Command Palette",
MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
return 0;
}
catch (Exception e2)
{
// This is unexpected. For the sake of debugging:
// pop a message box
PInvoke.MessageBox(
(HWND)IntPtr.Zero,
$"Failed to initialize the logger. Unknown Exception: \r{e2.Message}",
"Command Palette",
MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
return 0;
}
Logger.LogDebug($"Starting at {DateTime.UtcNow}");
Log_StartingAt(logger, DateTime.UtcNow);
PowerToysTelemetry.Log.WriteEvent(new CmdPalProcessStarted());
WinRT.ComWrappersSupport.InitializeComWrappers();
@@ -75,7 +48,7 @@ internal sealed class Program
{
uiContext = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(uiContext);
app = new App();
app = new App(logger);
});
}
@@ -122,11 +95,11 @@ internal sealed class Program
}
catch (OperationCanceledException)
{
Logger.LogError($"Failed to activate existing instance; timed out after {redirectTimeout}.");
Log_FailedToActivate(logger, redirectTimeout);
}
catch (Exception ex)
{
Logger.LogError("Failed to activate existing instance", ex);
Log_ActivateError(logger, ex);
}
finally
{
@@ -155,4 +128,13 @@ internal sealed class Program
mainWindow.HandleLaunchNonUI(args);
}
}
[LoggerMessage(Level = LogLevel.Debug, Message = "Starting at {UtcNow}.")]
private static partial void Log_StartingAt(ILogger logger, DateTime utcNow);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to activate existing instance; timed out after {RedirectTimeout}.")]
private static partial void Log_FailedToActivate(ILogger logger, TimeSpan redirectTimeout);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to activate existing instance")]
private static partial void Log_ActivateError(ILogger logger, Exception ex);
}

View File

@@ -0,0 +1,196 @@
// 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.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.Extensions.Logging;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Microsoft.CmdPal.UI.Services;
// Adapter implementing Microsoft.Extensions.Logging.ILogger,
// delegating to ManagedCommon.Logger.
internal sealed partial class LogWrapper : ILogger
{
private static readonly AsyncLocal<Stack<object>> _scopeStack = new();
private readonly LogLevel _minLevel;
public string CurrentVersionLogDirectoryPath => Logger.CurrentVersionLogDirectoryPath;
public string CurrentLogFile => Logger.CurrentLogFile;
public LogWrapper(LogLevel minLevel = LogLevel.Information)
{
_minLevel = minLevel;
// Ensure underlying logger initialized (idempotent if already done elsewhere).
try
{
Logger.InitializeLogger("\\CmdPal\\Logs\\");
}
catch (COMException e)
{
// This is unexpected. For the sake of debugging:
// pop a message box
PInvoke.MessageBox(
(HWND)IntPtr.Zero,
$"Failed to initialize the logger. COMException: \r{e.Message}",
"Command Palette",
MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
}
catch (Exception e2)
{
// This is unexpected. For the sake of debugging:
// pop a message box
PInvoke.MessageBox(
(HWND)IntPtr.Zero,
$"Failed to initialize the logger. Unknown Exception: \r{e2.Message}",
"Command Palette",
MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
}
}
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

@@ -4,55 +4,58 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.MainPage;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WinRT;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Microsoft.CmdPal.UI;
namespace Microsoft.CmdPal.UI.Services;
internal sealed class PowerToysRootPageService : IRootPageService
internal sealed partial class PowerToysRootPageService : IRootPageService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger logger;
private readonly TopLevelCommandManager _topLevelCommandManager;
private IExtensionWrapper? _activeExtension;
private Lazy<MainListPage> _mainListPage;
public PowerToysRootPageService(IServiceProvider serviceProvider)
public PowerToysRootPageService(
TopLevelCommandManager topLevelCommandManager,
AliasManager aliasManager,
SettingsModel settingsModel,
AppStateModel appStateModel,
ILogger logger)
{
_serviceProvider = serviceProvider;
this.logger = logger;
_topLevelCommandManager = topLevelCommandManager;
_mainListPage = new Lazy<MainListPage>(() =>
{
return new MainListPage(_serviceProvider);
return new MainListPage(topLevelCommandManager, settingsModel, aliasManager, appStateModel);
});
}
public async Task PreLoadAsync()
{
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
await tlcManager.LoadBuiltinsAsync();
await _topLevelCommandManager.LoadBuiltinsAsync();
}
public Microsoft.CommandPalette.Extensions.IPage GetRootPage()
public IPage GetRootPage()
{
return _mainListPage.Value;
}
public async Task PostLoadRootPageAsync()
{
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
tlcManager.LoadExtensionsCommand.Execute(null);
_topLevelCommandManager.LoadExtensionsCommand.Execute(null);
await tlcManager.LoadExtensionsCommand.ExecutionTask!;
if (tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
await _topLevelCommandManager.LoadExtensionsCommand.ExecutionTask!;
if (_topLevelCommandManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
{
// TODO: Handle failure case
}
@@ -69,8 +72,7 @@ internal sealed class PowerToysRootPageService : IRootPageService
}
catch (Exception ex)
{
Logger.LogError("Failed to update history in PowerToysRootPageService");
Logger.LogError(ex.ToString());
Log_ErrorUpdatingHistory(ex);
}
}
@@ -111,13 +113,13 @@ internal sealed class PowerToysRootPageService : IRootPageService
var hr = Native.CoAllowSetForegroundWindow(intPtr);
if (hr != 0)
{
Logger.LogWarning($"Error giving foreground rights: 0x{hr.Value:X8}");
Log_FailureToGiveForegroundRights(hr);
}
}
}
catch (Exception ex)
{
ManagedCommon.Logger.LogError(ex.ToString());
Log_ErrorSettingActiveExtension(ex);
}
}
}
@@ -141,4 +143,13 @@ internal sealed class PowerToysRootPageService : IRootPageService
[SupportedOSPlatform("windows5.0")]
internal static extern unsafe global::Windows.Win32.Foundation.HRESULT CoAllowSetForegroundWindow(nint pUnk, [Optional] void* lpvReserved);
}
[LoggerMessage(Level = LogLevel.Error)]
partial void Log_ErrorSettingActiveExtension(Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to update history in PowerToysRootPageService")]
partial void Log_ErrorUpdatingHistory(Exception ex);
[LoggerMessage(Level = LogLevel.Warning, Message = "Error giving foreground rights: 0x{hr.Value:X8}")]
partial void Log_FailureToGiveForegroundRights(global::Windows.Win32.Foundation.HRESULT hr);
}

View File

@@ -4,7 +4,7 @@
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.ViewModels.Messages;
@@ -12,9 +12,6 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.HiDpi;
using Windows.Win32.UI.WindowsAndMessaging;
using WinUIEx;
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
@@ -24,6 +21,7 @@ namespace Microsoft.CmdPal.UI;
public sealed partial class ToastWindow : WindowEx,
IRecipient<QuitMessage>
{
// private readonly ILogger logger = App.Current.Services.GetRequiredService<ILogger>();
private readonly HWND _hwnd;
private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
private readonly HiddenOwnerWindowBehavior _hiddenOwnerWindowBehavior = new();
@@ -47,21 +45,20 @@ public sealed partial class ToastWindow : WindowEx,
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
}
private static double GetScaleFactor(HWND hwnd)
{
try
{
var monitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
_ = PInvoke.GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out _);
return dpiX / 96.0;
}
catch (Exception ex)
{
Logger.LogError($"Failed to get scale factor, error: {ex.Message}");
return 1.0;
}
}
// private static double GetScaleFactor(HWND hwnd)
// {
// try
// {
// var monitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
// _ = PInvoke.GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out _);
// return dpiX / 96.0;
// }
// catch (Exception ex)
// {
// Log_FailedToGetScaleFactor(ex);
// return 1.0;
// }
// }
private void PositionCentered()
{
this.SetWindowSize(ToastText.ActualWidth, ToastText.ActualHeight);
@@ -106,4 +103,7 @@ public sealed partial class ToastWindow : WindowEx,
// This might come in on a background thread
DispatcherQueue.TryEnqueue(() => Close());
}
// [LoggerMessage(Level = LogLevel.Warning, Message = "Failed to get scale factor")]
// static partial void Log_FailedToGetScaleFactor(Exception ex);
}

View File

@@ -8,8 +8,11 @@ using System.Globalization;
using System.Text.RegularExpressions;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ManagedCommon;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI;
using Windows.System;
using Windows.UI;
@@ -19,6 +22,8 @@ namespace Microsoft.CmdPal.UI.ViewModels;
internal sealed partial class DevRibbonViewModel : ObservableObject
{
private readonly LogWrapper logger;
private const int MaxLogEntries = 2;
private const string Release = "Release";
private const string Debug = "Debug";
@@ -32,6 +37,7 @@ internal sealed partial class DevRibbonViewModel : ObservableObject
public DevRibbonViewModel()
{
logger = (LogWrapper)App.Current.Services.GetRequiredService<ILogger>();
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
Trace.Listeners.Add(new DevRibbonTraceListener(this));
@@ -74,7 +80,7 @@ internal sealed partial class DevRibbonViewModel : ObservableObject
[RelayCommand]
private async Task OpenLogFileAsync()
{
var logPath = Logger.CurrentLogFile;
var logPath = logger.CurrentLogFile;
if (File.Exists(logPath))
{
await Launcher.LaunchUriAsync(new Uri(logPath));
@@ -84,7 +90,7 @@ internal sealed partial class DevRibbonViewModel : ObservableObject
[RelayCommand]
private async Task OpenLogFolderAsync()
{
var logFolderPath = Logger.CurrentVersionLogDirectoryPath;
var logFolderPath = logger.CurrentVersionLogDirectoryPath;
if (Directory.Exists(logFolderPath))
{
await Launcher.LaunchFolderPathAsync(logFolderPath);
@@ -187,4 +193,7 @@ internal sealed partial class DevRibbonViewModel : ObservableObject
}
}
}
[LoggerMessage(level: LogLevel.Information, Message = "DevRibbonViewModel initialized.")]
public partial void LogInitialized();
}