mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-02 10:26:22 +01:00
Compare commits
21 Commits
tools/Rele
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd9f523d5d | ||
|
|
de5fee2ca6 | ||
|
|
8318a40dd4 | ||
|
|
f1f00475d1 | ||
|
|
5da2e74622 | ||
|
|
0d3db48ab1 | ||
|
|
c8486087d8 | ||
|
|
d43a23c745 | ||
|
|
ffae061135 | ||
|
|
1e7c60ec23 | ||
|
|
2a5c61ce1f | ||
|
|
fdfffdc256 | ||
|
|
f249f99694 | ||
|
|
05c700a4cd | ||
|
|
28c0d4a420 | ||
|
|
48b70e0861 | ||
|
|
b0f6e0ae87 | ||
|
|
8cb518b649 | ||
|
|
08dc3fbcef | ||
|
|
eeb84cb621 | ||
|
|
5b2388cd58 |
2
.github/actions/spell-check/allow/names.txt
vendored
2
.github/actions/spell-check/allow/names.txt
vendored
@@ -210,6 +210,7 @@ capturevideosample
|
||||
cmdow
|
||||
Controlz
|
||||
cortana
|
||||
devhints
|
||||
dlnilsson
|
||||
fancymouse
|
||||
firefox
|
||||
@@ -229,6 +230,7 @@ regedit
|
||||
roslyn
|
||||
Skia
|
||||
Spotify
|
||||
tldr
|
||||
Vanara
|
||||
wangyi
|
||||
WEX
|
||||
|
||||
@@ -327,6 +327,12 @@
|
||||
"WinUI3Apps\\ReverseMarkdown.dll",
|
||||
"WinUI3Apps\\SharpCompress.dll",
|
||||
"WinUI3Apps\\ZstdSharp.dll",
|
||||
"CommunityToolkit.WinUI.Controls.MarkdownTextBlock.dll",
|
||||
"WinUI3Apps\\CommunityToolkit.WinUI.Controls.MarkdownTextBlock.dll",
|
||||
"Markdig.dll",
|
||||
"WinUI3Apps\\Markdig.dll",
|
||||
"RomanNumerals.dll",
|
||||
"WinUI3Apps\\RomanNumerals.dll",
|
||||
"TestableIO.System.IO.Abstractions.dll",
|
||||
"WinUI3Apps\\TestableIO.System.IO.Abstractions.dll",
|
||||
"TestableIO.System.IO.Abstractions.Wrappers.dll",
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
|
||||
<PackageVersion Include="UnitsNet" Version="5.56.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
|
||||
<PackageVersion Include="WinUIEx" Version="2.2.0" />
|
||||
<PackageVersion Include="WinUIEx" Version="2.8.0" />
|
||||
<PackageVersion Include="WPF-UI" Version="3.0.5" />
|
||||
<PackageVersion Include="WyHash" Version="1.0.5" />
|
||||
<PackageVersion Include="WixToolset.Heat" Version="5.0.2" />
|
||||
|
||||
@@ -50,6 +50,7 @@ Contact the developers of a plugin directly for assistance with a specific plugi
|
||||
| [Hotkeys](https://github.com/ruslanlap/PowerToysRun-Hotkeys) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and trigger custom keyboard shortcuts directly from PowerToys Run. |
|
||||
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
|
||||
| [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI |
|
||||
| [CheatSheets](https://github.com/ruslanlap/PowerToysRun-CheatSheets) | [ruslanlap](https://github.com/ruslanlap) | 📚 Find cheat sheets and command examples instantly from tldr pages, cheat.sh, and devhints.io. Features include favorites system, categories, offline mode, and smart caching. |
|
||||
|
||||
## Extending software plugins
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<CmdPalVersion Condition="'$(CmdPalVersion)'=='' and '$(XES_APPXMANIFESTVERSION)'!=''">$(XES_APPXMANIFESTVERSION)</CmdPalVersion>
|
||||
|
||||
<!-- MIKE: The file you're looking for is src/modules/cmdpal/custom.props -->
|
||||
<CmdPalVersion Condition="'$(CmdPalVersion)'==''">0.0.1.0</CmdPalVersion>
|
||||
|
||||
<DevEnvironment>Local</DevEnvironment>
|
||||
|
||||
<!-- Forcing for every DLL on by default -->
|
||||
|
||||
@@ -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 System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.Common.Services;
|
||||
|
||||
public interface IRunHistoryService
|
||||
@@ -25,3 +23,12 @@ public interface IRunHistoryService
|
||||
/// <param name="item">The run history item to add.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.2.0" />
|
||||
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.5.250829002" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0-preview.24508.2" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools.MSIX" Version="1.7.20250829.1" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
||||
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
|
||||
@@ -40,10 +40,13 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CommandPalette.Extensions" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWinRT" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<PackageReference Include="Shmuelie.WinRTServer" />
|
||||
|
||||
<!-- Needed to enable building an MSIX package -->
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools.MSIX">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
@@ -84,6 +87,9 @@
|
||||
<!-- In Release builds, trimming is enabled by default.
|
||||
feel free to disable this if needed -->
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
|
||||
<!-- In release, also ignore the aforementioned ILLink warning -->
|
||||
<ILLinkTreatWarningsAsErrors>false</ILLinkTreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
@@ -160,7 +160,7 @@ public partial class App : Application
|
||||
|
||||
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
||||
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
|
||||
services.AddSingleton(new TelemetryForwarder());
|
||||
services.AddSingleton<ITelemetryService, TelemetryForwarder>();
|
||||
|
||||
// ViewModels
|
||||
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
|
||||
@@ -2,9 +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.UI.Xaml;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
internal static class BindTransformers
|
||||
{
|
||||
public static bool Negate(bool value) => !value;
|
||||
|
||||
public static Visibility EmptyToCollapsed(string? input)
|
||||
=> string.IsNullOrEmpty(input) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
public static Visibility EmptyOrWhitespaceToCollapsed(string? input)
|
||||
=> string.IsNullOrWhiteSpace(input) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
@@ -19,6 +20,7 @@ namespace Microsoft.CmdPal.UI;
|
||||
/// or something similar, but this works for now.
|
||||
/// </summary>
|
||||
internal sealed class TelemetryForwarder :
|
||||
ITelemetryService,
|
||||
IRecipient<BeginInvokeMessage>,
|
||||
IRecipient<CmdPalInvokeResultMessage>
|
||||
{
|
||||
@@ -37,4 +39,19 @@ internal sealed class TelemetryForwarder :
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,31 @@
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
|
||||
<Style
|
||||
x:Key="DetailKeyTextBlockStyle"
|
||||
BasedOn="{StaticResource CaptionTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="SeparatorKeyTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DetailValueTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="TagTemplate" x:DataType="coreViewModels:TagViewModel">
|
||||
<cpcontrols:Tag
|
||||
HorizontalAlignment="Left"
|
||||
@@ -68,7 +93,7 @@
|
||||
Margin="0,3,8,0"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
<TextBlock Text="{x:Bind Name}" />
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{x:Bind Name}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
@@ -76,20 +101,13 @@
|
||||
|
||||
<DataTemplate x:Key="DetailsLinkTemplate" x:DataType="coreViewModels:DetailsLinkViewModel">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Style="{StaticResource DetailKeyTextBlockStyle}" Text="{x:Bind Key, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Key, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Style="{StaticResource DetailValueTextBlockStyle}"
|
||||
Text="{x:Bind Text, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="{x:Bind IsText, Mode=OneWay}" />
|
||||
<HyperlinkButton
|
||||
Padding="0"
|
||||
FontSize="12"
|
||||
NavigateUri="{x:Bind Link, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsLink, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Bind Text, Mode=OneWay}" TextWrapping="Wrap" />
|
||||
@@ -98,10 +116,7 @@
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="DetailsCommandsTemplate" x:DataType="coreViewModels:DetailsCommandsViewModel">
|
||||
<StackPanel Orientation="Vertical" Spacing="4">
|
||||
<TextBlock
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Key, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock Style="{StaticResource DetailKeyTextBlockStyle}" Text="{x:Bind Key, Mode=OneWay}" />
|
||||
<ItemsControl
|
||||
ItemTemplate="{StaticResource CommandTemplate}"
|
||||
ItemsSource="{x:Bind Commands, Mode=OneWay}"
|
||||
@@ -111,24 +126,20 @@
|
||||
<DataTemplate x:Key="DetailsSeparatorTemplate" x:DataType="coreViewModels:DetailsSeparatorViewModel">
|
||||
<StackPanel Margin="0,8,8,0" Orientation="Vertical">
|
||||
<Border
|
||||
Margin="8,0,0,0"
|
||||
BorderBrush="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Margin="0,0,0,0"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,2">
|
||||
<TextBlock
|
||||
Margin="-8,0,0,8"
|
||||
FontWeight="SemiBold"
|
||||
IsTextSelectionEnabled="True"
|
||||
Margin="0,0,0,0"
|
||||
Style="{StaticResource SeparatorKeyTextBlockStyle}"
|
||||
Text="{x:Bind Key, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
Visibility="{x:Bind help:BindTransformers.EmptyOrWhitespaceToCollapsed(Key), FallbackValue=Collapsed}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="DetailsTagsTemplate" x:DataType="coreViewModels:DetailsTagsViewModel">
|
||||
<StackPanel Orientation="Vertical" Spacing="4">
|
||||
<TextBlock
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Key, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock Style="{StaticResource DetailKeyTextBlockStyle}" Text="{x:Bind Key, Mode=OneWay}" />
|
||||
<ItemsControl
|
||||
ItemTemplate="{StaticResource TagTemplate}"
|
||||
ItemsSource="{x:Bind Tags, Mode=OneWay}"
|
||||
|
||||
@@ -24,10 +24,11 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar x:Name="TitleBar">
|
||||
<TitleBar x:Name="AppTitleBar" PaneToggleRequested="AppTitleBar_PaneToggleRequested">
|
||||
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
|
||||
<TitleBar.LeftHeader>
|
||||
<ImageIcon
|
||||
x:Name="WorkAroundIcon"
|
||||
Height="16"
|
||||
Margin="16,0,0,0"
|
||||
Source="ms-appx:///Assets/icon.svg" />
|
||||
@@ -36,18 +37,19 @@
|
||||
<NavigationView
|
||||
x:Name="NavView"
|
||||
Grid.Row="1"
|
||||
CompactModeThresholdWidth="1007"
|
||||
DisplayModeChanged="NavView_DisplayModeChanged"
|
||||
ExpandedModeThresholdWidth="1007"
|
||||
IsBackButtonVisible="Collapsed"
|
||||
IsPaneToggleButtonVisible="False"
|
||||
IsSettingsVisible="False"
|
||||
ItemInvoked="NavView_ItemInvoked"
|
||||
Loaded="NavView_Loaded"
|
||||
OpenPaneLength="200">
|
||||
Loaded="NavView_Loaded">
|
||||
<NavigationView.Resources>
|
||||
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="NavigationViewContentGridBorderBrush" Color="Transparent" />
|
||||
<Thickness x:Key="NavigationViewHeaderMargin">15,0,0,0</Thickness>
|
||||
</NavigationView.Resources>
|
||||
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_General"
|
||||
@@ -58,34 +60,32 @@
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="Extensions" />
|
||||
</NavigationView.MenuItems>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<BreadcrumbBar
|
||||
x:Name="NavigationBreadcrumbBar"
|
||||
Grid.Row="0"
|
||||
MaxWidth="1000"
|
||||
ItemClicked="NavigationBreadcrumbBar_ItemClicked"
|
||||
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
|
||||
<BreadcrumbBar.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:Crumb">
|
||||
<TextBlock Text="{x:Bind Label, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</BreadcrumbBar.ItemTemplate>
|
||||
<BreadcrumbBar.Resources>
|
||||
<ResourceDictionary>
|
||||
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">28</x:Double>
|
||||
<Thickness x:Key="BreadcrumbBarChevronPadding">7,4,8,0</Thickness>
|
||||
<FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</FontWeight>
|
||||
<x:Double x:Key="BreadcrumbBarChevronFontSize">16</x:Double>
|
||||
</ResourceDictionary>
|
||||
</BreadcrumbBar.Resources>
|
||||
</BreadcrumbBar>
|
||||
|
||||
<Grid Padding="16,0">
|
||||
<BreadcrumbBar
|
||||
x:Name="NavigationBreadcrumbBar"
|
||||
MaxWidth="1000"
|
||||
ItemClicked="NavigationBreadcrumbBar_ItemClicked"
|
||||
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
|
||||
<BreadcrumbBar.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:Crumb">
|
||||
<TextBlock Text="{x:Bind Label, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</BreadcrumbBar.ItemTemplate>
|
||||
<BreadcrumbBar.Resources>
|
||||
<ResourceDictionary>
|
||||
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">28</x:Double>
|
||||
<Thickness x:Key="BreadcrumbBarChevronPadding">7,4,8,0</Thickness>
|
||||
<FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</FontWeight>
|
||||
<x:Double x:Key="BreadcrumbBarChevronFontSize">16</x:Double>
|
||||
</ResourceDictionary>
|
||||
</BreadcrumbBar.Resources>
|
||||
</BreadcrumbBar>
|
||||
</Grid>
|
||||
<Frame x:Name="NavFrame" Grid.Row="1" />
|
||||
</Grid>
|
||||
</NavigationView>
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.UI.Xaml.Automation.Peers;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using WinUIEx;
|
||||
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
|
||||
using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
@@ -34,7 +35,7 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
var title = RS_.GetString("SettingsWindowTitle");
|
||||
this.AppWindow.Title = title;
|
||||
this.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
|
||||
this.TitleBar.Title = title;
|
||||
this.AppTitleBar.Title = title;
|
||||
PositionCentered();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<NavigateToExtensionSettingsMessage>(this);
|
||||
@@ -142,11 +143,13 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
{
|
||||
if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal)
|
||||
{
|
||||
NavView.IsPaneToggleButtonVisible = false;
|
||||
AppTitleBar.IsPaneToggleButtonVisible = true;
|
||||
WorkAroundIcon.Margin = new Thickness(8, 0, 16, 0); // Required for workaround, see XAML comment
|
||||
}
|
||||
else
|
||||
{
|
||||
NavView.IsPaneToggleButtonVisible = true;
|
||||
AppTitleBar.IsPaneToggleButtonVisible = false;
|
||||
WorkAroundIcon.Margin = new Thickness(16, 0, 0, 0); // Required for workaround, see XAML comment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +158,11 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
// This might come in on a background thread
|
||||
DispatcherQueue.TryEnqueue(() => Close());
|
||||
}
|
||||
|
||||
private void AppTitleBar_PaneToggleRequested(TitleBar sender, object args)
|
||||
{
|
||||
NavView.IsPaneOpen = !NavView.IsPaneOpen;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Crumb
|
||||
|
||||
@@ -23,6 +23,7 @@ public class NormalizeCommandLineTests : CommandPaletteUnitTestBase
|
||||
[DataRow("ping bing.com", "c:\\Windows\\system32\\ping.exe", "bing.com")]
|
||||
[DataRow("curl bing.com", "c:\\Windows\\system32\\curl.exe", "bing.com")]
|
||||
[DataRow("ipconfig /all", "c:\\Windows\\system32\\ipconfig.exe", "/all")]
|
||||
[DataRow("ipconfig a b \"c d\"", "c:\\Windows\\system32\\ipconfig.exe", "a b \"c d\"")]
|
||||
public void NormalizeCommandLineSimple(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
NormalizeTestCore(input, expectedExe, expectedArgs);
|
||||
@@ -46,7 +47,7 @@ public class NormalizeCommandLineTests : CommandPaletteUnitTestBase
|
||||
[TestMethod]
|
||||
[DataRow("cmd --run --test", "C:\\Windows\\System32\\cmd.exe", "--run --test")]
|
||||
[DataRow("cmd --run --test ", "C:\\Windows\\System32\\cmd.exe", "--run --test")]
|
||||
[DataRow("cmd \"--run --test\" --pass", "C:\\Windows\\System32\\cmd.exe", "--run --test --pass")]
|
||||
[DataRow("cmd \"--run --test\" --pass", "C:\\Windows\\System32\\cmd.exe", "\"--run --test\" --pass")]
|
||||
public void NormalizeArgsWithSpaces(string input, string expectedExe, string expectedArgs = "")
|
||||
{
|
||||
NormalizeTestCore(input, expectedExe, expectedArgs);
|
||||
|
||||
@@ -2,7 +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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -83,7 +82,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
var settings = Settings.CreateDefaultSettings();
|
||||
var mockHistory = CreateMockHistoryService();
|
||||
|
||||
var pages = new ShellListPage(settings, mockHistory.Object);
|
||||
var pages = new ShellListPage(settings, mockHistory.Object, telemetryService: null);
|
||||
|
||||
await UpdatePageAndWaitForItems(pages, () =>
|
||||
{
|
||||
@@ -115,7 +114,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
var settings = Settings.CreateDefaultSettings();
|
||||
var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
|
||||
|
||||
var pages = new ShellListPage(settings, mockHistoryService.Object);
|
||||
var pages = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
|
||||
|
||||
await UpdatePageAndWaitForItems(pages, () =>
|
||||
{
|
||||
@@ -141,7 +140,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
var settings = Settings.CreateDefaultSettings();
|
||||
var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
|
||||
|
||||
var pages = new ShellListPage(settings, mockHistoryService.Object);
|
||||
var pages = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
|
||||
|
||||
await UpdatePageAndWaitForItems(pages, () =>
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ public class ShellCommandProviderTests
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryService = new Mock<IRunHistoryService>();
|
||||
var provider = new ShellCommandsProvider(mockHistoryService.Object);
|
||||
var provider = new ShellCommandsProvider(mockHistoryService.Object, telemetryService: null);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(provider.DisplayName);
|
||||
@@ -28,7 +28,7 @@ public class ShellCommandProviderTests
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryService = new Mock<IRunHistoryService>();
|
||||
var provider = new ShellCommandsProvider(mockHistoryService.Object);
|
||||
var provider = new ShellCommandsProvider(mockHistoryService.Object, telemetryService: null);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(provider.Icon);
|
||||
@@ -39,7 +39,7 @@ public class ShellCommandProviderTests
|
||||
{
|
||||
// Setup
|
||||
var mockHistoryService = new Mock<IRunHistoryService>();
|
||||
var provider = new ShellCommandsProvider(mockHistoryService.Object);
|
||||
var provider = new ShellCommandsProvider(mockHistoryService.Object, telemetryService: null);
|
||||
|
||||
// Act
|
||||
var commands = provider.TopLevelCommands();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>0</VersionMajor>
|
||||
<VersionMinor>5</VersionMinor>
|
||||
<VersionMinor>6</VersionMinor>
|
||||
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell;
|
||||
@@ -13,18 +11,33 @@ internal sealed partial class OpenUrlWithHistoryCommand : OpenUrlCommand
|
||||
{
|
||||
private readonly Action<string>? _addToHistory;
|
||||
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)
|
||||
{
|
||||
_addToHistory = addToHistory;
|
||||
_url = url;
|
||||
_telemetryService = telemetryService;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_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.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Shell.Pages;
|
||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell;
|
||||
@@ -19,18 +14,20 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
private static readonly char[] _systemDirectoryRoots = ['\\', '/'];
|
||||
|
||||
private readonly Action<string>? _addToHistory;
|
||||
private readonly ITelemetryService _telemetryService;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private Task? _currentUpdateTask;
|
||||
|
||||
public FallbackExecuteItem(SettingsManager settings, Action<string>? addToHistory)
|
||||
public FallbackExecuteItem(SettingsManager settings, Action<string>? addToHistory, ITelemetryService telemetryService)
|
||||
: base(
|
||||
new NoOpCommand() { Id = "com.microsoft.run.fallback" },
|
||||
Resources.shell_command_display_title)
|
||||
ResourceLoaderInstance.GetString("shell_command_display_title"))
|
||||
{
|
||||
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.
|
||||
_addToHistory = addToHistory;
|
||||
_telemetryService = telemetryService;
|
||||
}
|
||||
|
||||
public override void UpdateQuery(string query)
|
||||
@@ -147,7 +144,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
if (exeExists)
|
||||
{
|
||||
// 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;
|
||||
Subtitle = exeItem.Subtitle;
|
||||
Icon = exeItem.Icon;
|
||||
@@ -156,7 +153,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
}
|
||||
else if (pathIsDir)
|
||||
{
|
||||
var pathItem = new PathListItem(exe, query, _addToHistory);
|
||||
var pathItem = new PathListItem(exe, query, _addToHistory, _telemetryService);
|
||||
Command = pathItem.Command;
|
||||
MoreCommands = pathItem.MoreCommands;
|
||||
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))
|
||||
{
|
||||
Command = new OpenUrlWithHistoryCommand(searchText, _addToHistory) { Result = CommandResult.Dismiss() };
|
||||
Command = new OpenUrlWithHistoryCommand(searchText, _addToHistory, _telemetryService) { Result = CommandResult.Dismiss() };
|
||||
Title = searchText;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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 System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Storage.FileSystem;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
|
||||
@@ -19,38 +19,11 @@ namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
public static class CommandLineNormalizer
|
||||
{
|
||||
#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 FILE_ATTRIBUTE_DIRECTORY = 0x10;
|
||||
|
||||
private const int MAX_PATH = 260;
|
||||
#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>
|
||||
/// Normalizes a command line string by expanding environment variables, resolving executable paths,
|
||||
/// and standardizing the format for comparison purposes.
|
||||
@@ -129,9 +102,9 @@ public static class CommandLineNormalizer
|
||||
private static string ExpandEnvironmentVariables(string input)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -139,11 +112,11 @@ public static class CommandLineNormalizer
|
||||
return input;
|
||||
}
|
||||
|
||||
if (result > buffer.Capacity)
|
||||
if (result > buffer.Length)
|
||||
{
|
||||
// Buffer was too small, resize and try again
|
||||
buffer.Capacity = (int)result;
|
||||
result = ExpandEnvironmentStringsW(input, buffer, (uint)buffer.Capacity);
|
||||
buffer = new char[result];
|
||||
result = PInvoke.ExpandEnvironmentStrings(input, buffer);
|
||||
|
||||
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>
|
||||
@@ -159,28 +132,30 @@ public static class CommandLineNormalizer
|
||||
/// </summary>
|
||||
private static string[] ParseCommandLineToArguments(string commandLine)
|
||||
{
|
||||
var argv = CommandLineToArgvW(commandLine, out var argc);
|
||||
|
||||
if (argv == IntPtr.Zero || argc == 0)
|
||||
unsafe
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
var argv = PInvoke.CommandLineToArgv(commandLine, out var argc);
|
||||
|
||||
try
|
||||
{
|
||||
var args = new string[argc];
|
||||
|
||||
for (var i = 0; i < argc; i++)
|
||||
if (argv == null || argc == 0)
|
||||
{
|
||||
var argPtr = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
|
||||
args[i] = Marshal.PtrToStringUni(argPtr) ?? string.Empty;
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
finally
|
||||
{
|
||||
LocalFree(argv);
|
||||
try
|
||||
{
|
||||
var args = new string[argc];
|
||||
|
||||
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>
|
||||
private static string TryResolveExecutable(string executableName)
|
||||
{
|
||||
var buffer = new StringBuilder(MAX_PATH);
|
||||
var buffer = new char[MAX_PATH];
|
||||
|
||||
var result = SearchPathW(
|
||||
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)
|
||||
unsafe
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var outParam = default(PWSTR); // ultimately discarded
|
||||
|
||||
if (result > buffer.Capacity)
|
||||
{
|
||||
// Buffer was too small, resize and try again
|
||||
buffer.Capacity = (int)result;
|
||||
result = SearchPathW(null, executableName, ".exe", (uint)buffer.Capacity, buffer, out var _);
|
||||
var result = PInvoke.SearchPath(
|
||||
null, // Use default search path
|
||||
executableName,
|
||||
".exe", // Default extension
|
||||
buffer,
|
||||
&outParam); // We don't need the file part
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
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>
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Shell.Commands;
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.Shell.Pages;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -16,37 +10,6 @@ namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
|
||||
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)
|
||||
{
|
||||
return FileExistInPath(filename, out var _);
|
||||
@@ -58,7 +21,7 @@ public class ShellListPageHelpers
|
||||
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();
|
||||
|
||||
@@ -100,7 +63,7 @@ public class ShellListPageHelpers
|
||||
if (exeExists)
|
||||
{
|
||||
// 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.Title = exeItem.Title;
|
||||
li.Subtitle = exeItem.Subtitle;
|
||||
@@ -109,7 +72,7 @@ public class ShellListPageHelpers
|
||||
}
|
||||
else if (pathIsDir)
|
||||
{
|
||||
var pathItem = new PathListItem(exe, query, addToHistory);
|
||||
var pathItem = new PathListItem(exe, query, addToHistory, telemetryService);
|
||||
li.Command = pathItem.Command;
|
||||
li.Title = pathItem.Title;
|
||||
li.Subtitle = pathItem.Subtitle;
|
||||
@@ -118,7 +81,7 @@ public class ShellListPageHelpers
|
||||
}
|
||||
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;
|
||||
}
|
||||
else
|
||||
@@ -157,7 +120,98 @@ public class ShellListPageHelpers
|
||||
executable = segments[0];
|
||||
if (segments.Length > 1)
|
||||
{
|
||||
arguments = string.Join(' ', segments[1..]);
|
||||
arguments = ArgumentBuilder.BuildArguments(segments[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ArgumentBuilder
|
||||
{
|
||||
internal static string BuildArguments(string[] arguments)
|
||||
{
|
||||
if (arguments.Length <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
foreach (var argument in arguments)
|
||||
{
|
||||
AppendArgument(stringBuilder, argument);
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
private static void AppendArgument(StringBuilder stringBuilder, string argument)
|
||||
{
|
||||
if (stringBuilder.Length > 0)
|
||||
{
|
||||
stringBuilder.Append(' ');
|
||||
}
|
||||
|
||||
if (argument.Length == 0 || ShouldBeQuoted(argument))
|
||||
{
|
||||
stringBuilder.Append('\"');
|
||||
var index = 0;
|
||||
while (index < argument.Length)
|
||||
{
|
||||
var c = argument[index++];
|
||||
if (c == '\\')
|
||||
{
|
||||
var numBackSlash = 1;
|
||||
while (index < argument.Length && argument[index] == '\\')
|
||||
{
|
||||
index++;
|
||||
numBackSlash++;
|
||||
}
|
||||
|
||||
if (index == argument.Length)
|
||||
{
|
||||
stringBuilder.Append('\\', numBackSlash * 2);
|
||||
}
|
||||
else if (argument[index] == '\"')
|
||||
{
|
||||
stringBuilder.Append('\\', (numBackSlash * 2) + 1);
|
||||
stringBuilder.Append('\"');
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append('\\', numBackSlash);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\"')
|
||||
{
|
||||
stringBuilder.Append('\\');
|
||||
stringBuilder.Append('\"');
|
||||
continue;
|
||||
}
|
||||
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
|
||||
stringBuilder.Append('\"');
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(argument);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldBeQuoted(string s)
|
||||
{
|
||||
foreach (var c in s)
|
||||
{
|
||||
if (char.IsWhiteSpace(c) || c == '\"')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<Import Project="..\Common.ExtDependencies.props" />
|
||||
<Import Project="..\..\CoreCommonProps.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.Shell</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
@@ -16,7 +12,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
<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.
|
||||
// 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.Services;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
@@ -13,13 +12,18 @@ namespace Microsoft.CmdPal.Ext.Shell;
|
||||
|
||||
internal sealed partial class PathListItem : ListItem
|
||||
{
|
||||
private readonly Lazy<IconInfo> _icon;
|
||||
private readonly bool _isDirectory;
|
||||
private readonly Lazy<bool> fetchedIcon;
|
||||
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)
|
||||
: base(new OpenUrlWithHistoryCommand(path, addToHistory))
|
||||
private IIconInfo? _icon;
|
||||
|
||||
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);
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
@@ -27,8 +31,8 @@ internal sealed partial class PathListItem : ListItem
|
||||
fileName = Path.GetFileName(Path.GetDirectoryName(path)) ?? string.Empty;
|
||||
}
|
||||
|
||||
_isDirectory = Directory.Exists(path);
|
||||
if (_isDirectory)
|
||||
isDirectory = Directory.Exists(path);
|
||||
if (isDirectory)
|
||||
{
|
||||
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
|
||||
Subtitle = path; // What the user typed is the subtitle
|
||||
|
||||
@@ -58,23 +64,35 @@ internal sealed partial class PathListItem : ListItem
|
||||
// wrap it in quotes
|
||||
suggestion = string.Concat("\"", suggestion, "\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
suggestion = path;
|
||||
}
|
||||
|
||||
TextToSuggest = suggestion;
|
||||
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new OpenWithCommand(path)),
|
||||
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 OpenPropertiesCommand(path)),
|
||||
];
|
||||
|
||||
_icon = new Lazy<IconInfo>(() =>
|
||||
fetchedIcon = new Lazy<bool>(() =>
|
||||
{
|
||||
var iconStream = ThumbnailHelper.GetThumbnail(path).Result;
|
||||
var icon = iconStream is not null ? IconInfo.FromStream(iconStream) :
|
||||
_isDirectory ? Icons.FolderIcon : Icons.RunV2Icon;
|
||||
return icon;
|
||||
_ = Task.Run(FetchIconAsync);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Storage.Streams;
|
||||
@@ -15,6 +15,7 @@ internal sealed partial class RunExeItem : ListItem
|
||||
{
|
||||
private readonly Lazy<IconInfo> _icon;
|
||||
private readonly Action<string>? _addToHistory;
|
||||
private readonly ITelemetryService? _telemetryService;
|
||||
|
||||
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}";
|
||||
|
||||
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;
|
||||
Exe = exe;
|
||||
var command = new AnonymousCommand(Run)
|
||||
{
|
||||
Name = Properties.Resources.generic_run_command,
|
||||
Name = ResourceLoaderInstance.GetString("generic_run_command"),
|
||||
Result = CommandResult.Dismiss(),
|
||||
};
|
||||
Command = command;
|
||||
@@ -46,6 +52,7 @@ internal sealed partial class RunExeItem : ListItem
|
||||
});
|
||||
|
||||
_addToHistory = addToHistory;
|
||||
_telemetryService = telemetryService;
|
||||
|
||||
UpdateArgs(args);
|
||||
|
||||
@@ -53,13 +60,13 @@ internal sealed partial class RunExeItem : ListItem
|
||||
new CommandContextItem(
|
||||
new AnonymousCommand(RunAsAdmin)
|
||||
{
|
||||
Name = Properties.Resources.cmd_run_as_administrator,
|
||||
Name = ResourceLoaderInstance.GetString("cmd_run_as_administrator"),
|
||||
Icon = Icons.AdminIcon,
|
||||
}) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter) },
|
||||
new CommandContextItem(
|
||||
new AnonymousCommand(RunAsOther)
|
||||
{
|
||||
Name = Properties.Resources.cmd_run_as_user,
|
||||
Name = ResourceLoaderInstance.GetString("cmd_run_as_user"),
|
||||
Icon = Icons.UserIcon,
|
||||
}) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.U) },
|
||||
];
|
||||
@@ -97,20 +104,26 @@ internal sealed partial class RunExeItem : ListItem
|
||||
{
|
||||
_addToHistory?.Invoke(FullString);
|
||||
|
||||
ShellHelpers.OpenInShell(FullExePath, _args);
|
||||
var success = ShellHelpers.OpenInShell(FullExePath, _args);
|
||||
|
||||
_telemetryService?.LogRunCommand(FullString, false, success);
|
||||
}
|
||||
|
||||
public void RunAsAdmin()
|
||||
{
|
||||
_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()
|
||||
{
|
||||
_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.
|
||||
// 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.Ext.Shell.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -18,13 +11,13 @@ namespace Microsoft.CmdPal.Ext.Shell.Pages;
|
||||
|
||||
internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
{
|
||||
private readonly ShellListPageHelpers _helper;
|
||||
|
||||
private readonly List<ListItem> _topLevelItems = [];
|
||||
private readonly Dictionary<string, ListItem> _historyItems = [];
|
||||
private readonly List<ListItem> _currentHistoryItems = [];
|
||||
|
||||
private readonly IRunHistoryService _historyService;
|
||||
private readonly ITelemetryService? _telemetryService;
|
||||
|
||||
private readonly Dictionary<string, ListItem> _currentPathItems = new();
|
||||
|
||||
private ListItem? _exeItem;
|
||||
private List<ListItem> _pathItems = [];
|
||||
@@ -35,27 +28,26 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
|
||||
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;
|
||||
Id = "com.microsoft.cmdpal.shell";
|
||||
Name = Resources.cmd_plugin_name;
|
||||
PlaceholderText = Resources.list_placeholder_text;
|
||||
_helper = new(settingsManager);
|
||||
Name = ResourceLoaderInstance.GetString("cmd_plugin_name");
|
||||
PlaceholderText = ResourceLoaderInstance.GetString("list_placeholder_text");
|
||||
_historyService = runHistoryService;
|
||||
_telemetryService = telemetryService;
|
||||
|
||||
EmptyContent = new CommandItem()
|
||||
{
|
||||
Title = Resources.cmd_plugin_name,
|
||||
Title = ResourceLoaderInstance.GetString("cmd_plugin_name"),
|
||||
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)
|
||||
@@ -123,8 +115,13 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
|
||||
private async Task BuildListItemsForSearchAsync(string newSearch, CancellationToken cancellationToken)
|
||||
{
|
||||
var timer = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
// new search is just the oldSearch+some chars
|
||||
@@ -206,7 +206,10 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
couldResolvePath = false;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pathItems.Clear();
|
||||
|
||||
@@ -221,7 +224,10 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
}
|
||||
|
||||
// Check for cancellation before creating exe items
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (couldResolvePath && exeExists)
|
||||
{
|
||||
@@ -278,17 +284,31 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
_currentHistoryItems.AddRange(filteredHistory);
|
||||
|
||||
// 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
|
||||
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,
|
||||
@@ -306,24 +326,22 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
LoadInitialHistory();
|
||||
}
|
||||
|
||||
var filteredTopLevel = ListHelpers.FilterList(_topLevelItems, SearchText);
|
||||
List<ListItem> uriItems = _uriItem is not null ? [_uriItem] : [];
|
||||
List<ListItem> exeItems = _exeItem is not null ? [_exeItem] : [];
|
||||
|
||||
return
|
||||
exeItems
|
||||
.Concat(filteredTopLevel)
|
||||
.Concat(_currentHistoryItems)
|
||||
.Concat(_pathItems)
|
||||
.Concat(uriItems)
|
||||
.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.
|
||||
// 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)
|
||||
@@ -335,7 +353,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
}
|
||||
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
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dirExists = Directory.Exists(directoryPath);
|
||||
|
||||
@@ -408,30 +429,71 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
if (dirExists)
|
||||
{
|
||||
// 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
|
||||
// Run this on a background thread to avoid blocking
|
||||
var files = await Task.Run(() => Directory.GetFileSystemEntries(directoryPath, searchPattern), cancellationToken);
|
||||
|
||||
// Check for cancellation after file enumeration
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
originalBeginning = string.Concat(originalBeginning, '\\');
|
||||
}
|
||||
|
||||
// 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
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
@@ -458,7 +520,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
{
|
||||
var hist = _historyService.GetRunHistory();
|
||||
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)
|
||||
.Select(tuple => (tuple.h, tuple.Item2!))
|
||||
.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 FallbackCommandItem _fallbackItem;
|
||||
private readonly IRunHistoryService _historyService;
|
||||
private readonly ITelemetryService _telemetryService;
|
||||
|
||||
public ShellCommandsProvider(IRunHistoryService runHistoryService)
|
||||
public ShellCommandsProvider(IRunHistoryService runHistoryService, ITelemetryService telemetryService)
|
||||
{
|
||||
_historyService = runHistoryService;
|
||||
_telemetryService = telemetryService;
|
||||
|
||||
Id = "com.microsoft.cmdpal.builtin.run";
|
||||
DisplayName = Resources.cmd_plugin_name;
|
||||
Icon = Icons.RunV2Icon;
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -159,15 +159,6 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open.
|
||||
/// </summary>
|
||||
internal static string Page_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Page_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Settings.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,44 +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.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Activation
|
||||
{
|
||||
// For more information on understanding and extending activation flow see
|
||||
// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md
|
||||
internal abstract class ActivationHandler
|
||||
{
|
||||
public abstract bool CanHandle(object args);
|
||||
|
||||
public abstract Task HandleAsync(object args);
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "abstract T and abstract")]
|
||||
internal abstract class ActivationHandler<T> : ActivationHandler
|
||||
where T : class
|
||||
{
|
||||
public override async Task HandleAsync(object args)
|
||||
{
|
||||
await HandleInternalAsync(args as T).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override bool CanHandle(object args)
|
||||
{
|
||||
// CanHandle checks the args is of type you have configured
|
||||
return args is T && CanHandleInternal(args as T);
|
||||
}
|
||||
|
||||
// Override this method to add the activation logic in your activation handler
|
||||
protected abstract Task HandleInternalAsync(T args);
|
||||
|
||||
// You can override this method to add extra validation on activation args
|
||||
// to determine if your ActivationHandler should handle this activation args
|
||||
protected virtual bool CanHandleInternal(T args)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +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.Threading.Tasks;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Activation
|
||||
{
|
||||
internal sealed class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||
{
|
||||
private readonly Type navElement;
|
||||
|
||||
public DefaultActivationHandler(Type navElement)
|
||||
{
|
||||
this.navElement = navElement;
|
||||
}
|
||||
|
||||
protected override async Task HandleInternalAsync(IActivatedEventArgs args)
|
||||
{
|
||||
// When the navigation stack isn't restored, navigate to the first page and configure
|
||||
// the new page by passing required information in the navigation parameter
|
||||
object arguments = null;
|
||||
if (args is LaunchActivatedEventArgs launchArgs)
|
||||
{
|
||||
arguments = launchArgs.Arguments;
|
||||
}
|
||||
|
||||
NavigationService.Navigate(navElement, arguments);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||
{
|
||||
// None of the ActivationHandlers has handled the app activation
|
||||
return NavigationService.Frame.Content == null && navElement != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Activation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
// For more information on understanding and extending activation flow see
|
||||
// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md
|
||||
internal sealed class ActivationService
|
||||
{
|
||||
private readonly App app;
|
||||
private readonly Type defaultNavItem;
|
||||
private Lazy<UIElement> shell;
|
||||
|
||||
private object lastActivationArgs;
|
||||
|
||||
public ActivationService(App app, Type defaultNavItem, Lazy<UIElement> shell = null)
|
||||
{
|
||||
this.app = app;
|
||||
this.shell = shell;
|
||||
this.defaultNavItem = defaultNavItem;
|
||||
}
|
||||
|
||||
public async Task ActivateAsync(object activationArgs)
|
||||
{
|
||||
if (IsInteractive(activationArgs))
|
||||
{
|
||||
// Initialize services that you need before app activation
|
||||
// take into account that the splash screen is shown while this code runs.
|
||||
await InitializeAsync().ConfigureAwait(false);
|
||||
|
||||
// Do not repeat app initialization when the Window already has content,
|
||||
// just ensure that the window is active
|
||||
if (Window.Current.Content == null)
|
||||
{
|
||||
// Create a Shell or Frame to act as the navigation context
|
||||
Window.Current.Content = shell?.Value ?? new Frame();
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on activationArgs one of ActivationHandlers or DefaultActivationHandler
|
||||
// will navigate to the first page
|
||||
await HandleActivationAsync(activationArgs).ConfigureAwait(false);
|
||||
lastActivationArgs = activationArgs;
|
||||
|
||||
if (IsInteractive(activationArgs))
|
||||
{
|
||||
// Ensure the current window is active
|
||||
Window.Current.Activate();
|
||||
|
||||
// Tasks after activation
|
||||
await StartupAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task InitializeAsync()
|
||||
{
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleActivationAsync(object activationArgs)
|
||||
{
|
||||
var activationHandler = GetActivationHandlers()
|
||||
.FirstOrDefault(h => h.CanHandle(activationArgs));
|
||||
|
||||
if (activationHandler != null)
|
||||
{
|
||||
await activationHandler.HandleAsync(activationArgs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (IsInteractive(activationArgs))
|
||||
{
|
||||
var defaultHandler = new DefaultActivationHandler(defaultNavItem);
|
||||
if (defaultHandler.CanHandle(activationArgs))
|
||||
{
|
||||
await defaultHandler.HandleAsync(activationArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task StartupAsync()
|
||||
{
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static IEnumerable<ActivationHandler> GetActivationHandlers()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
private static bool IsInteractive(object args)
|
||||
{
|
||||
return args is IActivatedEventArgs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,6 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
get
|
||||
{
|
||||
if (frame == null)
|
||||
{
|
||||
frame = Window.Current.Content as Frame;
|
||||
RegisterFrameEvents();
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user