2025-07-15 12:21:44 -05:00
|
|
|
|
// Copyright (c) Microsoft Corporation
|
|
|
|
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
|
|
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
|
|
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
|
using System.Diagnostics;
|
2026-02-23 04:05:09 -08:00
|
|
|
|
using Microsoft.CmdPal.Common;
|
2025-07-15 12:21:44 -05:00
|
|
|
|
using Microsoft.CommandPalette.Extensions;
|
|
|
|
|
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
|
|
|
|
|
using Windows.Foundation;
|
|
|
|
|
|
|
2026-02-23 04:05:09 -08:00
|
|
|
|
namespace Microsoft.CmdPal.UI.ViewModels;
|
2025-07-15 12:21:44 -05:00
|
|
|
|
|
|
|
|
|
|
public abstract partial class AppExtensionHost : IExtensionHost
|
|
|
|
|
|
{
|
|
|
|
|
|
private static readonly GlobalLogPageContext _globalLogPageContext = new();
|
|
|
|
|
|
|
|
|
|
|
|
private static ulong _hostingHwnd;
|
|
|
|
|
|
|
|
|
|
|
|
public static ObservableCollection<LogMessageViewModel> LogMessages { get; } = [];
|
|
|
|
|
|
|
|
|
|
|
|
public ulong HostingHwnd => _hostingHwnd;
|
|
|
|
|
|
|
|
|
|
|
|
public string LanguageOverride => string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
public ObservableCollection<StatusMessageViewModel> StatusMessages { get; } = [];
|
|
|
|
|
|
|
|
|
|
|
|
public static void SetHostHwnd(ulong hostHwnd) => _hostingHwnd = hostHwnd;
|
|
|
|
|
|
|
|
|
|
|
|
public void DebugLog(string message)
|
|
|
|
|
|
{
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
this.ProcessLogMessage(new LogMessage(message));
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IAsyncAction HideStatus(IStatusMessage? message)
|
|
|
|
|
|
{
|
2025-08-18 06:07:28 -05:00
|
|
|
|
if (message is null)
|
2025-07-15 12:21:44 -05:00
|
|
|
|
{
|
|
|
|
|
|
return Task.CompletedTask.AsAsyncAction();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_ = Task.Run(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
ProcessHideStatusMessage(message);
|
|
|
|
|
|
});
|
|
|
|
|
|
return Task.CompletedTask.AsAsyncAction();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Log(string message)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.ProcessLogMessage(new LogMessage(message));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IAsyncAction LogMessage(ILogMessage? message)
|
|
|
|
|
|
{
|
2025-08-18 06:07:28 -05:00
|
|
|
|
if (message is null)
|
2025-07-15 12:21:44 -05:00
|
|
|
|
{
|
|
|
|
|
|
return Task.CompletedTask.AsAsyncAction();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-26 08:05:37 -05:00
|
|
|
|
CoreLogger.LogDebug(message.Message);
|
2025-07-15 12:21:44 -05:00
|
|
|
|
|
|
|
|
|
|
_ = Task.Run(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
ProcessLogMessage(message);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// We can't just make a LogMessageViewModel : ExtensionObjectViewModel
|
|
|
|
|
|
// because we don't necessarily know the page context. Butts.
|
|
|
|
|
|
return Task.CompletedTask.AsAsyncAction();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ProcessHideStatusMessage(IStatusMessage message)
|
|
|
|
|
|
{
|
|
|
|
|
|
Task.Factory.StartNew(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var vm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
|
2025-08-18 06:07:28 -05:00
|
|
|
|
if (vm is not null)
|
2025-07-15 12:21:44 -05:00
|
|
|
|
{
|
|
|
|
|
|
StatusMessages.Remove(vm);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
CancellationToken.None,
|
|
|
|
|
|
TaskCreationOptions.None,
|
|
|
|
|
|
_globalLogPageContext.Scheduler);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ProcessLogMessage(ILogMessage message)
|
|
|
|
|
|
{
|
|
|
|
|
|
var vm = new LogMessageViewModel(message, _globalLogPageContext);
|
|
|
|
|
|
vm.SafeInitializePropertiesSynchronous();
|
|
|
|
|
|
|
|
|
|
|
|
Task.Factory.StartNew(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
LogMessages.Add(vm);
|
|
|
|
|
|
},
|
|
|
|
|
|
CancellationToken.None,
|
|
|
|
|
|
TaskCreationOptions.None,
|
|
|
|
|
|
_globalLogPageContext.Scheduler);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ProcessStatusMessage(IStatusMessage message, StatusContext context)
|
|
|
|
|
|
{
|
|
|
|
|
|
// If this message is already in the list of messages, just bring it to the top
|
|
|
|
|
|
var oldVm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
|
2025-08-18 06:07:28 -05:00
|
|
|
|
if (oldVm is not null)
|
2025-07-15 12:21:44 -05:00
|
|
|
|
{
|
|
|
|
|
|
Task.Factory.StartNew(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
StatusMessages.Remove(oldVm);
|
|
|
|
|
|
StatusMessages.Add(oldVm);
|
|
|
|
|
|
},
|
|
|
|
|
|
CancellationToken.None,
|
|
|
|
|
|
TaskCreationOptions.None,
|
|
|
|
|
|
_globalLogPageContext.Scheduler);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
|
|
|
|
|
|
vm.SafeInitializePropertiesSynchronous();
|
|
|
|
|
|
|
|
|
|
|
|
Task.Factory.StartNew(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
StatusMessages.Add(vm);
|
|
|
|
|
|
},
|
|
|
|
|
|
CancellationToken.None,
|
|
|
|
|
|
TaskCreationOptions.None,
|
|
|
|
|
|
_globalLogPageContext.Scheduler);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IAsyncAction ShowStatus(IStatusMessage? message, StatusContext context)
|
|
|
|
|
|
{
|
2025-08-18 06:07:28 -05:00
|
|
|
|
if (message is null)
|
2025-07-15 12:21:44 -05:00
|
|
|
|
{
|
|
|
|
|
|
return Task.CompletedTask.AsAsyncAction();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Debug.WriteLine(message.Message);
|
|
|
|
|
|
|
|
|
|
|
|
_ = Task.Run(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
ProcessStatusMessage(message, context);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return Task.CompletedTask.AsAsyncAction();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public abstract string? GetExtensionDisplayName();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public interface IAppHostService
|
|
|
|
|
|
{
|
|
|
|
|
|
AppExtensionHost GetDefaultHost();
|
|
|
|
|
|
|
|
|
|
|
|
AppExtensionHost GetHostForCommand(object? context, AppExtensionHost? currentHost);
|
CmdPal: Load pinned command items from anywhere (#45566)
This doesn't actually have a UX to expose this yet - we need to stack a
couple of PRs up to get to that.
But this adds plumbing such that we can now stash away a command ID, and
retrieve it later as a top-level command. Kinda like pinning for apps,
but for _anything_.
It works off of a new command provider interface `ICommandProvider4`,
which lets us look up Command**Item**s by ID. If we see a command ID
stored in that command provider's settings, we will try to look it up,
and then load it from the command provider.
e.g.
```json
"com.microsoft.cmdpal.builtin.system": {
"IsEnabled": true,
"FallbackCommands": {
"com.microsoft.cmdpal.builtin.system.fallback": {
"IsEnabled": true,
"IncludeInGlobalResults": true
}
},
"PinnedCommandIds": [
"com.microsoft.cmdpal.builtin.system.lock",
"com.microsoft.cmdpal.builtin.system.restart_shell"
]
},
```
will get us
<img width="840" height="197" alt="image"
src="https://github.com/user-attachments/assets/9ed19003-8361-4318-8dc9-055414456a51"
/>
Then it's just a matter of plumbing the command provider ID through the
layers, so that the command item knows who it is from. We'll need that
later for actually wiring this to the command's context menu.
related to #45191
related to #45201
2026-02-19 16:20:05 -06:00
|
|
|
|
|
|
|
|
|
|
CommandProviderContext GetProviderContextForCommand(object? command, CommandProviderContext? currentContext);
|
2025-07-15 12:21:44 -05:00
|
|
|
|
}
|