Skip to content

Commit ed500d6

Browse files
authored
Added Support for Installing Dotnet Templates (#1)
1 parent 814460d commit ed500d6

6 files changed

Lines changed: 166 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ NuGetPackageSearchExtension is an extension for the PowerToys Command Palette th
66

77
- Search for NuGet packages directly from the PowerToys Command Palette.
88
- Copy package references in various formats (e.g., PackageReference, dotnet CLI command, NuGet Package Manager Console command).
9+
- Install dotnet template packages directly from the Command Palette.
910

1011
## Upcoming Features
1112
- **Install dotnet tools**: Install dotnet tools directly from the Command Palette.
1213
- **Search for specific package versions**: Filter search results to find specific versions of packages.
13-
- **Install dotnet template packages**: Install dotnet template packages directly from the Command Palette.
1414

1515
## Requirements
1616

src/NuGetPackageSearchCmdPalExtension/NuGetPackageSearchCmdPalExtension.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
<AppxSymbolPackageEnabled>False</AppxSymbolPackageEnabled>
7373
<GenerateTestArtifacts>True</GenerateTestArtifacts>
7474
<AppxBundle>Auto</AppxBundle>
75-
<AppxBundlePlatforms>x86|x64|arm64</AppxBundlePlatforms>
75+
<AppxBundlePlatforms>x64|arm64</AppxBundlePlatforms>
7676
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
7777
</PropertyGroup>
7878
</Project>

src/NuGetPackageSearchCmdPalExtension/NuGetPackageSearchCmdPalExtensionCommandsProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public NuGetPackageSearchCmdPalExtensionCommandsProvider()
1616
DisplayName = "NuGet Package Search Extension";
1717
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.png");
1818
_commands = [
19-
new CommandItem(new Pages.NuGetPackageSearchCmdPalExtensionPage()) { Title = "Search NuGet Packages" },
19+
new CommandItem(new Pages.SearchNuGetPackagesPage()) { Title = "Search NuGet Packages" },
20+
new CommandItem(new Pages.SearchDotnetTemplatesPage()) {Title = "Search Dotnet Templates"}
2021
];
2122
}
2223

src/NuGetPackageSearchCmdPalExtension/Package.appxmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<Identity
1313
Name="23610AlickolliSoftware.NuGetPackageSearchforComman"
1414
Publisher="CN=5CECD841-CF4E-45AF-B443-573F4A3614A0"
15-
Version="0.0.1.0" />
15+
Version="0.0.2.0" />
1616
<!-- When you're ready to publish your extension, you'll need to change the
1717
Publisher= to match your own identity -->
1818

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using Microsoft.CommandPalette.Extensions;
2+
using Microsoft.CommandPalette.Extensions.Toolkit;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using System.Net.Http;
8+
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using System.Threading.Tasks.Dataflow;
12+
using Windows.ApplicationModel;
13+
14+
namespace NuGetPackageSearchCmdPalExtension.Pages
15+
{
16+
internal sealed partial class SearchDotnetTemplatesPage : DynamicListPage, IDisposable
17+
{
18+
private bool _isError;
19+
private readonly CancellationTokenSource _cancellationTokenSource = new();
20+
private readonly BufferBlock<string> _searchTextBuffer = new();
21+
private IReadOnlyList<ListItem> _results = [];
22+
23+
public SearchDotnetTemplatesPage()
24+
{
25+
// Retrieve the app version
26+
var version = Package.Current.Id.Version;
27+
var appVersion = $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
28+
29+
Icon = new IconInfo("\uE773");
30+
Title = $"NuGet Package Search Extension - v{appVersion}";
31+
Name = "Search";
32+
33+
// Configure the search processing pipeline
34+
Task.Run(async () =>
35+
{
36+
while (await _searchTextBuffer.OutputAvailableAsync(_cancellationTokenSource.Token))
37+
{
38+
var searchText = await _searchTextBuffer.ReceiveAsync(_cancellationTokenSource.Token);
39+
IsLoading = true;
40+
try
41+
{
42+
_results = await ProcessSearchAsync(searchText, _cancellationTokenSource.Token);
43+
_isError = false;
44+
}
45+
catch
46+
{
47+
_isError = true;
48+
_results = [];
49+
}
50+
finally
51+
{
52+
IsLoading = false;
53+
RaiseItemsChanged(_results!.Count);
54+
}
55+
}
56+
}, _cancellationTokenSource.Token);
57+
}
58+
59+
public override IListItem[] GetItems()
60+
{
61+
return [.. _results];
62+
}
63+
64+
public override void UpdateSearchText(string oldSearch, string newSearch)
65+
{
66+
if (newSearch == oldSearch) return;
67+
_searchTextBuffer.Post(newSearch);
68+
}
69+
70+
public void Dispose()
71+
{
72+
_cancellationTokenSource.Cancel();
73+
_cancellationTokenSource.Dispose();
74+
}
75+
76+
public override ICommandItem? EmptyContent
77+
{
78+
get
79+
{
80+
if (_isError)
81+
{
82+
return new CommandItem
83+
{
84+
Title = "Error loading dotnet templates",
85+
Icon = new IconInfo("\uea39"),
86+
Subtitle = "An error occurred while fetching dotnet templates.",
87+
Command = new NoOpCommand()
88+
};
89+
}
90+
91+
return new CommandItem
92+
{
93+
Title = "Search for a dotnet template",
94+
Icon = new IconInfo("\uea39"),
95+
Subtitle = "Search for a dotnet template",
96+
Command = new NoOpCommand()
97+
}; ;
98+
}
99+
}
100+
101+
private static async Task<IReadOnlyList<ListItem>> ProcessSearchAsync(string searchText, CancellationToken cancellationToken)
102+
{
103+
await Task.Yield(); // Simulate asynchronous behavior
104+
105+
var url = $"https://azuresearch-usnc.nuget.org/query?take=20&packageType=Template&q={Uri.EscapeDataString(searchText)}";
106+
using var httpClient = new HttpClient();
107+
using var response = await httpClient.GetAsync(url, cancellationToken);
108+
response.EnsureSuccessStatusCode();
109+
110+
var json = await response.Content.ReadAsStringAsync(cancellationToken);
111+
112+
using var doc = System.Text.Json.JsonDocument.Parse(json);
113+
var results = new List<ListItem>();
114+
115+
if (!doc.RootElement.TryGetProperty("data", out var dataElement) ||
116+
dataElement.ValueKind != System.Text.Json.JsonValueKind.Array) return results;
117+
118+
foreach (var package in dataElement.EnumerateArray())
119+
{
120+
var id = package.GetProperty("id").GetString();
121+
var version = package.GetProperty("version").GetString();
122+
string? iconUrl = null;
123+
if (package.TryGetProperty("iconUrl", out var iconUrlProp) && iconUrlProp.ValueKind == System.Text.Json.JsonValueKind.String)
124+
{
125+
iconUrl = iconUrlProp.GetString();
126+
}
127+
// Fallback to a glyph if iconUrl is missing
128+
var icon = !string.IsNullOrEmpty(iconUrl) ? new IconInfo(iconUrl) : new IconInfo("\uE7B8");
129+
results.Add(new ListItem
130+
{
131+
Title = id!,
132+
Subtitle = version!,
133+
Icon = icon,
134+
Command = new CopyTextCommand($"dotnet new install {id}::{version}") { Name = "Copy install Command" },
135+
MoreCommands =
136+
[
137+
new CommandContextItem(new AnonymousCommand(() =>
138+
{
139+
var startInfo = new ProcessStartInfo
140+
{
141+
FileName = "cmd.exe",
142+
Arguments = $"/k dotnet new install {id}::{version} --force ", // Use /k to keep the shell open
143+
UseShellExecute = true,
144+
CreateNoWindow = false
145+
};
146+
147+
using var process = new Process();
148+
process.StartInfo = startInfo;
149+
process.Start();
150+
process.WaitForExit();
151+
}){Icon = new IconInfo("\uE896"), Name = "Install Template"})
152+
]
153+
});
154+
}
155+
156+
return results;
157+
}
158+
}
159+
}

src/NuGetPackageSearchCmdPalExtension/Pages/NuGetPackageSearchCmdPalExtensionPage.cs renamed to src/NuGetPackageSearchCmdPalExtension/Pages/SearchNuGetPackagesPage.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515

1616
namespace NuGetPackageSearchCmdPalExtension.Pages;
1717

18-
internal sealed partial class NuGetPackageSearchCmdPalExtensionPage : DynamicListPage, IDisposable
18+
internal sealed partial class SearchNuGetPackagesPage : DynamicListPage, IDisposable
1919
{
2020
private bool _isError;
2121
private readonly CancellationTokenSource _cancellationTokenSource = new();
2222
private readonly BufferBlock<string> _searchTextBuffer = new();
2323
private IReadOnlyList<ListItem> _results = [];
2424

25-
public NuGetPackageSearchCmdPalExtensionPage()
25+
public SearchNuGetPackagesPage()
2626
{
2727
// Retrieve the app version
2828
var version = Package.Current.Id.Version;

0 commit comments

Comments
 (0)