mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-21 18:49:56 +01:00
Compare commits
12 Commits
async-cpp-
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd9f523d5d | ||
|
|
de5fee2ca6 | ||
|
|
5da2e74622 | ||
|
|
d43a23c745 | ||
|
|
ffae061135 | ||
|
|
1e7c60ec23 | ||
|
|
2a5c61ce1f | ||
|
|
fdfffdc256 | ||
|
|
f249f99694 | ||
|
|
28c0d4a420 | ||
|
|
b0f6e0ae87 | ||
|
|
8cb518b649 |
@@ -2,8 +2,6 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Core.Common.Services;
|
namespace Microsoft.CmdPal.Core.Common.Services;
|
||||||
|
|
||||||
public interface IRunHistoryService
|
public interface IRunHistoryService
|
||||||
@@ -25,3 +23,12 @@ public interface IRunHistoryService
|
|||||||
/// <param name="item">The run history item to add.</param>
|
/// <param name="item">The run history item to add.</param>
|
||||||
void AddRunHistoryItem(string item);
|
void AddRunHistoryItem(string item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ITelemetryService
|
||||||
|
{
|
||||||
|
void LogRunQuery(string query, int resultCount, ulong durationMs);
|
||||||
|
|
||||||
|
void LogRunCommand(string command, bool asAdmin, bool success);
|
||||||
|
|
||||||
|
void LogOpenUri(string uri, bool isWeb, bool success);
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ public partial class App : Application
|
|||||||
|
|
||||||
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
||||||
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
|
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
|
||||||
services.AddSingleton(new TelemetryForwarder());
|
services.AddSingleton<ITelemetryService, TelemetryForwarder>();
|
||||||
|
|
||||||
// ViewModels
|
// ViewModels
|
||||||
services.AddSingleton<ShellViewModel>();
|
services.AddSingleton<ShellViewModel>();
|
||||||
|
|||||||
80
src/modules/cmdpal/Microsoft.CmdPal.UI/Events/RunEvents.cs
Normal file
80
src/modules/cmdpal/Microsoft.CmdPal.UI/Events/RunEvents.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
using Microsoft.PowerToys.Telemetry.Events;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.Events;
|
||||||
|
|
||||||
|
// Just put all the run events in one file for simplicity.
|
||||||
|
#pragma warning disable SA1402 // File may only contain a single type
|
||||||
|
#pragma warning disable SA1649 // File name should match first type name
|
||||||
|
|
||||||
|
[EventData]
|
||||||
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||||
|
public class CmdPalRunQuery : EventBase, IEvent
|
||||||
|
{
|
||||||
|
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||||
|
|
||||||
|
public string Query { get; set; }
|
||||||
|
|
||||||
|
public int ResultCount { get; set; }
|
||||||
|
|
||||||
|
public ulong DurationMs { get; set; }
|
||||||
|
|
||||||
|
public CmdPalRunQuery(string query, int resultCount, ulong durationMs)
|
||||||
|
{
|
||||||
|
EventName = "CmdPal_RunQuery";
|
||||||
|
Query = query;
|
||||||
|
ResultCount = resultCount;
|
||||||
|
DurationMs = durationMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[EventData]
|
||||||
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||||
|
public class CmdPalRunCommand : EventBase, IEvent
|
||||||
|
{
|
||||||
|
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||||
|
|
||||||
|
public string Command { get; set; }
|
||||||
|
|
||||||
|
public bool AsAdmin { get; set; }
|
||||||
|
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
public CmdPalRunCommand(string command, bool asAdmin, bool success)
|
||||||
|
{
|
||||||
|
EventName = "CmdPal_RunCommand";
|
||||||
|
Command = command;
|
||||||
|
AsAdmin = asAdmin;
|
||||||
|
Success = success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[EventData]
|
||||||
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||||
|
public class CmdPalOpenUri : EventBase, IEvent
|
||||||
|
{
|
||||||
|
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||||
|
|
||||||
|
public string Uri { get; set; }
|
||||||
|
|
||||||
|
public bool IsWeb { get; set; }
|
||||||
|
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
public CmdPalOpenUri(string uri, bool isWeb, bool success)
|
||||||
|
{
|
||||||
|
EventName = "CmdPal_OpenUri";
|
||||||
|
Uri = uri;
|
||||||
|
IsWeb = isWeb;
|
||||||
|
Success = success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1649 // File name should match first type name
|
||||||
|
#pragma warning restore SA1402 // File may only contain a single type
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.CmdPal.Core.Common.Services;
|
||||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||||
using Microsoft.CmdPal.UI.Events;
|
using Microsoft.CmdPal.UI.Events;
|
||||||
using Microsoft.PowerToys.Telemetry;
|
using Microsoft.PowerToys.Telemetry;
|
||||||
@@ -19,6 +20,7 @@ namespace Microsoft.CmdPal.UI;
|
|||||||
/// or something similar, but this works for now.
|
/// or something similar, but this works for now.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class TelemetryForwarder :
|
internal sealed class TelemetryForwarder :
|
||||||
|
ITelemetryService,
|
||||||
IRecipient<BeginInvokeMessage>,
|
IRecipient<BeginInvokeMessage>,
|
||||||
IRecipient<CmdPalInvokeResultMessage>
|
IRecipient<CmdPalInvokeResultMessage>
|
||||||
{
|
{
|
||||||
@@ -37,4 +39,19 @@ internal sealed class TelemetryForwarder :
|
|||||||
{
|
{
|
||||||
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
|
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void LogRunQuery(string query, int resultCount, ulong durationMs)
|
||||||
|
{
|
||||||
|
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunQuery(query, resultCount, durationMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogRunCommand(string command, bool asAdmin, bool success)
|
||||||
|
{
|
||||||
|
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunCommand(command, asAdmin, success));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogOpenUri(string uri, bool isWeb, bool success)
|
||||||
|
{
|
||||||
|
PowerToysTelemetry.Log.WriteEvent(new CmdPalOpenUri(uri, isWeb, success));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -83,7 +82,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
|||||||
var settings = Settings.CreateDefaultSettings();
|
var settings = Settings.CreateDefaultSettings();
|
||||||
var mockHistory = CreateMockHistoryService();
|
var mockHistory = CreateMockHistoryService();
|
||||||
|
|
||||||
var pages = new ShellListPage(settings, mockHistory.Object);
|
var pages = new ShellListPage(settings, mockHistory.Object, telemetryService: null);
|
||||||
|
|
||||||
await UpdatePageAndWaitForItems(pages, () =>
|
await UpdatePageAndWaitForItems(pages, () =>
|
||||||
{
|
{
|
||||||
@@ -115,7 +114,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
|||||||
var settings = Settings.CreateDefaultSettings();
|
var settings = Settings.CreateDefaultSettings();
|
||||||
var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
|
var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
|
||||||
|
|
||||||
var pages = new ShellListPage(settings, mockHistoryService.Object);
|
var pages = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
|
||||||
|
|
||||||
await UpdatePageAndWaitForItems(pages, () =>
|
await UpdatePageAndWaitForItems(pages, () =>
|
||||||
{
|
{
|
||||||
@@ -141,7 +140,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
|||||||
var settings = Settings.CreateDefaultSettings();
|
var settings = Settings.CreateDefaultSettings();
|
||||||
var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
|
var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
|
||||||
|
|
||||||
var pages = new ShellListPage(settings, mockHistoryService.Object);
|
var pages = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
|
||||||
|
|
||||||
await UpdatePageAndWaitForItems(pages, () =>
|
await UpdatePageAndWaitForItems(pages, () =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class ShellCommandProviderTests
|
|||||||
{
|
{
|
||||||
// Setup
|
// Setup
|
||||||
var mockHistoryService = new Mock<IRunHistoryService>();
|
var mockHistoryService = new Mock<IRunHistoryService>();
|
||||||
var provider = new ShellCommandsProvider(mockHistoryService.Object);
|
var provider = new ShellCommandsProvider(mockHistoryService.Object, telemetryService: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsNotNull(provider.DisplayName);
|
Assert.IsNotNull(provider.DisplayName);
|
||||||
@@ -28,7 +28,7 @@ public class ShellCommandProviderTests
|
|||||||
{
|
{
|
||||||
// Setup
|
// Setup
|
||||||
var mockHistoryService = new Mock<IRunHistoryService>();
|
var mockHistoryService = new Mock<IRunHistoryService>();
|
||||||
var provider = new ShellCommandsProvider(mockHistoryService.Object);
|
var provider = new ShellCommandsProvider(mockHistoryService.Object, telemetryService: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsNotNull(provider.Icon);
|
Assert.IsNotNull(provider.Icon);
|
||||||
@@ -39,7 +39,7 @@ public class ShellCommandProviderTests
|
|||||||
{
|
{
|
||||||
// Setup
|
// Setup
|
||||||
var mockHistoryService = new Mock<IRunHistoryService>();
|
var mockHistoryService = new Mock<IRunHistoryService>();
|
||||||
var provider = new ShellCommandsProvider(mockHistoryService.Object);
|
var provider = new ShellCommandsProvider(mockHistoryService.Object, telemetryService: null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var commands = provider.TopLevelCommands();
|
var commands = provider.TopLevelCommands();
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
|
||||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Shell.Commands;
|
|
||||||
|
|
||||||
internal sealed partial class ExecuteItem : InvokableCommand
|
|
||||||
{
|
|
||||||
private readonly ISettingsInterface _settings;
|
|
||||||
private readonly RunAsType _runas;
|
|
||||||
|
|
||||||
public string Cmd { get; internal set; } = string.Empty;
|
|
||||||
|
|
||||||
private static readonly char[] Separator = [' '];
|
|
||||||
|
|
||||||
public ExecuteItem(string cmd, ISettingsInterface settings, RunAsType type = RunAsType.None)
|
|
||||||
{
|
|
||||||
if (type == RunAsType.Administrator)
|
|
||||||
{
|
|
||||||
Name = Properties.Resources.cmd_run_as_administrator;
|
|
||||||
Icon = Icons.AdminIcon;
|
|
||||||
}
|
|
||||||
else if (type == RunAsType.OtherUser)
|
|
||||||
{
|
|
||||||
Name = Properties.Resources.cmd_run_as_user;
|
|
||||||
Icon = Icons.UserIcon;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Name = Properties.Resources.generic_run_command;
|
|
||||||
Icon = Icons.RunV2Icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cmd = cmd;
|
|
||||||
_settings = settings;
|
|
||||||
_runas = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Execute(Func<ProcessStartInfo, Process?> startProcess, ProcessStartInfo info)
|
|
||||||
{
|
|
||||||
if (startProcess is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
startProcess(info);
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException e)
|
|
||||||
{
|
|
||||||
var name = "Plugin: " + Properties.Resources.cmd_plugin_name;
|
|
||||||
var message = $"{Properties.Resources.cmd_command_not_found}: {e.Message}";
|
|
||||||
|
|
||||||
// GH TODO #138 -- show this message once that's wired up
|
|
||||||
// _context.API.ShowMsg(name, message);
|
|
||||||
}
|
|
||||||
catch (Win32Exception e)
|
|
||||||
{
|
|
||||||
var name = "Plugin: " + Properties.Resources.cmd_plugin_name;
|
|
||||||
var message = $"{Properties.Resources.cmd_command_failed}: {e.Message}";
|
|
||||||
ExtensionHost.LogMessage(new LogMessage() { Message = name + message });
|
|
||||||
|
|
||||||
// GH TODO #138 -- show this message once that's wired up
|
|
||||||
// _context.API.ShowMsg(name, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ProcessStartInfo SetProcessStartInfo(string fileName, string workingDirectory = "", string arguments = "", string verb = "")
|
|
||||||
{
|
|
||||||
var info = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = fileName,
|
|
||||||
WorkingDirectory = workingDirectory,
|
|
||||||
Arguments = arguments,
|
|
||||||
Verb = verb,
|
|
||||||
};
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProcessStartInfo PrepareProcessStartInfo(string command, RunAsType runAs = RunAsType.None)
|
|
||||||
{
|
|
||||||
command = Environment.ExpandEnvironmentVariables(command);
|
|
||||||
var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
|
||||||
|
|
||||||
// Set runAsArg
|
|
||||||
var runAsVerbArg = string.Empty;
|
|
||||||
if (runAs == RunAsType.OtherUser)
|
|
||||||
{
|
|
||||||
runAsVerbArg = "runAsUser";
|
|
||||||
}
|
|
||||||
else if (runAs == RunAsType.Administrator || _settings.RunAsAdministrator)
|
|
||||||
{
|
|
||||||
runAsVerbArg = "runAs";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Enum.TryParse<ExecutionShell>(_settings.ShellCommandExecution, out var executionShell))
|
|
||||||
{
|
|
||||||
ProcessStartInfo info;
|
|
||||||
if (executionShell == ExecutionShell.Cmd)
|
|
||||||
{
|
|
||||||
var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause";
|
|
||||||
|
|
||||||
info = SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsVerbArg);
|
|
||||||
}
|
|
||||||
else if (executionShell == ExecutionShell.Powershell)
|
|
||||||
{
|
|
||||||
var arguments = _settings.LeaveShellOpen
|
|
||||||
? $"-NoExit \"{command}\""
|
|
||||||
: $"\"{command} ; Read-Host -Prompt \\\"{Resources.run_plugin_cmd_wait_message}\\\"\"";
|
|
||||||
info = SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsVerbArg);
|
|
||||||
}
|
|
||||||
else if (executionShell == ExecutionShell.PowerShellSeven)
|
|
||||||
{
|
|
||||||
var arguments = _settings.LeaveShellOpen
|
|
||||||
? $"-NoExit -C \"{command}\""
|
|
||||||
: $"-C \"{command} ; Read-Host -Prompt \\\"{Resources.run_plugin_cmd_wait_message}\\\"\"";
|
|
||||||
info = SetProcessStartInfo("pwsh.exe", workingDirectory, arguments, runAsVerbArg);
|
|
||||||
}
|
|
||||||
else if (executionShell == ExecutionShell.WindowsTerminalCmd)
|
|
||||||
{
|
|
||||||
var arguments = _settings.LeaveShellOpen ? $"cmd.exe /k \"{command}\"" : $"cmd.exe /c \"{command}\" & pause";
|
|
||||||
info = SetProcessStartInfo("wt.exe", workingDirectory, arguments, runAsVerbArg);
|
|
||||||
}
|
|
||||||
else if (executionShell == ExecutionShell.WindowsTerminalPowerShell)
|
|
||||||
{
|
|
||||||
var arguments = _settings.LeaveShellOpen ? $"powershell -NoExit -C \"{command}\"" : $"powershell -C \"{command}\"";
|
|
||||||
info = SetProcessStartInfo("wt.exe", workingDirectory, arguments, runAsVerbArg);
|
|
||||||
}
|
|
||||||
else if (executionShell == ExecutionShell.WindowsTerminalPowerShellSeven)
|
|
||||||
{
|
|
||||||
var arguments = _settings.LeaveShellOpen ? $"pwsh.exe -NoExit -C \"{command}\"" : $"pwsh.exe -C \"{command}\"";
|
|
||||||
info = SetProcessStartInfo("wt.exe", workingDirectory, arguments, runAsVerbArg);
|
|
||||||
}
|
|
||||||
else if (executionShell == ExecutionShell.RunCommand)
|
|
||||||
{
|
|
||||||
// Open explorer if the path is a file or directory
|
|
||||||
if (Directory.Exists(command) || File.Exists(command))
|
|
||||||
{
|
|
||||||
info = SetProcessStartInfo("explorer.exe", arguments: command, verb: runAsVerbArg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var parts = command.Split(Separator, 2);
|
|
||||||
if (parts.Length == 2)
|
|
||||||
{
|
|
||||||
var filename = parts[0];
|
|
||||||
if (ShellListPageHelpers.FileExistInPath(filename))
|
|
||||||
{
|
|
||||||
var arguments = parts[1];
|
|
||||||
if (_settings.LeaveShellOpen)
|
|
||||||
{
|
|
||||||
// Wrap the command in a cmd.exe process
|
|
||||||
info = SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{filename} {arguments}\"", runAsVerbArg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info = SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_settings.LeaveShellOpen)
|
|
||||||
{
|
|
||||||
// Wrap the command in a cmd.exe process
|
|
||||||
info = SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info = SetProcessStartInfo(command, verb: runAsVerbArg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_settings.LeaveShellOpen)
|
|
||||||
{
|
|
||||||
// Wrap the command in a cmd.exe process
|
|
||||||
info = SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info = SetProcessStartInfo(command, verb: runAsVerbArg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
info.UseShellExecute = true;
|
|
||||||
|
|
||||||
_settings.AddCmdHistory(command);
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ExtensionHost.LogMessage(new LogMessage() { Message = "Error extracting setting" });
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override CommandResult Invoke()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Execute(Process.Start, PrepareProcessStartInfo(Cmd, _runas));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
ExtensionHost.LogMessage(new LogMessage() { Message = "Error starting the process " });
|
|
||||||
}
|
|
||||||
|
|
||||||
return CommandResult.Dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,7 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using Microsoft.CmdPal.Core.Common.Services;
|
||||||
using System.IO;
|
|
||||||
using Microsoft.CommandPalette.Extensions;
|
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Shell;
|
namespace Microsoft.CmdPal.Ext.Shell;
|
||||||
@@ -13,18 +11,33 @@ internal sealed partial class OpenUrlWithHistoryCommand : OpenUrlCommand
|
|||||||
{
|
{
|
||||||
private readonly Action<string>? _addToHistory;
|
private readonly Action<string>? _addToHistory;
|
||||||
private readonly string _url;
|
private readonly string _url;
|
||||||
|
private readonly ITelemetryService? _telemetryService;
|
||||||
|
|
||||||
public OpenUrlWithHistoryCommand(string url, Action<string>? addToHistory = null)
|
public OpenUrlWithHistoryCommand(string url, Action<string>? addToHistory = null, ITelemetryService? telemetryService = null)
|
||||||
: base(url)
|
: base(url)
|
||||||
{
|
{
|
||||||
_addToHistory = addToHistory;
|
_addToHistory = addToHistory;
|
||||||
_url = url;
|
_url = url;
|
||||||
|
_telemetryService = telemetryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override CommandResult Invoke()
|
public override CommandResult Invoke()
|
||||||
{
|
{
|
||||||
_addToHistory?.Invoke(_url);
|
_addToHistory?.Invoke(_url);
|
||||||
var result = base.Invoke();
|
|
||||||
return result;
|
var success = ShellHelpers.OpenInShell(_url);
|
||||||
|
var isWebUrl = false;
|
||||||
|
|
||||||
|
if (Uri.TryCreate(_url, UriKind.Absolute, out var uri))
|
||||||
|
{
|
||||||
|
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
|
||||||
|
{
|
||||||
|
isWebUrl = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_telemetryService?.LogOpenUri(_url, isWebUrl, success);
|
||||||
|
|
||||||
|
return CommandResult.Dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,9 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using Microsoft.CmdPal.Core.Common.Services;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Pages;
|
using Microsoft.CmdPal.Ext.Shell.Pages;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Shell;
|
namespace Microsoft.CmdPal.Ext.Shell;
|
||||||
@@ -19,18 +14,20 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
|||||||
private static readonly char[] _systemDirectoryRoots = ['\\', '/'];
|
private static readonly char[] _systemDirectoryRoots = ['\\', '/'];
|
||||||
|
|
||||||
private readonly Action<string>? _addToHistory;
|
private readonly Action<string>? _addToHistory;
|
||||||
|
private readonly ITelemetryService _telemetryService;
|
||||||
private CancellationTokenSource? _cancellationTokenSource;
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
private Task? _currentUpdateTask;
|
private Task? _currentUpdateTask;
|
||||||
|
|
||||||
public FallbackExecuteItem(SettingsManager settings, Action<string>? addToHistory)
|
public FallbackExecuteItem(SettingsManager settings, Action<string>? addToHistory, ITelemetryService telemetryService)
|
||||||
: base(
|
: base(
|
||||||
new NoOpCommand() { Id = "com.microsoft.run.fallback" },
|
new NoOpCommand() { Id = "com.microsoft.run.fallback" },
|
||||||
Resources.shell_command_display_title)
|
ResourceLoaderInstance.GetString("shell_command_display_title"))
|
||||||
{
|
{
|
||||||
Title = string.Empty;
|
Title = string.Empty;
|
||||||
Subtitle = Properties.Resources.generic_run_command;
|
Subtitle = ResourceLoaderInstance.GetString("generic_run_command");
|
||||||
Icon = Icons.RunV2Icon; // Defined in Icons.cs and contains the execute command icon.
|
Icon = Icons.RunV2Icon; // Defined in Icons.cs and contains the execute command icon.
|
||||||
_addToHistory = addToHistory;
|
_addToHistory = addToHistory;
|
||||||
|
_telemetryService = telemetryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateQuery(string query)
|
public override void UpdateQuery(string query)
|
||||||
@@ -147,7 +144,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
|||||||
if (exeExists)
|
if (exeExists)
|
||||||
{
|
{
|
||||||
// TODO we need to probably get rid of the settings for this provider entirely
|
// TODO we need to probably get rid of the settings for this provider entirely
|
||||||
var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, _addToHistory);
|
var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, _addToHistory, telemetryService: _telemetryService);
|
||||||
Title = exeItem.Title;
|
Title = exeItem.Title;
|
||||||
Subtitle = exeItem.Subtitle;
|
Subtitle = exeItem.Subtitle;
|
||||||
Icon = exeItem.Icon;
|
Icon = exeItem.Icon;
|
||||||
@@ -156,7 +153,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
|||||||
}
|
}
|
||||||
else if (pathIsDir)
|
else if (pathIsDir)
|
||||||
{
|
{
|
||||||
var pathItem = new PathListItem(exe, query, _addToHistory);
|
var pathItem = new PathListItem(exe, query, _addToHistory, _telemetryService);
|
||||||
Command = pathItem.Command;
|
Command = pathItem.Command;
|
||||||
MoreCommands = pathItem.MoreCommands;
|
MoreCommands = pathItem.MoreCommands;
|
||||||
Title = pathItem.Title;
|
Title = pathItem.Title;
|
||||||
@@ -165,7 +162,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
|||||||
}
|
}
|
||||||
else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
||||||
{
|
{
|
||||||
Command = new OpenUrlWithHistoryCommand(searchText, _addToHistory) { Result = CommandResult.Dismiss() };
|
Command = new OpenUrlWithHistoryCommand(searchText, _addToHistory, _telemetryService) { Result = CommandResult.Dismiss() };
|
||||||
Title = searchText;
|
Title = searchText;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.Storage.FileSystem;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||||
|
|
||||||
@@ -19,38 +19,11 @@ namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
|||||||
public static class CommandLineNormalizer
|
public static class CommandLineNormalizer
|
||||||
{
|
{
|
||||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||||
private const int MAX_PATH = 260;
|
|
||||||
private const uint INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF;
|
private const uint INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF;
|
||||||
private const uint FILE_ATTRIBUTE_DIRECTORY = 0x10;
|
|
||||||
|
private const int MAX_PATH = 260;
|
||||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
||||||
private static extern uint ExpandEnvironmentStringsW(
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string lpSrc,
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpDst,
|
|
||||||
uint nSize);
|
|
||||||
|
|
||||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
||||||
private static extern IntPtr CommandLineToArgvW(
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine,
|
|
||||||
out int pNumArgs);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
||||||
private static extern uint SearchPathW(
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string? lpPath,
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string? lpExtension,
|
|
||||||
uint nBufferLength,
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpBuffer,
|
|
||||||
out IntPtr lpFilePart);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
||||||
private static extern uint GetFileAttributesW(
|
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll")]
|
|
||||||
private static extern IntPtr LocalFree(IntPtr hMem);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normalizes a command line string by expanding environment variables, resolving executable paths,
|
/// Normalizes a command line string by expanding environment variables, resolving executable paths,
|
||||||
/// and standardizing the format for comparison purposes.
|
/// and standardizing the format for comparison purposes.
|
||||||
@@ -129,9 +102,9 @@ public static class CommandLineNormalizer
|
|||||||
private static string ExpandEnvironmentVariables(string input)
|
private static string ExpandEnvironmentVariables(string input)
|
||||||
{
|
{
|
||||||
const int initialBufferSize = 1024;
|
const int initialBufferSize = 1024;
|
||||||
var buffer = new StringBuilder(initialBufferSize);
|
var buffer = new char[initialBufferSize];
|
||||||
|
|
||||||
var result = ExpandEnvironmentStringsW(input, buffer, (uint)buffer.Capacity);
|
var result = PInvoke.ExpandEnvironmentStrings(input, buffer);
|
||||||
|
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
{
|
{
|
||||||
@@ -139,11 +112,11 @@ public static class CommandLineNormalizer
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result > buffer.Capacity)
|
if (result > buffer.Length)
|
||||||
{
|
{
|
||||||
// Buffer was too small, resize and try again
|
// Buffer was too small, resize and try again
|
||||||
buffer.Capacity = (int)result;
|
buffer = new char[result];
|
||||||
result = ExpandEnvironmentStringsW(input, buffer, (uint)buffer.Capacity);
|
result = PInvoke.ExpandEnvironmentStrings(input, buffer);
|
||||||
|
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
{
|
{
|
||||||
@@ -151,7 +124,7 @@ public static class CommandLineNormalizer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.ToString();
|
return new string(buffer, 0, (int)result - 1); // -1 to exclude null terminator
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -159,28 +132,30 @@ public static class CommandLineNormalizer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static string[] ParseCommandLineToArguments(string commandLine)
|
private static string[] ParseCommandLineToArguments(string commandLine)
|
||||||
{
|
{
|
||||||
var argv = CommandLineToArgvW(commandLine, out var argc);
|
unsafe
|
||||||
|
|
||||||
if (argv == IntPtr.Zero || argc == 0)
|
|
||||||
{
|
{
|
||||||
return Array.Empty<string>();
|
var argv = PInvoke.CommandLineToArgv(commandLine, out var argc);
|
||||||
}
|
|
||||||
|
|
||||||
try
|
if (argv == null || argc == 0)
|
||||||
{
|
|
||||||
var args = new string[argc];
|
|
||||||
|
|
||||||
for (var i = 0; i < argc; i++)
|
|
||||||
{
|
{
|
||||||
var argPtr = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
|
return Array.Empty<string>();
|
||||||
args[i] = Marshal.PtrToStringUni(argPtr) ?? string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
try
|
||||||
}
|
{
|
||||||
finally
|
var args = new string[argc];
|
||||||
{
|
|
||||||
LocalFree(argv);
|
for (var i = 0; i < argc; i++)
|
||||||
|
{
|
||||||
|
args[i] = new string(argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PInvoke.LocalFree(new HLOCAL(argv));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,39 +202,46 @@ public static class CommandLineNormalizer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static string TryResolveExecutable(string executableName)
|
private static string TryResolveExecutable(string executableName)
|
||||||
{
|
{
|
||||||
var buffer = new StringBuilder(MAX_PATH);
|
var buffer = new char[MAX_PATH];
|
||||||
|
|
||||||
var result = SearchPathW(
|
unsafe
|
||||||
null, // Use default search path
|
|
||||||
executableName,
|
|
||||||
".exe", // Default extension
|
|
||||||
(uint)buffer.Capacity,
|
|
||||||
buffer,
|
|
||||||
out var _); // We don't need the file part
|
|
||||||
|
|
||||||
if (result == 0)
|
|
||||||
{
|
{
|
||||||
return string.Empty;
|
var outParam = default(PWSTR); // ultimately discarded
|
||||||
}
|
|
||||||
|
|
||||||
if (result > buffer.Capacity)
|
var result = PInvoke.SearchPath(
|
||||||
{
|
null, // Use default search path
|
||||||
// Buffer was too small, resize and try again
|
executableName,
|
||||||
buffer.Capacity = (int)result;
|
".exe", // Default extension
|
||||||
result = SearchPathW(null, executableName, ".exe", (uint)buffer.Capacity, buffer, out var _);
|
buffer,
|
||||||
|
&outParam); // We don't need the file part
|
||||||
|
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result > buffer.Length)
|
||||||
|
{
|
||||||
|
// Buffer was too small, resize and try again
|
||||||
|
buffer = new char[result];
|
||||||
|
result = PInvoke.SearchPath(null, executableName, ".exe", buffer, &outParam);
|
||||||
|
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedPath = new string(buffer, 0, (int)result);
|
||||||
|
|
||||||
|
// Verify the resolved path exists and is not a directory
|
||||||
|
var attributes = PInvoke.GetFileAttributes(resolvedPath);
|
||||||
|
|
||||||
|
return attributes == INVALID_FILE_ATTRIBUTES ||
|
||||||
|
(attributes & (uint)FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_DIRECTORY) != 0 ?
|
||||||
|
string.Empty :
|
||||||
|
resolvedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolvedPath = buffer.ToString();
|
|
||||||
|
|
||||||
// Verify the resolved path exists and is not a directory
|
|
||||||
var attributes = GetFileAttributesW(resolvedPath);
|
|
||||||
|
|
||||||
return attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ? string.Empty : resolvedPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -2,13 +2,7 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using Microsoft.CmdPal.Core.Common.Services;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.CmdPal.Ext.Shell.Commands;
|
|
||||||
using Microsoft.CmdPal.Ext.Shell.Pages;
|
using Microsoft.CmdPal.Ext.Shell.Pages;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
@@ -16,37 +10,6 @@ namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
|||||||
|
|
||||||
public class ShellListPageHelpers
|
public class ShellListPageHelpers
|
||||||
{
|
{
|
||||||
private static readonly CompositeFormat CmdHasBeenExecutedTimes = System.Text.CompositeFormat.Parse(Properties.Resources.cmd_has_been_executed_times);
|
|
||||||
private readonly ISettingsInterface _settings;
|
|
||||||
|
|
||||||
public ShellListPageHelpers(ISettingsInterface settings)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ListItem GetCurrentCmd(string cmd)
|
|
||||||
{
|
|
||||||
var result = new ListItem(new ExecuteItem(cmd, _settings))
|
|
||||||
{
|
|
||||||
Title = cmd,
|
|
||||||
Subtitle = Properties.Resources.cmd_plugin_name + ": " + Properties.Resources.cmd_execute_through_shell,
|
|
||||||
Icon = new IconInfo(string.Empty),
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CommandContextItem> LoadContextMenus(ListItem listItem)
|
|
||||||
{
|
|
||||||
var resultList = new List<CommandContextItem>
|
|
||||||
{
|
|
||||||
new(new ExecuteItem(listItem.Title, _settings, RunAsType.Administrator)),
|
|
||||||
new(new ExecuteItem(listItem.Title, _settings, RunAsType.OtherUser )),
|
|
||||||
};
|
|
||||||
|
|
||||||
return resultList;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool FileExistInPath(string filename)
|
internal static bool FileExistInPath(string filename)
|
||||||
{
|
{
|
||||||
return FileExistInPath(filename, out var _);
|
return FileExistInPath(filename, out var _);
|
||||||
@@ -58,7 +21,7 @@ public class ShellListPageHelpers
|
|||||||
return ShellHelpers.FileExistInPath(filename, out fullPath, token ?? CancellationToken.None);
|
return ShellHelpers.FileExistInPath(filename, out fullPath, token ?? CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ListItem? ListItemForCommandString(string query, Action<string>? addToHistory)
|
internal static ListItem? ListItemForCommandString(string query, Action<string>? addToHistory, ITelemetryService? telemetryService)
|
||||||
{
|
{
|
||||||
var li = new ListItem();
|
var li = new ListItem();
|
||||||
|
|
||||||
@@ -100,7 +63,7 @@ public class ShellListPageHelpers
|
|||||||
if (exeExists)
|
if (exeExists)
|
||||||
{
|
{
|
||||||
// TODO we need to probably get rid of the settings for this provider entirely
|
// TODO we need to probably get rid of the settings for this provider entirely
|
||||||
var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, addToHistory);
|
var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, addToHistory, telemetryService);
|
||||||
li.Command = exeItem.Command;
|
li.Command = exeItem.Command;
|
||||||
li.Title = exeItem.Title;
|
li.Title = exeItem.Title;
|
||||||
li.Subtitle = exeItem.Subtitle;
|
li.Subtitle = exeItem.Subtitle;
|
||||||
@@ -109,7 +72,7 @@ public class ShellListPageHelpers
|
|||||||
}
|
}
|
||||||
else if (pathIsDir)
|
else if (pathIsDir)
|
||||||
{
|
{
|
||||||
var pathItem = new PathListItem(exe, query, addToHistory);
|
var pathItem = new PathListItem(exe, query, addToHistory, telemetryService);
|
||||||
li.Command = pathItem.Command;
|
li.Command = pathItem.Command;
|
||||||
li.Title = pathItem.Title;
|
li.Title = pathItem.Title;
|
||||||
li.Subtitle = pathItem.Subtitle;
|
li.Subtitle = pathItem.Subtitle;
|
||||||
@@ -118,7 +81,7 @@ public class ShellListPageHelpers
|
|||||||
}
|
}
|
||||||
else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
||||||
{
|
{
|
||||||
li.Command = new OpenUrlWithHistoryCommand(searchText) { Result = CommandResult.Dismiss() };
|
li.Command = new OpenUrlWithHistoryCommand(searchText, addToHistory, telemetryService) { Result = CommandResult.Dismiss() };
|
||||||
li.Title = searchText;
|
li.Title = searchText;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
<Import Project="..\..\CoreCommonProps.props" />
|
||||||
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
|
|
||||||
|
|
||||||
<Import Project="..\Common.ExtDependencies.props" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<RootNamespace>Microsoft.CmdPal.Ext.Shell</RootNamespace>
|
<RootNamespace>Microsoft.CmdPal.Ext.Shell</RootNamespace>
|
||||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
@@ -16,7 +12,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Core\Microsoft.CmdPal.Core.Common\Microsoft.CmdPal.Core.Common.csproj" />
|
<ProjectReference Include="..\..\Core\Microsoft.CmdPal.Core.Common\Microsoft.CmdPal.Core.Common.csproj" />
|
||||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.CommandLine" />
|
<PackageReference Include="System.CommandLine" />
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||||
|
"allowMarshaling": false,
|
||||||
|
"comInterop": {
|
||||||
|
"preserveSigMethods": [ "*" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
GetCurrentPackageFullName
|
||||||
|
SetWindowLong
|
||||||
|
GetWindowLong
|
||||||
|
WINDOW_EX_STYLE
|
||||||
|
SFBS_FLAGS
|
||||||
|
MAX_PATH
|
||||||
|
GetDpiForWindow
|
||||||
|
GetWindowRect
|
||||||
|
GetMonitorInfo
|
||||||
|
SetWindowPos
|
||||||
|
MonitorFromWindow
|
||||||
|
|
||||||
|
SHOW_WINDOW_CMD
|
||||||
|
ShellExecuteEx
|
||||||
|
SEE_MASK_INVOKEIDLIST
|
||||||
|
|
||||||
|
ExpandEnvironmentStringsW
|
||||||
|
CommandLineToArgvW
|
||||||
|
SearchPathW
|
||||||
|
GetFileAttributesW
|
||||||
|
LocalFree
|
||||||
|
FILE_FLAGS_AND_ATTRIBUTES
|
||||||
@@ -2,9 +2,8 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.CmdPal.Core.Common.Commands;
|
using Microsoft.CmdPal.Core.Common.Commands;
|
||||||
|
using Microsoft.CmdPal.Core.Common.Services;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.System;
|
using Windows.System;
|
||||||
@@ -13,13 +12,18 @@ namespace Microsoft.CmdPal.Ext.Shell;
|
|||||||
|
|
||||||
internal sealed partial class PathListItem : ListItem
|
internal sealed partial class PathListItem : ListItem
|
||||||
{
|
{
|
||||||
private readonly Lazy<IconInfo> _icon;
|
private readonly Lazy<bool> fetchedIcon;
|
||||||
private readonly bool _isDirectory;
|
private readonly bool isDirectory;
|
||||||
|
private readonly string path;
|
||||||
|
|
||||||
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
public override IIconInfo? Icon { get => fetchedIcon.Value ? _icon : _icon; set => base.Icon = value; }
|
||||||
|
|
||||||
public PathListItem(string path, string originalDir, Action<string>? addToHistory)
|
private IIconInfo? _icon;
|
||||||
: base(new OpenUrlWithHistoryCommand(path, addToHistory))
|
|
||||||
|
internal bool IsDirectory => isDirectory;
|
||||||
|
|
||||||
|
public PathListItem(string path, string originalDir, Action<string>? addToHistory, ITelemetryService? telemetryService = null)
|
||||||
|
: base(new OpenUrlWithHistoryCommand(path, addToHistory, telemetryService))
|
||||||
{
|
{
|
||||||
var fileName = Path.GetFileName(path);
|
var fileName = Path.GetFileName(path);
|
||||||
if (string.IsNullOrEmpty(fileName))
|
if (string.IsNullOrEmpty(fileName))
|
||||||
@@ -27,8 +31,8 @@ internal sealed partial class PathListItem : ListItem
|
|||||||
fileName = Path.GetFileName(Path.GetDirectoryName(path)) ?? string.Empty;
|
fileName = Path.GetFileName(Path.GetDirectoryName(path)) ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isDirectory = Directory.Exists(path);
|
isDirectory = Directory.Exists(path);
|
||||||
if (_isDirectory)
|
if (isDirectory)
|
||||||
{
|
{
|
||||||
if (!path.EndsWith('\\'))
|
if (!path.EndsWith('\\'))
|
||||||
{
|
{
|
||||||
@@ -41,6 +45,8 @@ internal sealed partial class PathListItem : ListItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.path = path;
|
||||||
|
|
||||||
Title = fileName; // Just the name of the file is the Title
|
Title = fileName; // Just the name of the file is the Title
|
||||||
Subtitle = path; // What the user typed is the subtitle
|
Subtitle = path; // What the user typed is the subtitle
|
||||||
|
|
||||||
@@ -58,23 +64,35 @@ internal sealed partial class PathListItem : ListItem
|
|||||||
// wrap it in quotes
|
// wrap it in quotes
|
||||||
suggestion = string.Concat("\"", suggestion, "\"");
|
suggestion = string.Concat("\"", suggestion, "\"");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
suggestion = path;
|
||||||
|
}
|
||||||
|
|
||||||
TextToSuggest = suggestion;
|
TextToSuggest = suggestion;
|
||||||
|
|
||||||
MoreCommands = [
|
MoreCommands = [
|
||||||
new CommandContextItem(new OpenWithCommand(path)),
|
new CommandContextItem(new OpenWithCommand(path)),
|
||||||
new CommandContextItem(new ShowFileInFolderCommand(path)) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E) },
|
new CommandContextItem(new ShowFileInFolderCommand(path)) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E) },
|
||||||
new CommandContextItem(new CopyPathCommand(path) { Name = Properties.Resources.copy_path_command_name }) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C) },
|
new CommandContextItem(new CopyPathCommand(path)) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C) },
|
||||||
new CommandContextItem(new OpenInConsoleCommand(path)) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R) },
|
new CommandContextItem(new OpenInConsoleCommand(path)) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R) },
|
||||||
new CommandContextItem(new OpenPropertiesCommand(path)),
|
new CommandContextItem(new OpenPropertiesCommand(path)),
|
||||||
];
|
];
|
||||||
|
|
||||||
_icon = new Lazy<IconInfo>(() =>
|
fetchedIcon = new Lazy<bool>(() =>
|
||||||
{
|
{
|
||||||
var iconStream = ThumbnailHelper.GetThumbnail(path).Result;
|
_ = Task.Run(FetchIconAsync);
|
||||||
var icon = iconStream is not null ? IconInfo.FromStream(iconStream) :
|
return true;
|
||||||
_isDirectory ? Icons.FolderIcon : Icons.RunV2Icon;
|
|
||||||
return icon;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task FetchIconAsync()
|
||||||
|
{
|
||||||
|
var iconStream = await ThumbnailHelper.GetThumbnail(path);
|
||||||
|
var icon = iconStream != null ?
|
||||||
|
IconInfo.FromStream(iconStream) :
|
||||||
|
isDirectory ? Icons.FolderIcon : Icons.RunV2Icon;
|
||||||
|
_icon = icon;
|
||||||
|
OnPropertyChanged(nameof(Icon));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using Microsoft.CmdPal.Core.Common.Services;
|
||||||
using System.Threading.Tasks;
|
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.Storage.Streams;
|
using Windows.Storage.Streams;
|
||||||
@@ -15,6 +15,7 @@ internal sealed partial class RunExeItem : ListItem
|
|||||||
{
|
{
|
||||||
private readonly Lazy<IconInfo> _icon;
|
private readonly Lazy<IconInfo> _icon;
|
||||||
private readonly Action<string>? _addToHistory;
|
private readonly Action<string>? _addToHistory;
|
||||||
|
private readonly ITelemetryService? _telemetryService;
|
||||||
|
|
||||||
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
||||||
|
|
||||||
@@ -26,13 +27,18 @@ internal sealed partial class RunExeItem : ListItem
|
|||||||
|
|
||||||
private string FullString => string.IsNullOrEmpty(_args) ? Exe : $"{Exe} {_args}";
|
private string FullString => string.IsNullOrEmpty(_args) ? Exe : $"{Exe} {_args}";
|
||||||
|
|
||||||
public RunExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory)
|
public RunExeItem(
|
||||||
|
string exe,
|
||||||
|
string args,
|
||||||
|
string fullExePath,
|
||||||
|
Action<string>? addToHistory,
|
||||||
|
ITelemetryService? telemetryService = null)
|
||||||
{
|
{
|
||||||
FullExePath = fullExePath;
|
FullExePath = fullExePath;
|
||||||
Exe = exe;
|
Exe = exe;
|
||||||
var command = new AnonymousCommand(Run)
|
var command = new AnonymousCommand(Run)
|
||||||
{
|
{
|
||||||
Name = Properties.Resources.generic_run_command,
|
Name = ResourceLoaderInstance.GetString("generic_run_command"),
|
||||||
Result = CommandResult.Dismiss(),
|
Result = CommandResult.Dismiss(),
|
||||||
};
|
};
|
||||||
Command = command;
|
Command = command;
|
||||||
@@ -46,6 +52,7 @@ internal sealed partial class RunExeItem : ListItem
|
|||||||
});
|
});
|
||||||
|
|
||||||
_addToHistory = addToHistory;
|
_addToHistory = addToHistory;
|
||||||
|
_telemetryService = telemetryService;
|
||||||
|
|
||||||
UpdateArgs(args);
|
UpdateArgs(args);
|
||||||
|
|
||||||
@@ -53,13 +60,13 @@ internal sealed partial class RunExeItem : ListItem
|
|||||||
new CommandContextItem(
|
new CommandContextItem(
|
||||||
new AnonymousCommand(RunAsAdmin)
|
new AnonymousCommand(RunAsAdmin)
|
||||||
{
|
{
|
||||||
Name = Properties.Resources.cmd_run_as_administrator,
|
Name = ResourceLoaderInstance.GetString("cmd_run_as_administrator"),
|
||||||
Icon = Icons.AdminIcon,
|
Icon = Icons.AdminIcon,
|
||||||
}) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter) },
|
}) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter) },
|
||||||
new CommandContextItem(
|
new CommandContextItem(
|
||||||
new AnonymousCommand(RunAsOther)
|
new AnonymousCommand(RunAsOther)
|
||||||
{
|
{
|
||||||
Name = Properties.Resources.cmd_run_as_user,
|
Name = ResourceLoaderInstance.GetString("cmd_run_as_user"),
|
||||||
Icon = Icons.UserIcon,
|
Icon = Icons.UserIcon,
|
||||||
}) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.U) },
|
}) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.U) },
|
||||||
];
|
];
|
||||||
@@ -97,20 +104,26 @@ internal sealed partial class RunExeItem : ListItem
|
|||||||
{
|
{
|
||||||
_addToHistory?.Invoke(FullString);
|
_addToHistory?.Invoke(FullString);
|
||||||
|
|
||||||
ShellHelpers.OpenInShell(FullExePath, _args);
|
var success = ShellHelpers.OpenInShell(FullExePath, _args);
|
||||||
|
|
||||||
|
_telemetryService?.LogRunCommand(FullString, false, success);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunAsAdmin()
|
public void RunAsAdmin()
|
||||||
{
|
{
|
||||||
_addToHistory?.Invoke(FullString);
|
_addToHistory?.Invoke(FullString);
|
||||||
|
|
||||||
ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.Administrator);
|
var success = ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.Administrator);
|
||||||
|
|
||||||
|
_telemetryService?.LogRunCommand(FullString, true, success);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunAsOther()
|
public void RunAsOther()
|
||||||
{
|
{
|
||||||
_addToHistory?.Invoke(FullString);
|
_addToHistory?.Invoke(FullString);
|
||||||
|
|
||||||
ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.OtherUser);
|
var success = ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.OtherUser);
|
||||||
|
|
||||||
|
_telemetryService?.LogRunCommand(FullString, false, success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,8 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.CmdPal.Core.Common.Services;
|
using Microsoft.CmdPal.Core.Common.Services;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
@@ -18,13 +11,13 @@ namespace Microsoft.CmdPal.Ext.Shell.Pages;
|
|||||||
|
|
||||||
internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ShellListPageHelpers _helper;
|
|
||||||
|
|
||||||
private readonly List<ListItem> _topLevelItems = [];
|
|
||||||
private readonly Dictionary<string, ListItem> _historyItems = [];
|
private readonly Dictionary<string, ListItem> _historyItems = [];
|
||||||
private readonly List<ListItem> _currentHistoryItems = [];
|
private readonly List<ListItem> _currentHistoryItems = [];
|
||||||
|
|
||||||
private readonly IRunHistoryService _historyService;
|
private readonly IRunHistoryService _historyService;
|
||||||
|
private readonly ITelemetryService? _telemetryService;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, ListItem> _currentPathItems = new();
|
||||||
|
|
||||||
private ListItem? _exeItem;
|
private ListItem? _exeItem;
|
||||||
private List<ListItem> _pathItems = [];
|
private List<ListItem> _pathItems = [];
|
||||||
@@ -35,27 +28,26 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
|
|
||||||
private bool _loadedInitialHistory;
|
private bool _loadedInitialHistory;
|
||||||
|
|
||||||
public ShellListPage(ISettingsInterface settingsManager, IRunHistoryService runHistoryService, bool addBuiltins = false)
|
private string _currentSubdir = string.Empty;
|
||||||
|
|
||||||
|
public ShellListPage(
|
||||||
|
ISettingsInterface settingsManager,
|
||||||
|
IRunHistoryService runHistoryService,
|
||||||
|
ITelemetryService? telemetryService)
|
||||||
{
|
{
|
||||||
Icon = Icons.RunV2Icon;
|
Icon = Icons.RunV2Icon;
|
||||||
Id = "com.microsoft.cmdpal.shell";
|
Id = "com.microsoft.cmdpal.shell";
|
||||||
Name = Resources.cmd_plugin_name;
|
Name = ResourceLoaderInstance.GetString("cmd_plugin_name");
|
||||||
PlaceholderText = Resources.list_placeholder_text;
|
PlaceholderText = ResourceLoaderInstance.GetString("list_placeholder_text");
|
||||||
_helper = new(settingsManager);
|
|
||||||
_historyService = runHistoryService;
|
_historyService = runHistoryService;
|
||||||
|
_telemetryService = telemetryService;
|
||||||
|
|
||||||
EmptyContent = new CommandItem()
|
EmptyContent = new CommandItem()
|
||||||
{
|
{
|
||||||
Title = Resources.cmd_plugin_name,
|
Title = ResourceLoaderInstance.GetString("cmd_plugin_name"),
|
||||||
Icon = Icons.RunV2Icon,
|
Icon = Icons.RunV2Icon,
|
||||||
Subtitle = Resources.list_placeholder_text,
|
Subtitle = ResourceLoaderInstance.GetString("list_placeholder_text"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (addBuiltins)
|
|
||||||
{
|
|
||||||
// here, we _could_ add built-in providers if we wanted. links to apps, calc, etc.
|
|
||||||
// That would be a truly run-first experience
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||||
@@ -123,8 +115,13 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
|
|
||||||
private async Task BuildListItemsForSearchAsync(string newSearch, CancellationToken cancellationToken)
|
private async Task BuildListItemsForSearchAsync(string newSearch, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var timer = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
|
||||||
// Check for cancellation at the start
|
// Check for cancellation at the start
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the search text is the start of a path to a file (it might be a
|
// If the search text is the start of a path to a file (it might be a
|
||||||
// UNC path), then we want to list all the files that start with that text:
|
// UNC path), then we want to list all the files that start with that text:
|
||||||
@@ -136,7 +133,10 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
var expanded = Environment.ExpandEnvironmentVariables(searchText);
|
var expanded = Environment.ExpandEnvironmentVariables(searchText);
|
||||||
|
|
||||||
// Check for cancellation after environment expansion
|
// Check for cancellation after environment expansion
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO we can be smarter about only re-reading the filesystem if the
|
// TODO we can be smarter about only re-reading the filesystem if the
|
||||||
// new search is just the oldSearch+some chars
|
// new search is just the oldSearch+some chars
|
||||||
@@ -206,7 +206,10 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
couldResolvePath = false;
|
couldResolvePath = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_pathItems.Clear();
|
_pathItems.Clear();
|
||||||
|
|
||||||
@@ -221,7 +224,10 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for cancellation before creating exe items
|
// Check for cancellation before creating exe items
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (couldResolvePath && exeExists)
|
if (couldResolvePath && exeExists)
|
||||||
{
|
{
|
||||||
@@ -278,17 +284,31 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
_currentHistoryItems.AddRange(filteredHistory);
|
_currentHistoryItems.AddRange(filteredHistory);
|
||||||
|
|
||||||
// Final cancellation check
|
// Final cancellation check
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.Stop();
|
||||||
|
_telemetryService?.LogRunQuery(newSearch, GetItems().Length, (ulong)timer.ElapsedMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ListItem PathToListItem(string path, string originalPath, string args = "", Action<string>? addToHistory = null)
|
private static ListItem PathToListItem(string path, string originalPath, string args = "", Action<string>? addToHistory = null, ITelemetryService? telemetryService = null)
|
||||||
{
|
{
|
||||||
var pathItem = new PathListItem(path, originalPath, addToHistory);
|
var pathItem = new PathListItem(path, originalPath, addToHistory, telemetryService);
|
||||||
|
|
||||||
|
if (pathItem.IsDirectory)
|
||||||
|
{
|
||||||
|
return pathItem;
|
||||||
|
}
|
||||||
|
|
||||||
// Is this path an executable? If so, then make a RunExeItem
|
// Is this path an executable? If so, then make a RunExeItem
|
||||||
if (IsExecutable(path))
|
if (IsExecutable(path))
|
||||||
{
|
{
|
||||||
var exeItem = new RunExeItem(Path.GetFileName(path), args, path, addToHistory);
|
var exeItem = new RunExeItem(Path.GetFileName(path), args, path, addToHistory, telemetryService)
|
||||||
|
{
|
||||||
|
TextToSuggest = path,
|
||||||
|
};
|
||||||
|
|
||||||
exeItem.MoreCommands = [
|
exeItem.MoreCommands = [
|
||||||
.. exeItem.MoreCommands,
|
.. exeItem.MoreCommands,
|
||||||
@@ -306,24 +326,22 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
LoadInitialHistory();
|
LoadInitialHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
var filteredTopLevel = ListHelpers.FilterList(_topLevelItems, SearchText);
|
|
||||||
List<ListItem> uriItems = _uriItem is not null ? [_uriItem] : [];
|
List<ListItem> uriItems = _uriItem is not null ? [_uriItem] : [];
|
||||||
List<ListItem> exeItems = _exeItem is not null ? [_exeItem] : [];
|
List<ListItem> exeItems = _exeItem is not null ? [_exeItem] : [];
|
||||||
|
|
||||||
return
|
return
|
||||||
exeItems
|
exeItems
|
||||||
.Concat(filteredTopLevel)
|
|
||||||
.Concat(_currentHistoryItems)
|
.Concat(_currentHistoryItems)
|
||||||
.Concat(_pathItems)
|
.Concat(_pathItems)
|
||||||
.Concat(uriItems)
|
.Concat(uriItems)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ListItem CreateExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory)
|
internal static ListItem CreateExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory, ITelemetryService? telemetryService)
|
||||||
{
|
{
|
||||||
// PathToListItem will return a RunExeItem if it can find a executable.
|
// PathToListItem will return a RunExeItem if it can find a executable.
|
||||||
// It will ALSO add the file search commands to the RunExeItem.
|
// It will ALSO add the file search commands to the RunExeItem.
|
||||||
return PathToListItem(fullExePath, exe, args, addToHistory);
|
return PathToListItem(fullExePath, exe, args, addToHistory, telemetryService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateAndAddExeItems(string exe, string args, string fullExePath)
|
private void CreateAndAddExeItems(string exe, string args, string fullExePath)
|
||||||
@@ -335,7 +353,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_exeItem = CreateExeItem(exe, args, fullExePath, AddToHistory);
|
_exeItem = CreateExeItem(exe, args, fullExePath, AddToHistory, _telemetryService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +407,10 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for cancellation before directory operations
|
// Check for cancellation before directory operations
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var dirExists = Directory.Exists(directoryPath);
|
var dirExists = Directory.Exists(directoryPath);
|
||||||
|
|
||||||
@@ -408,30 +429,71 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
if (dirExists)
|
if (dirExists)
|
||||||
{
|
{
|
||||||
// Check for cancellation before file system enumeration
|
// Check for cancellation before file system enumeration
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directoryPath == _currentSubdir)
|
||||||
|
{
|
||||||
|
// Filter the items we already had
|
||||||
|
var fuzzyString = searchPattern.TrimEnd('*');
|
||||||
|
var newMatchedPathItems = new List<ListItem>();
|
||||||
|
|
||||||
|
foreach (var kv in _currentPathItems)
|
||||||
|
{
|
||||||
|
var score = string.IsNullOrEmpty(fuzzyString) ?
|
||||||
|
1 :
|
||||||
|
FuzzyStringMatcher.ScoreFuzzy(fuzzyString, kv.Key);
|
||||||
|
if (score > 0)
|
||||||
|
{
|
||||||
|
newMatchedPathItems.Add(kv.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListHelpers.InPlaceUpdateList(_pathItems, newMatchedPathItems);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get all the files in the directory that start with the search text
|
// Get all the files in the directory that start with the search text
|
||||||
// Run this on a background thread to avoid blocking
|
// Run this on a background thread to avoid blocking
|
||||||
var files = await Task.Run(() => Directory.GetFileSystemEntries(directoryPath, searchPattern), cancellationToken);
|
var files = await Task.Run(() => Directory.GetFileSystemEntries(directoryPath, searchPattern), cancellationToken);
|
||||||
|
|
||||||
// Check for cancellation after file enumeration
|
// Check for cancellation after file enumeration
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var searchPathTrailer = trimmed.Remove(0, Math.Min(directoryPath.Length, trimmed.Length));
|
var searchPathTrailer = trimmed.Remove(0, Math.Min(directoryPath.Length, trimmed.Length));
|
||||||
var originalBeginning = originalPath.Remove(originalPath.Length - searchPathTrailer.Length);
|
var originalBeginning = originalPath.EndsWith(searchPathTrailer, StringComparison.CurrentCultureIgnoreCase) ?
|
||||||
|
originalPath.Remove(originalPath.Length - searchPathTrailer.Length) :
|
||||||
|
originalPath;
|
||||||
|
|
||||||
if (isDriveRoot)
|
if (isDriveRoot)
|
||||||
{
|
{
|
||||||
originalBeginning = string.Concat(originalBeginning, '\\');
|
originalBeginning = string.Concat(originalBeginning, '\\');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a list of commands for each file
|
// Create a list of commands for each file
|
||||||
var commands = files.Select(f => PathToListItem(f, originalBeginning)).ToList();
|
var newPathItems = files
|
||||||
|
.Select(f => PathToListItem(f, originalBeginning))
|
||||||
|
.ToDictionary(item => item.Title, item => item);
|
||||||
|
|
||||||
// Final cancellation check before updating results
|
// Final cancellation check before updating results
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Add the commands to the list
|
// Add the commands to the list
|
||||||
_pathItems = commands;
|
_pathItems = newPathItems.Values.ToList();
|
||||||
|
_currentSubdir = directoryPath;
|
||||||
|
_currentPathItems.Clear();
|
||||||
|
foreach ((var k, IListItem v) in newPathItems)
|
||||||
|
{
|
||||||
|
_currentPathItems[k] = (ListItem)v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -458,7 +520,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
{
|
{
|
||||||
var hist = _historyService.GetRunHistory();
|
var hist = _historyService.GetRunHistory();
|
||||||
var histItems = hist
|
var histItems = hist
|
||||||
.Select(h => (h, ShellListPageHelpers.ListItemForCommandString(h, AddToHistory)))
|
.Select(h => (h, ShellListPageHelpers.ListItemForCommandString(h, AddToHistory, _telemetryService)))
|
||||||
.Where(tuple => tuple.Item2 is not null)
|
.Where(tuple => tuple.Item2 is not null)
|
||||||
.Select(tuple => (tuple.h, tuple.Item2!))
|
.Select(tuple => (tuple.h, tuple.Item2!))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Shell;
|
||||||
|
|
||||||
|
internal static class ResourceLoaderInstance
|
||||||
|
{
|
||||||
|
public static string GetString(string resourceKey)
|
||||||
|
{
|
||||||
|
return Properties.Resources.ResourceManager.GetString(resourceKey, Properties.Resources.Culture) ?? throw new InvalidOperationException($"Resource key '{resourceKey}' not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,19 +19,21 @@ public partial class ShellCommandsProvider : CommandProvider
|
|||||||
private readonly ShellListPage _shellListPage;
|
private readonly ShellListPage _shellListPage;
|
||||||
private readonly FallbackCommandItem _fallbackItem;
|
private readonly FallbackCommandItem _fallbackItem;
|
||||||
private readonly IRunHistoryService _historyService;
|
private readonly IRunHistoryService _historyService;
|
||||||
|
private readonly ITelemetryService _telemetryService;
|
||||||
|
|
||||||
public ShellCommandsProvider(IRunHistoryService runHistoryService)
|
public ShellCommandsProvider(IRunHistoryService runHistoryService, ITelemetryService telemetryService)
|
||||||
{
|
{
|
||||||
_historyService = runHistoryService;
|
_historyService = runHistoryService;
|
||||||
|
_telemetryService = telemetryService;
|
||||||
|
|
||||||
Id = "com.microsoft.cmdpal.builtin.run";
|
Id = "com.microsoft.cmdpal.builtin.run";
|
||||||
DisplayName = Resources.cmd_plugin_name;
|
DisplayName = Resources.cmd_plugin_name;
|
||||||
Icon = Icons.RunV2Icon;
|
Icon = Icons.RunV2Icon;
|
||||||
Settings = _settingsManager.Settings;
|
Settings = _settingsManager.Settings;
|
||||||
|
|
||||||
_shellListPage = new ShellListPage(_settingsManager, _historyService);
|
_shellListPage = new ShellListPage(_settingsManager, _historyService, _telemetryService);
|
||||||
|
|
||||||
_fallbackItem = new FallbackExecuteItem(_settingsManager, _shellListPage.AddToHistory);
|
_fallbackItem = new FallbackExecuteItem(_settingsManager, _shellListPage.AddToHistory, _telemetryService);
|
||||||
|
|
||||||
_shellPageItem = new CommandItem(_shellListPage)
|
_shellPageItem = new CommandItem(_shellListPage)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user