Compare commits
9 Commits
issue/101-
...
user/rossl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64f5309851 | ||
|
|
a690ea96a5 | ||
|
|
f995e414b7 | ||
|
|
aae601aa36 | ||
|
|
51caa5b50b | ||
|
|
8edaa44cee | ||
|
|
879e03b436 | ||
|
|
bb706fb5f1 | ||
|
|
2ce76b861f |
19
.github/workflows/automatic-issue-deduplication.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Automatic New Issue Deduplication
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
deduplicate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run Deduplicate Action
|
||||
uses: pelikhan/action-genai-issue-dedup@v0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
label_as_duplicate: true
|
||||
@@ -418,7 +418,7 @@ jobs:
|
||||
}
|
||||
|
||||
if ($Packages.Count -gt 0) {
|
||||
# Priority: Look for platform-specific MSIX (x64/arm64) first, then fallback to any
|
||||
# Priority: Look for platform-specific MSIX (x64/arm64) first, then fall back to any
|
||||
$PlatformPackage = $Packages | Where-Object { $_.Name -match "Microsoft\.CmdPal\.UI_.*_(x64|arm64)\.msix$" } | Select-Object -First 1
|
||||
if ($PlatformPackage) {
|
||||
$Package = $PlatformPackage
|
||||
|
||||
@@ -67,7 +67,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
private bool _isDynamic;
|
||||
|
||||
private Task? _initializeItemsTask;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private CancellationTokenSource? _fetchItemsCancellationTokenSource;
|
||||
|
||||
@@ -200,13 +199,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
// Check for cancellation before initializing first twenty items
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var firstTwenty = newViewModels.Take(20);
|
||||
foreach (var item in firstTwenty)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
item?.SafeInitializeProperties();
|
||||
}
|
||||
|
||||
// Cancel any ongoing search
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
@@ -262,18 +254,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_initializeItemsTask = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeItemsTask(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
});
|
||||
_initializeItemsTask.Start();
|
||||
|
||||
DoOnUiThread(
|
||||
() =>
|
||||
{
|
||||
@@ -301,32 +281,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeItemsTask(CancellationToken ct)
|
||||
{
|
||||
// Were we already canceled?
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
ListItemViewModel[] iterable;
|
||||
lock (_listLock)
|
||||
{
|
||||
iterable = Items.ToArray();
|
||||
}
|
||||
|
||||
foreach (var item in iterable)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// TODO: GH #502
|
||||
// We should probably remove the item from the list if it
|
||||
// entered the error state. I had issues doing that without having
|
||||
// multiple threads muck with `Items` (and possibly FilteredItems!)
|
||||
// at once.
|
||||
item.SafeInitializeProperties();
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply our current filter text to the list of items, and update
|
||||
/// FilteredItems to match the results.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class IconMarginConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
// Only include a margin if there is text to separate from the icon.
|
||||
var text = value as string;
|
||||
return string.IsNullOrEmpty(text) ? new Thickness(0) : new Thickness(0, 0, 4, 0);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -27,6 +27,8 @@
|
||||
<Thickness x:Key="TagPadding">4,2,4,2</Thickness>
|
||||
<Thickness x:Key="TagBorderThickness">1</Thickness>
|
||||
|
||||
<local:IconMarginConverter x:Key="IconMarginConverter" />
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultTagStyle}" TargetType="local:Tag" />
|
||||
|
||||
<Style x:Key="DefaultTagStyle" TargetType="local:Tag">
|
||||
@@ -71,7 +73,7 @@
|
||||
x:Name="PART_Icon"
|
||||
Grid.Column="0"
|
||||
Height="12"
|
||||
Margin="0,0,4,0"
|
||||
Margin="{Binding Text, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IconMarginConverter}}"
|
||||
SourceKey="{TemplateBinding Icon}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
|
||||
@@ -276,6 +276,7 @@
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Items_ItemClick"
|
||||
ContainerContentChanging="ItemsList_ContainerContentChanging"
|
||||
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
RightTapped="Items_RightTapped"
|
||||
@@ -295,6 +296,7 @@
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Items_ItemClick"
|
||||
ContainerContentChanging="ItemsList_ContainerContentChanging"
|
||||
ItemTemplateSelector="{StaticResource GridItemTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
RightTapped="Items_RightTapped"
|
||||
|
||||
@@ -427,4 +427,12 @@ public sealed partial class ListPage : Page,
|
||||
Keyboard,
|
||||
Pointer,
|
||||
}
|
||||
|
||||
private void ItemsList_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
|
||||
{
|
||||
if (args.Item is ListItemViewModel item)
|
||||
{
|
||||
_ = Task.Run(() => item.SafeInitializeProperties());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.08535 3C7.29127 2.4174 7.84689 2 8.5 2H11.5C12.1531 2 12.7087 2.4174 12.9146 3H14.5C15.3284 3 16 3.67157 16 4.5V16.5C16 17.3284 15.3284 18 14.5 18H5.5C4.67157 18 4 17.3284 4 16.5V4.5C4 3.67157 4.67157 3 5.5 3H7.08535ZM8.5 3C8.22386 3 8 3.22386 8 3.5C8 3.77614 8.22386 4 8.5 4H11.5C11.7761 4 12 3.77614 12 3.5C12 3.22386 11.7761 3 11.5 3H8.5ZM7.08535 4H5.5C5.22386 4 5 4.22386 5 4.5V16.5C5 16.7761 5.22386 17 5.5 17H14.5C14.7761 17 15 16.7761 15 16.5V4.5C15 4.22386 14.7761 4 14.5 4H12.9146C12.7087 4.5826 12.1531 5 11.5 5H8.5C7.84689 5 7.29127 4.5826 7.08535 4Z" fill="#D0D0D0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 695 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.08535 3C7.29127 2.4174 7.84689 2 8.5 2H11.5C12.1531 2 12.7087 2.4174 12.9146 3H14.5C15.3284 3 16 3.67157 16 4.5V16.5C16 17.3284 15.3284 18 14.5 18H5.5C4.67157 18 4 17.3284 4 16.5V4.5C4 3.67157 4.67157 3 5.5 3H7.08535ZM8.5 3C8.22386 3 8 3.22386 8 3.5C8 3.77614 8.22386 4 8.5 4H11.5C11.7761 4 12 3.77614 12 3.5C12 3.22386 11.7761 3 11.5 3H8.5ZM7.08535 4H5.5C5.22386 4 5 4.22386 5 4.5V16.5C5 16.7761 5.22386 17 5.5 17H14.5C14.7761 17 15 16.7761 15 16.5V4.5C15 4.22386 14.7761 4 14.5 4H12.9146C12.7087 4.5826 12.1531 5 11.5 5H8.5C7.84689 5 7.29127 4.5826 7.08535 4Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 695 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.08535 3C7.29127 2.4174 7.84689 2 8.5 2H11.5C12.1531 2 12.7087 2.4174 12.9146 3H14.5C15.3284 3 16 3.67157 16 4.5V9H15V4.5C15 4.22386 14.7761 4 14.5 4H12.9146C12.7087 4.5826 12.1531 5 11.5 5H8.5C7.84689 5 7.29127 4.5826 7.08535 4H5.5C5.22386 4 5 4.22386 5 4.5V16.5C5 16.7761 5.22386 17 5.5 17H9.03544C9.08595 17.3531 9.18915 17.6891 9.33682 18H5.5C4.67157 18 4 17.3284 4 16.5V4.5C4 3.67157 4.67157 3 5.5 3H7.08535ZM8.5 3C8.22386 3 8 3.22386 8 3.5C8 3.77614 8.22386 4 8.5 4H11.5C11.7761 4 12 3.77614 12 3.5C12 3.22386 11.7761 3 11.5 3H8.5ZM10 12.5C10 11.1193 11.1193 10 12.5 10H16.5C17.8807 10 19 11.1193 19 12.5V16.5C19 17.0095 18.8476 17.4835 18.5858 17.8787L15.5607 14.8536C14.9749 14.2678 14.0251 14.2678 13.4393 14.8536L10.4142 17.8787C10.1524 17.4835 10 17.0095 10 16.5V12.5ZM17 12.75C17 12.3358 16.6642 12 16.25 12C15.8358 12 15.5 12.3358 15.5 12.75C15.5 13.1642 15.8358 13.5 16.25 13.5C16.6642 13.5 17 13.1642 17 12.75ZM11.1213 18.5858C11.5165 18.8476 11.9905 19 12.5 19H16.5C17.0095 19 17.4835 18.8476 17.8787 18.5858L14.8536 15.5607C14.6583 15.3654 14.3417 15.3654 14.1464 15.5607L11.1213 18.5858Z" fill="#D0D0D0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.08535 3C7.29127 2.4174 7.84689 2 8.5 2H11.5C12.1531 2 12.7087 2.4174 12.9146 3H14.5C15.3284 3 16 3.67157 16 4.5V9H15V4.5C15 4.22386 14.7761 4 14.5 4H12.9146C12.7087 4.5826 12.1531 5 11.5 5H8.5C7.84689 5 7.29127 4.5826 7.08535 4H5.5C5.22386 4 5 4.22386 5 4.5V16.5C5 16.7761 5.22386 17 5.5 17H9.03544C9.08595 17.3531 9.18915 17.6891 9.33682 18H5.5C4.67157 18 4 17.3284 4 16.5V4.5C4 3.67157 4.67157 3 5.5 3H7.08535ZM8.5 3C8.22386 3 8 3.22386 8 3.5C8 3.77614 8.22386 4 8.5 4H11.5C11.7761 4 12 3.77614 12 3.5C12 3.22386 11.7761 3 11.5 3H8.5ZM10 12.5C10 11.1193 11.1193 10 12.5 10H16.5C17.8807 10 19 11.1193 19 12.5V16.5C19 17.0095 18.8476 17.4835 18.5858 17.8787L15.5607 14.8536C14.9749 14.2678 14.0251 14.2678 13.4393 14.8536L10.4142 17.8787C10.1524 17.4835 10 17.0095 10 16.5V12.5ZM17 12.75C17 12.3358 16.6642 12 16.25 12C15.8358 12 15.5 12.3358 15.5 12.75C15.5 13.1642 15.8358 13.5 16.25 13.5C16.6642 13.5 17 13.1642 17 12.75ZM11.1213 18.5858C11.5165 18.8476 11.9905 19 12.5 19H16.5C17.0095 19 17.4835 18.8476 17.8787 18.5858L14.8536 15.5607C14.6583 15.3654 14.3417 15.3654 14.1464 15.5607L11.1213 18.5858Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.08535 3C7.29127 2.4174 7.84689 2 8.5 2H11.5C12.1531 2 12.7087 2.4174 12.9146 3H14.5C15.3284 3 16 3.67157 16 4.5V10.337L15.3698 8.89819C15.2826 8.69904 15.1554 8.52562 15 8.38568V4.5C15 4.22386 14.7761 4 14.5 4H12.9146C12.7087 4.5826 12.1531 5 11.5 5H8.5C7.84689 5 7.29127 4.5826 7.08535 4H5.5C5.22386 4 5 4.22386 5 4.5V16.5C5 16.7761 5.22386 17 5.5 17H9.08561C8.9672 17.3338 8.97447 17.6857 9.08567 18H5.5C4.67157 18 4 17.3284 4 16.5V4.5C4 3.67157 4.67157 3 5.5 3H7.08535ZM8.5 3C8.22386 3 8 3.22386 8 3.5C8 3.77614 8.22386 4 8.5 4H11.5C11.7761 4 12 3.77614 12 3.5C12 3.22386 11.7761 3 11.5 3H8.5ZM14.4538 9.2994C14.3741 9.11744 14.1942 8.99992 13.9956 9C13.797 9.00008 13.6172 9.11776 13.5376 9.29979L10.0417 17.2998C9.93114 17.5528 10.0466 17.8476 10.2997 17.9582C10.5527 18.0687 10.8475 17.9533 10.958 17.7002L12.138 15H15.859L17.0419 17.7006C17.1527 17.9535 17.4475 18.0688 17.7005 17.958C17.9534 17.8472 18.0687 17.5523 17.9579 17.2994L14.4538 9.2994ZM15.421 14H12.575L13.9963 10.7474L15.421 14Z" fill="#D0D0D0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.08535 3C7.29127 2.4174 7.84689 2 8.5 2H11.5C12.1531 2 12.7087 2.4174 12.9146 3H14.5C15.3284 3 16 3.67157 16 4.5V10.337L15.3698 8.89819C15.2826 8.69904 15.1554 8.52562 15 8.38568V4.5C15 4.22386 14.7761 4 14.5 4H12.9146C12.7087 4.5826 12.1531 5 11.5 5H8.5C7.84689 5 7.29127 4.5826 7.08535 4H5.5C5.22386 4 5 4.22386 5 4.5V16.5C5 16.7761 5.22386 17 5.5 17H9.08561C8.9672 17.3338 8.97447 17.6857 9.08567 18H5.5C4.67157 18 4 17.3284 4 16.5V4.5C4 3.67157 4.67157 3 5.5 3H7.08535ZM8.5 3C8.22386 3 8 3.22386 8 3.5C8 3.77614 8.22386 4 8.5 4H11.5C11.7761 4 12 3.77614 12 3.5C12 3.22386 11.7761 3 11.5 3H8.5ZM14.4538 9.2994C14.3741 9.11744 14.1942 8.99992 13.9956 9C13.797 9.00008 13.6172 9.11776 13.5376 9.29979L10.0417 17.2998C9.93114 17.5528 10.0466 17.8476 10.2997 17.9582C10.5527 18.0687 10.8475 17.9533 10.958 17.7002L12.138 15H15.859L17.0419 17.7006C17.1527 17.9535 17.4475 18.0688 17.7005 17.958C17.9534 17.8472 18.0687 17.5523 17.9579 17.2994L14.4538 9.2994ZM15.421 14H12.575L13.9963 10.7474L15.421 14Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 2C6.89543 2 6 2.89543 6 4V14C6 15.1046 6.89543 16 8 16H14C15.1046 16 16 15.1046 16 14V4C16 2.89543 15.1046 2 14 2H8ZM7 4C7 3.44772 7.44772 3 8 3H14C14.5523 3 15 3.44772 15 4V14C15 14.5523 14.5523 15 14 15H8C7.44772 15 7 14.5523 7 14V4ZM4 6.00001C4 5.25973 4.4022 4.61339 5 4.26758V14.5C5 15.8807 6.11929 17 7.5 17H13.7324C13.3866 17.5978 12.7403 18 12 18H7.5C5.567 18 4 16.433 4 14.5V6.00001Z" fill="#D0D0D0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 526 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 2C6.89543 2 6 2.89543 6 4V14C6 15.1046 6.89543 16 8 16H14C15.1046 16 16 15.1046 16 14V4C16 2.89543 15.1046 2 14 2H8ZM7 4C7 3.44772 7.44772 3 8 3H14C14.5523 3 15 3.44772 15 4V14C15 14.5523 14.5523 15 14 15H8C7.44772 15 7 14.5523 7 14V4ZM4 6.00001C4 5.25973 4.4022 4.61339 5 4.26758V14.5C5 15.8807 6.11929 17 7.5 17H13.7324C13.3866 17.5978 12.7403 18 12 18H7.5C5.567 18 4 16.433 4 14.5V6.00001Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 526 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4C6 2.89543 6.89543 2 8 2H11.5858C11.9836 2 12.3651 2.15804 12.6464 2.43934L16.5607 6.35355C16.842 6.63486 17 7.01639 17 7.41421V14C17 15.1046 16.1046 16 15 16H8C6.89543 16 6 15.1046 6 14V4ZM8 3C7.44772 3 7 3.44772 7 4V14C7 14.5523 7.44772 15 8 15H15C15.5523 15 16 14.5523 16 14V8H12.5C11.6716 8 11 7.32843 11 6.5V3H8ZM12 3.20711V6.5C12 6.77614 12.2239 7 12.5 7H15.7929L12 3.20711ZM4 5C4 4.44772 4.44772 4 5 4V14C5 15.6569 6.34315 17 8 17L15 17C15 17.5523 14.5523 18 14 18H7.93939C5.76373 18 4 16.2363 4 14.0606V5Z" fill="#D0D0D0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 648 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4C6 2.89543 6.89543 2 8 2H11.5858C11.9836 2 12.3651 2.15804 12.6464 2.43934L16.5607 6.35355C16.842 6.63486 17 7.01639 17 7.41421V14C17 15.1046 16.1046 16 15 16H8C6.89543 16 6 15.1046 6 14V4ZM8 3C7.44772 3 7 3.44772 7 4V14C7 14.5523 7.44772 15 8 15H15C15.5523 15 16 14.5523 16 14V8H12.5C11.6716 8 11 7.32843 11 6.5V3H8ZM12 3.20711V6.5C12 6.77614 12.2239 7 12.5 7H15.7929L12 3.20711ZM4 5C4 4.44772 4.44772 4 5 4V14C5 15.6569 6.34315 17 8 17L15 17C15 17.5523 14.5523 18 14 18H7.93939C5.76373 18 4 16.2363 4 14.0606V5Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 648 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.49751 7.4966C9.04842 7.4966 9.49502 7.05 9.49502 6.4991C9.49502 5.94819 9.04842 5.50159 8.49751 5.50159C7.9466 5.50159 7.5 5.94819 7.5 6.4991C7.5 7.05 7.9466 7.4966 8.49751 7.4966ZM5 6C5 4.34315 6.34315 3 8 3H14C15.6569 3 17 4.34315 17 6V12C17 13.6569 15.6569 15 14 15H8C6.34315 15 5 13.6569 5 12V6ZM8 4C6.89543 4 6 4.89543 6 6V12C6 12.3709 6.10097 12.7182 6.27692 13.016L9.79085 9.50207C10.4586 8.83427 11.5414 8.83427 12.2092 9.50207L15.7231 13.016C15.899 12.7182 16 12.3709 16 12V6C16 4.89543 15.1046 4 14 4H8ZM15.016 13.7231L11.502 10.2092C11.2248 9.9319 10.7752 9.9319 10.498 10.2092L6.98403 13.7231C7.28178 13.899 7.6291 14 8 14H14C14.3709 14 14.7182 13.899 15.016 13.7231ZM12 17C12.8885 17 13.6868 16.6138 14.2361 16H7.5C5.68782 16 4.1973 14.6228 4.01807 12.8579C4.00612 12.7402 4 12.6208 4 12.5V5.76392C3.38625 6.31324 3 7.11152 3 8.00002V12.5C3 14.9853 5.01472 17 7.5 17H12Z" fill="#D0D0D0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1017 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.49751 7.4966C9.04842 7.4966 9.49502 7.05 9.49502 6.4991C9.49502 5.94819 9.04842 5.50159 8.49751 5.50159C7.9466 5.50159 7.5 5.94819 7.5 6.4991C7.5 7.05 7.9466 7.4966 8.49751 7.4966ZM5 6C5 4.34315 6.34315 3 8 3H14C15.6569 3 17 4.34315 17 6V12C17 13.6569 15.6569 15 14 15H8C6.34315 15 5 13.6569 5 12V6ZM8 4C6.89543 4 6 4.89543 6 6V12C6 12.3709 6.10097 12.7182 6.27692 13.016L9.79085 9.50207C10.4586 8.83427 11.5414 8.83427 12.2092 9.50207L15.7231 13.016C15.899 12.7182 16 12.3709 16 12V6C16 4.89543 15.1046 4 14 4H8ZM15.016 13.7231L11.502 10.2092C11.2248 9.9319 10.7752 9.9319 10.498 10.2092L6.98403 13.7231C7.28178 13.899 7.6291 14 8 14H14C14.3709 14 14.7182 13.899 15.016 13.7231ZM12 17C12.8885 17 13.6868 16.6138 14.2361 16H7.5C5.68782 16 4.1973 14.6228 4.01807 12.8579C4.00612 12.7402 4 12.6208 4 12.5V5.76392C3.38625 6.31324 3 7.11152 3 8.00002V12.5C3 14.9853 5.01472 17 7.5 17H12Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1017 B |
@@ -0,0 +1,12 @@
|
||||
// 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.ClipboardHistory.Helpers;
|
||||
|
||||
internal enum PrimaryAction
|
||||
{
|
||||
Default,
|
||||
Paste,
|
||||
Copy,
|
||||
}
|
||||
@@ -2,6 +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.CmdPal.Ext.ClipboardHistory.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -26,10 +27,22 @@ internal sealed class SettingsManager : JsonSettingsManager, ISettingOptions
|
||||
Resources.settings_confirm_delete_description!,
|
||||
true);
|
||||
|
||||
private readonly ChoiceSetSetting _primaryAction = new(
|
||||
Namespaced(nameof(PrimaryAction)),
|
||||
Resources.settings_primary_action_title!,
|
||||
Resources.settings_primary_action_description!,
|
||||
[
|
||||
new ChoiceSetSetting.Choice(Resources.settings_primary_action_default!, PrimaryAction.Default.ToString("G")),
|
||||
new ChoiceSetSetting.Choice(Resources.settings_primary_action_paste!, PrimaryAction.Paste.ToString("G")),
|
||||
new ChoiceSetSetting.Choice(Resources.settings_primary_action_copy!, PrimaryAction.Copy.ToString("G"))
|
||||
]);
|
||||
|
||||
public bool KeepAfterPaste => _keepAfterPaste.Value;
|
||||
|
||||
public bool DeleteFromHistoryRequiresConfirmation => _confirmDelete.Value;
|
||||
|
||||
public PrimaryAction PrimaryAction => Enum.TryParse<PrimaryAction>(_primaryAction.Value, out var action) ? action : PrimaryAction.Default;
|
||||
|
||||
private static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
@@ -45,6 +58,7 @@ internal sealed class SettingsManager : JsonSettingsManager, ISettingOptions
|
||||
|
||||
Settings.Add(_keepAfterPaste);
|
||||
Settings.Add(_confirmDelete);
|
||||
Settings.Add(_primaryAction);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
@@ -6,7 +6,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
|
||||
internal sealed class Icons
|
||||
internal static class Icons
|
||||
{
|
||||
internal static IconInfo CopyIcon { get; } = new("\xE8C8");
|
||||
|
||||
@@ -17,4 +17,21 @@ internal sealed class Icons
|
||||
internal static IconInfo DeleteIcon { get; } = new("\uE74D");
|
||||
|
||||
internal static IconInfo ClipboardListIcon { get; } = IconHelpers.FromRelativePath("Assets\\ClipboardHistory.svg");
|
||||
|
||||
internal static IconInfo Clipboard { get; } = Create("ic_fluent_clipboard_20_regular");
|
||||
|
||||
internal static IconInfo ClipboardImage { get; } = Create("ic_fluent_clipboard_image_20_regular");
|
||||
|
||||
internal static IconInfo ClipboardLetter { get; } = Create("ic_fluent_clipboard_letter_20_regular");
|
||||
|
||||
internal static IconInfo Copy { get; } = Create(" ic_fluent_copy_20_regular");
|
||||
|
||||
internal static IconInfo DocumentCopy { get; } = Create("ic_fluent_document_copy_20_regular");
|
||||
|
||||
internal static IconInfo ImageCopy { get; } = Create("ic_fluent_image_copy_20_regular");
|
||||
|
||||
private static IconInfo Create(string name)
|
||||
{
|
||||
return IconHelpers.FromRelativePaths($"Assets\\Icons\\{name}.light.svg", $"Assets\\Icons\\{name}.dark.svg");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,5 +39,41 @@
|
||||
<Content Update="Assets\ClipboardHistory.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_clipboard_20_regular.dark.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_clipboard_20_regular.light.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_clipboard_image_20_regular.dark.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_clipboard_image_20_regular.light.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_clipboard_letter_20_regular.dark.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_clipboard_letter_20_regular.light.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_copy_20_regular.dark.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_copy_20_regular.light.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_document_copy_20_regular.dark.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_document_copy_20_regular.light.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_image_copy_20_regular.dark.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Icons\ic_fluent_image_copy_20_regular.light.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -3,14 +3,8 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Common.Commands;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Commands;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
@@ -41,126 +35,8 @@ public class ClipboardItem
|
||||
}
|
||||
|
||||
[MemberNotNullWhen(true, nameof(ImageData))]
|
||||
private bool IsImage => ImageData is not null;
|
||||
internal bool IsImage => ImageData is not null;
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Content))]
|
||||
private bool IsText => !string.IsNullOrEmpty(Content);
|
||||
|
||||
public static List<string> ShiftLinesLeft(List<string> lines)
|
||||
{
|
||||
// Determine the minimum leading whitespace
|
||||
var minLeadingWhitespace = lines
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
||||
.Min(line => line.TakeWhile(char.IsWhiteSpace).Count());
|
||||
|
||||
// Check if all lines have at least that much leading whitespace
|
||||
if (lines.Any(line => line.TakeWhile(char.IsWhiteSpace).Count() < minLeadingWhitespace))
|
||||
{
|
||||
return lines; // Return the original lines if any line doesn't have enough leading whitespace
|
||||
}
|
||||
|
||||
// Remove the minimum leading whitespace from each line
|
||||
var shiftedLines = lines.Select(line => line.Substring(minLeadingWhitespace)).ToList();
|
||||
|
||||
return shiftedLines;
|
||||
}
|
||||
|
||||
public static List<string> StripLeadingWhitespace(List<string> lines)
|
||||
{
|
||||
// Determine the minimum leading whitespace
|
||||
var minLeadingWhitespace = lines
|
||||
.Min(line => line.TakeWhile(char.IsWhiteSpace).Count());
|
||||
|
||||
// Remove the minimum leading whitespace from each line
|
||||
var shiftedLines = lines.Select(line =>
|
||||
line.Length >= minLeadingWhitespace
|
||||
? line.Substring(minLeadingWhitespace)
|
||||
: line).ToList();
|
||||
|
||||
return shiftedLines;
|
||||
}
|
||||
|
||||
public ListItem ToListItem()
|
||||
{
|
||||
ListItem listItem;
|
||||
|
||||
List<DetailsElement> metadata = [];
|
||||
metadata.Add(new DetailsElement()
|
||||
{
|
||||
Key = "Copied on",
|
||||
Data = new DetailsLink(Item.Timestamp.DateTime.ToString(DateTimeFormatInfo.CurrentInfo)),
|
||||
});
|
||||
|
||||
var deleteConfirmationCommand = new ConfirmableCommand()
|
||||
{
|
||||
Command = new DeleteItemCommand(this),
|
||||
ConfirmationTitle = Properties.Resources.delete_confirmation_title!,
|
||||
ConfirmationMessage = Properties.Resources.delete_confirmation_message!,
|
||||
IsConfirmationRequired = () => Settings.DeleteFromHistoryRequiresConfirmation,
|
||||
};
|
||||
var deleteContextMenuItem = new CommandContextItem(deleteConfirmationCommand)
|
||||
{
|
||||
IsCritical = true,
|
||||
RequestedShortcut = KeyChords.DeleteEntry,
|
||||
};
|
||||
|
||||
if (IsImage)
|
||||
{
|
||||
var iconData = new IconData(ImageData);
|
||||
var heroImage = new IconInfo(iconData, iconData);
|
||||
listItem = new(new CopyCommand(this, ClipboardFormat.Image))
|
||||
{
|
||||
// Placeholder subtitle as there’s no BitmapImage dimensions to retrieve
|
||||
Title = "Image Data",
|
||||
Details = new Details()
|
||||
{
|
||||
HeroImage = heroImage,
|
||||
Title = GetDataType(),
|
||||
Body = Timestamp.ToString(CultureInfo.InvariantCulture),
|
||||
Metadata = metadata.ToArray(),
|
||||
},
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Image, Settings)),
|
||||
new Separator(),
|
||||
deleteContextMenuItem,
|
||||
],
|
||||
};
|
||||
}
|
||||
else if (IsText)
|
||||
{
|
||||
var splitContent = Content.Split("\n");
|
||||
var head = splitContent.AsSpan(0, Math.Min(3, splitContent.Length)).ToArray().ToList();
|
||||
var preview2 = string.Join(
|
||||
"\n",
|
||||
StripLeadingWhitespace(head));
|
||||
|
||||
listItem = new(new CopyCommand(this, ClipboardFormat.Text))
|
||||
{
|
||||
Title = preview2,
|
||||
|
||||
Details = new Details
|
||||
{
|
||||
Title = GetDataType(),
|
||||
Body = $"```text\n{Content}\n```",
|
||||
Metadata = metadata.ToArray(),
|
||||
},
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new PasteCommand(this, ClipboardFormat.Text, Settings)),
|
||||
new Separator(),
|
||||
deleteContextMenuItem,
|
||||
],
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
listItem = new(new NoOpCommand())
|
||||
{
|
||||
Title = "Unknown",
|
||||
Subtitle = GetDataType(),
|
||||
Details = new Details { Title = GetDataType() },
|
||||
};
|
||||
}
|
||||
|
||||
return listItem;
|
||||
}
|
||||
internal bool IsText => !string.IsNullOrEmpty(Content);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ internal sealed partial class ClipboardHistoryListPage : ListPage
|
||||
var item = clipboardHistory[i];
|
||||
if (item is not null)
|
||||
{
|
||||
listItems.Add(item.ToListItem());
|
||||
listItems.Add(new ClipboardListItem(item, _settingsManager));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// 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.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Common.Commands;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Commands;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Models;
|
||||
|
||||
internal sealed partial class ClipboardListItem : ListItem
|
||||
{
|
||||
private readonly SettingsManager _settingsManager;
|
||||
private readonly ClipboardItem _item;
|
||||
|
||||
private readonly CommandContextItem _deleteContextMenuItem;
|
||||
private readonly CommandContextItem? _pasteCommand;
|
||||
private readonly CommandContextItem? _copyCommand;
|
||||
private readonly Lazy<Details> _lazyDetails;
|
||||
|
||||
public override IDetails? Details
|
||||
{
|
||||
get => _lazyDetails.Value;
|
||||
set
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public ClipboardListItem(ClipboardItem item, SettingsManager settingsManager)
|
||||
{
|
||||
_item = item;
|
||||
_settingsManager = settingsManager;
|
||||
_settingsManager.Settings.SettingsChanged += SettingsOnSettingsChanged;
|
||||
|
||||
_lazyDetails = new(() => CreateDetails());
|
||||
|
||||
var deleteConfirmationCommand = new ConfirmableCommand
|
||||
{
|
||||
Command = new DeleteItemCommand(_item),
|
||||
ConfirmationTitle = Properties.Resources.delete_confirmation_title!,
|
||||
ConfirmationMessage = Properties.Resources.delete_confirmation_message!,
|
||||
IsConfirmationRequired = () => _settingsManager.DeleteFromHistoryRequiresConfirmation,
|
||||
};
|
||||
_deleteContextMenuItem = new CommandContextItem(deleteConfirmationCommand)
|
||||
{
|
||||
IsCritical = true,
|
||||
RequestedShortcut = KeyChords.DeleteEntry,
|
||||
};
|
||||
|
||||
if (item.IsImage)
|
||||
{
|
||||
Title = "Image";
|
||||
|
||||
_pasteCommand = new CommandContextItem(new PasteCommand(_item, ClipboardFormat.Image, _settingsManager));
|
||||
_copyCommand = new CommandContextItem(new CopyCommand(_item, ClipboardFormat.Image));
|
||||
}
|
||||
else if (item.IsText)
|
||||
{
|
||||
var splitContent = _item.Content?.Split("\n") ?? [];
|
||||
var head = splitContent.Take(3);
|
||||
var preview2 = string.Join(
|
||||
"\n",
|
||||
StripLeadingWhitespace(head));
|
||||
|
||||
Title = preview2;
|
||||
|
||||
_pasteCommand = new CommandContextItem(new PasteCommand(_item, ClipboardFormat.Text, _settingsManager));
|
||||
_copyCommand = new CommandContextItem(new CopyCommand(_item, ClipboardFormat.Text));
|
||||
}
|
||||
else
|
||||
{
|
||||
_pasteCommand = null;
|
||||
_copyCommand = null;
|
||||
}
|
||||
|
||||
RefreshCommands();
|
||||
}
|
||||
|
||||
private void SettingsOnSettingsChanged(object sender, Settings args)
|
||||
{
|
||||
RefreshCommands();
|
||||
}
|
||||
|
||||
private void RefreshCommands()
|
||||
{
|
||||
if (_item is { IsText: false, IsImage: false })
|
||||
{
|
||||
MoreCommands = [_deleteContextMenuItem];
|
||||
Icon = _settingsManager.PrimaryAction == PrimaryAction.Paste ? Icons.Clipboard : Icons.Copy;
|
||||
}
|
||||
|
||||
switch (_settingsManager.PrimaryAction)
|
||||
{
|
||||
case PrimaryAction.Paste:
|
||||
Command = _pasteCommand?.Command;
|
||||
MoreCommands =
|
||||
[
|
||||
_copyCommand!,
|
||||
new Separator(),
|
||||
_deleteContextMenuItem,
|
||||
];
|
||||
|
||||
if (_item.IsText)
|
||||
{
|
||||
Icon = Icons.ClipboardLetter;
|
||||
}
|
||||
else if (_item.IsImage)
|
||||
{
|
||||
Icon = Icons.ClipboardImage;
|
||||
}
|
||||
else
|
||||
{
|
||||
Icon = Icons.ClipboardImage;
|
||||
}
|
||||
|
||||
break;
|
||||
case PrimaryAction.Default:
|
||||
case PrimaryAction.Copy:
|
||||
default:
|
||||
Command = _copyCommand?.Command;
|
||||
MoreCommands =
|
||||
[
|
||||
_pasteCommand!,
|
||||
new Separator(),
|
||||
_deleteContextMenuItem,
|
||||
];
|
||||
|
||||
if (_item.IsText)
|
||||
{
|
||||
Icon = Icons.DocumentCopy;
|
||||
}
|
||||
else if (_item.IsImage)
|
||||
{
|
||||
Icon = Icons.ImageCopy;
|
||||
}
|
||||
else
|
||||
{
|
||||
Icon = Icons.Copy;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private Details CreateDetails()
|
||||
{
|
||||
IDetailsElement[] metadata =
|
||||
[
|
||||
new DetailsElement
|
||||
{
|
||||
Key = "Copied on",
|
||||
Data = new DetailsLink(_item.Timestamp.DateTime.ToString(DateTimeFormatInfo.CurrentInfo)),
|
||||
}
|
||||
];
|
||||
|
||||
if (_item.IsImage)
|
||||
{
|
||||
var iconData = new IconData(_item.ImageData);
|
||||
var heroImage = new IconInfo(iconData);
|
||||
return new Details
|
||||
{
|
||||
Title = _item.GetDataType(),
|
||||
HeroImage = heroImage,
|
||||
Metadata = metadata,
|
||||
};
|
||||
}
|
||||
|
||||
if (_item.IsText)
|
||||
{
|
||||
return new Details
|
||||
{
|
||||
Title = _item.GetDataType(),
|
||||
Body = $"```text\n{_item.Content}\n```",
|
||||
Metadata = metadata,
|
||||
};
|
||||
}
|
||||
|
||||
return new Details { Title = _item.GetDataType() };
|
||||
}
|
||||
|
||||
private static List<string> StripLeadingWhitespace(IEnumerable<string> lines)
|
||||
{
|
||||
// Determine the minimum leading whitespace
|
||||
var minLeadingWhitespace = lines
|
||||
.Min(static line => line.TakeWhile(char.IsWhiteSpace).Count());
|
||||
|
||||
// Remove the minimum leading whitespace from each line
|
||||
var shiftedLines = lines.Select(line =>
|
||||
line.Length >= minLeadingWhitespace
|
||||
? line[minLeadingWhitespace..]
|
||||
: line).ToList();
|
||||
|
||||
return shiftedLines;
|
||||
}
|
||||
}
|
||||
@@ -212,5 +212,50 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties {
|
||||
return ResourceManager.GetString("settings_keep_after_paste_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy to Clipboard.
|
||||
/// </summary>
|
||||
public static string settings_primary_action_copy {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_primary_action_copy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Default (Copy to Clipboard).
|
||||
/// </summary>
|
||||
public static string settings_primary_action_default {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_primary_action_default", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Primary action (Enter key).
|
||||
/// </summary>
|
||||
public static string settings_primary_action_description {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_primary_action_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Paste.
|
||||
/// </summary>
|
||||
public static string settings_primary_action_paste {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_primary_action_paste", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Primary action.
|
||||
/// </summary>
|
||||
public static string settings_primary_action_title {
|
||||
get {
|
||||
return ResourceManager.GetString("settings_primary_action_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,4 +168,19 @@
|
||||
<data name="delete_confirmation_message" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this item from clipboard history? This action cannot be undone.</value>
|
||||
</data>
|
||||
<data name="settings_primary_action_title" xml:space="preserve">
|
||||
<value>Primary action</value>
|
||||
</data>
|
||||
<data name="settings_primary_action_description" xml:space="preserve">
|
||||
<value>Primary action (Enter key)</value>
|
||||
</data>
|
||||
<data name="settings_primary_action_default" xml:space="preserve">
|
||||
<value>Default (Copy to Clipboard)</value>
|
||||
</data>
|
||||
<data name="settings_primary_action_paste" xml:space="preserve">
|
||||
<value>Paste</value>
|
||||
</data>
|
||||
<data name="settings_primary_action_copy" xml:space="preserve">
|
||||
<value>Copy to Clipboard</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Strongly typed application-level settings for the Windows Terminal extension.
|
||||
/// These are distinct from the dynamic command palette <see cref="JsonSettingsManager"/> based settings
|
||||
/// and are meant for simple persisted state (e.g. last selections).
|
||||
/// </summary>
|
||||
public sealed class AppSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the last selected channel identifier for the Windows Terminal extension.
|
||||
/// Empty string when no channel has been selected yet.
|
||||
/// </summary>
|
||||
[JsonPropertyName("lastSelectedChannel")]
|
||||
public string LastSelectedChannel { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(AppSettings))]
|
||||
internal sealed partial class AppSettingsJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public sealed class AppSettingsManager
|
||||
{
|
||||
private const string FileName = "appsettings.json";
|
||||
|
||||
private static string SettingsPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
return Path.Combine(directory, FileName);
|
||||
}
|
||||
|
||||
private readonly string _filePath;
|
||||
|
||||
public AppSettings Current { get; private set; } = new();
|
||||
|
||||
public AppSettingsManager()
|
||||
{
|
||||
_filePath = SettingsPath();
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_filePath))
|
||||
{
|
||||
var json = File.ReadAllText(_filePath);
|
||||
var loaded = JsonSerializer.Deserialize(json, AppSettingsJsonContext.Default.AppSettings);
|
||||
if (loaded is not null)
|
||||
{
|
||||
Current = loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage { Message = ex.ToString() });
|
||||
Logger.LogError("Failed to load app settings", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(Current, AppSettingsJsonContext.Default.AppSettings);
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage { Message = ex.ToString() });
|
||||
Logger.LogError("Failed to save app settings", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,21 @@ public class SettingsManager : JsonSettingsManager
|
||||
Resources.open_quake_description,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _saveLastSelectedChannel = new(
|
||||
Namespaced(nameof(SaveLastSelectedChannel)),
|
||||
Resources.save_last_selected_channel!,
|
||||
Resources.save_last_selected_channel_description!,
|
||||
false);
|
||||
|
||||
public bool ShowHiddenProfiles => _showHiddenProfiles.Value;
|
||||
|
||||
public bool OpenNewTab => _openNewTab.Value;
|
||||
|
||||
public bool OpenQuake => _openQuake.Value;
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
public bool SaveLastSelectedChannel => _saveLastSelectedChannel.Value;
|
||||
|
||||
private static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
@@ -56,6 +64,7 @@ public class SettingsManager : JsonSettingsManager
|
||||
Settings.Add(_showHiddenProfiles);
|
||||
Settings.Add(_openNewTab);
|
||||
Settings.Add(_openQuake);
|
||||
Settings.Add(_saveLastSelectedChannel);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
@@ -61,7 +61,7 @@ public class TerminalQuery : ITerminalQuery
|
||||
return profiles.OrderBy(p => p.Name);
|
||||
}
|
||||
|
||||
private IEnumerable<TerminalPackage> GetTerminals()
|
||||
public IEnumerable<TerminalPackage> GetTerminals()
|
||||
{
|
||||
var user = WindowsIdentity.GetCurrent().User;
|
||||
var localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA");
|
||||
|
||||
@@ -6,9 +6,11 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal;
|
||||
|
||||
internal sealed class Icons
|
||||
internal static class Icons
|
||||
{
|
||||
internal static IconInfo TerminalIcon { get; } = IconHelpers.FromRelativePath("Assets\\WindowsTerminal.svg");
|
||||
|
||||
internal static IconInfo AdminIcon { get; } = new IconInfo("\xE7EF"); // Admin icon
|
||||
|
||||
internal static IconInfo FilterIcon { get; } = new IconInfo("\uE71C"); // Funnel icon
|
||||
}
|
||||
|
||||
@@ -2,40 +2,73 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.WindowsTerminal.Commands;
|
||||
using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Pages;
|
||||
|
||||
internal sealed partial class ProfilesListPage : ListPage
|
||||
internal sealed partial class ProfilesListPage : ListPage, INotifyItemsChanged
|
||||
{
|
||||
event TypedEventHandler<object, IItemsChangedEventArgs> INotifyItemsChanged.ItemsChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
ItemsChanged += value;
|
||||
EnsureInitialized();
|
||||
SelectTerminalFilter();
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
ItemsChanged -= value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly TerminalQuery _terminalQuery = new();
|
||||
private readonly SettingsManager _terminalSettings;
|
||||
private readonly AppSettingsManager _appSettingsManager;
|
||||
|
||||
private bool showHiddenProfiles;
|
||||
private bool openNewTab;
|
||||
private bool openQuake;
|
||||
|
||||
public ProfilesListPage(SettingsManager terminalSettings)
|
||||
private bool initialized;
|
||||
private TerminalChannelFilters? terminalFilters;
|
||||
|
||||
public ProfilesListPage(SettingsManager terminalSettings, AppSettingsManager appSettingsManager)
|
||||
{
|
||||
Icon = Icons.TerminalIcon;
|
||||
Name = Resources.profiles_list_page_name;
|
||||
_terminalSettings = terminalSettings;
|
||||
_appSettingsManager = appSettingsManager;
|
||||
}
|
||||
|
||||
#pragma warning disable SA1108
|
||||
public List<ListItem> Query()
|
||||
private List<ListItem> Query()
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
showHiddenProfiles = _terminalSettings.ShowHiddenProfiles;
|
||||
openNewTab = _terminalSettings.OpenNewTab;
|
||||
openQuake = _terminalSettings.OpenQuake;
|
||||
|
||||
var profiles = _terminalQuery.GetProfiles();
|
||||
|
||||
if (terminalFilters?.IsAllSelected == false)
|
||||
{
|
||||
profiles = profiles.Where(profile => profile.Terminal.AppUserModelId == terminalFilters.CurrentFilterId);
|
||||
}
|
||||
|
||||
var result = new List<ListItem>();
|
||||
|
||||
foreach (var profile in profiles)
|
||||
@@ -52,12 +85,66 @@ internal sealed partial class ProfilesListPage : ListPage
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new LaunchProfileAsAdminCommand(profile.Terminal.AppUserModelId, profile.Name, openNewTab, openQuake)),
|
||||
],
|
||||
#pragma warning restore SA1108
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => Query().ToArray();
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var terminals = _terminalQuery.GetTerminals();
|
||||
terminalFilters = new TerminalChannelFilters(terminals);
|
||||
terminalFilters.PropChanged += TerminalFiltersOnPropChanged;
|
||||
SelectTerminalFilter();
|
||||
Filters = terminalFilters;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private void SelectTerminalFilter()
|
||||
{
|
||||
Trace.Assert(terminalFilters != null);
|
||||
|
||||
// Select the preferred channel if it exists; we always select the preferred channel,
|
||||
// but user have an option to save the preferred channel when he changes the filter
|
||||
if (_terminalSettings.SaveLastSelectedChannel)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_appSettingsManager.Current.LastSelectedChannel) &&
|
||||
terminalFilters.ContainsFilter(_appSettingsManager.Current.LastSelectedChannel))
|
||||
{
|
||||
terminalFilters.CurrentFilterId = _appSettingsManager.Current.LastSelectedChannel;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
terminalFilters.CurrentFilterId = TerminalChannelFilters.AllTerminalsFilterId;
|
||||
}
|
||||
}
|
||||
|
||||
private void TerminalFiltersOnPropChanged(object sender, IPropChangedEventArgs args)
|
||||
{
|
||||
Trace.Assert(terminalFilters != null);
|
||||
|
||||
RaiseItemsChanged();
|
||||
_appSettingsManager.Current.LastSelectedChannel = terminalFilters.CurrentFilterId;
|
||||
_appSettingsManager.Save();
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
try
|
||||
{
|
||||
return [.. Query()];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to list Windows Terminal profiles", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Pages;
|
||||
|
||||
internal sealed partial class TerminalChannelFilters : Filters
|
||||
{
|
||||
internal const string AllTerminalsFilterId = "all";
|
||||
|
||||
private readonly List<TerminalPackage> _terminals;
|
||||
|
||||
public bool IsAllSelected => CurrentFilterId == AllTerminalsFilterId;
|
||||
|
||||
public TerminalChannelFilters(IEnumerable<TerminalPackage> terminals, string preselectedFilterId = AllTerminalsFilterId)
|
||||
{
|
||||
CurrentFilterId = preselectedFilterId;
|
||||
_terminals = [.. terminals];
|
||||
}
|
||||
|
||||
public override IFilterItem[] GetFilters()
|
||||
{
|
||||
var items = new List<IFilterItem>
|
||||
{
|
||||
new Filter()
|
||||
{
|
||||
Id = AllTerminalsFilterId,
|
||||
Name = Resources.all_channels,
|
||||
Icon = Icons.FilterIcon,
|
||||
},
|
||||
new Separator(),
|
||||
};
|
||||
|
||||
foreach (var terminalPackage in _terminals)
|
||||
{
|
||||
items.Add(new Filter()
|
||||
{
|
||||
Id = terminalPackage.AppUserModelId,
|
||||
Name = terminalPackage.DisplayName,
|
||||
Icon = new IconInfo(terminalPackage.LogoPath),
|
||||
});
|
||||
}
|
||||
|
||||
return [.. items];
|
||||
}
|
||||
|
||||
public bool ContainsFilter(string id)
|
||||
{
|
||||
return _terminals.FindIndex(terminal => terminal.AppUserModelId == id) > -1;
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,15 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to All channels.
|
||||
/// </summary>
|
||||
internal static string all_channels {
|
||||
get {
|
||||
return ResourceManager.GetString("all_channels", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Windows Terminal Profiles.
|
||||
/// </summary>
|
||||
@@ -132,6 +141,24 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Preferred channel.
|
||||
/// </summary>
|
||||
internal static string preferred_channel {
|
||||
get {
|
||||
return ResourceManager.GetString("preferred_channel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Preferred channel.
|
||||
/// </summary>
|
||||
internal static string preferred_channel_description {
|
||||
get {
|
||||
return ResourceManager.GetString("preferred_channel_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Windows Terminal Profiles.
|
||||
/// </summary>
|
||||
@@ -159,6 +186,24 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Keep last channel filter.
|
||||
/// </summary>
|
||||
internal static string save_last_selected_channel {
|
||||
get {
|
||||
return ResourceManager.GetString("save_last_selected_channel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remember the last selected channel instead of resetting to All Channels..
|
||||
/// </summary>
|
||||
internal static string save_last_selected_channel_description {
|
||||
get {
|
||||
return ResourceManager.GetString("save_last_selected_channel_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Settings.
|
||||
/// </summary>
|
||||
|
||||
@@ -158,4 +158,19 @@
|
||||
<data name="list_item_title" xml:space="preserve">
|
||||
<value>Open Windows Terminal Profiles</value>
|
||||
</data>
|
||||
<data name="preferred_channel" xml:space="preserve">
|
||||
<value>Preferred channel</value>
|
||||
</data>
|
||||
<data name="preferred_channel_description" xml:space="preserve">
|
||||
<value>Preferred channel</value>
|
||||
</data>
|
||||
<data name="all_channels" xml:space="preserve">
|
||||
<value>All channels</value>
|
||||
</data>
|
||||
<data name="save_last_selected_channel" xml:space="preserve">
|
||||
<value>Keep last channel filter</value>
|
||||
</data>
|
||||
<data name="save_last_selected_channel_description" xml:space="preserve">
|
||||
<value>Remember the last selected channel instead of resetting to All Channels.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -11,8 +11,8 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal;
|
||||
|
||||
public partial class TerminalTopLevelCommandItem : CommandItem
|
||||
{
|
||||
public TerminalTopLevelCommandItem(SettingsManager settingsManager)
|
||||
: base(new ProfilesListPage(settingsManager))
|
||||
public TerminalTopLevelCommandItem(SettingsManager settingsManager, AppSettingsManager appSettingsManager)
|
||||
: base(new ProfilesListPage(settingsManager, appSettingsManager))
|
||||
{
|
||||
Icon = Icons.TerminalIcon;
|
||||
Title = Resources.list_item_title;
|
||||
|
||||
@@ -13,6 +13,7 @@ public partial class WindowsTerminalCommandsProvider : CommandProvider
|
||||
{
|
||||
private readonly TerminalTopLevelCommandItem _terminalCommand;
|
||||
private readonly SettingsManager _settingsManager = new();
|
||||
private readonly AppSettingsManager _appSettingsManager = new();
|
||||
|
||||
public WindowsTerminalCommandsProvider()
|
||||
{
|
||||
@@ -21,7 +22,7 @@ public partial class WindowsTerminalCommandsProvider : CommandProvider
|
||||
Icon = Icons.TerminalIcon;
|
||||
Settings = _settingsManager.Settings;
|
||||
|
||||
_terminalCommand = new TerminalTopLevelCommandItem(_settingsManager)
|
||||
_terminalCommand = new TerminalTopLevelCommandItem(_settingsManager, _appSettingsManager)
|
||||
{
|
||||
MoreCommands = [
|
||||
new CommandContextItem(Settings.SettingsPage),
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="BuildXamlIndexBeforeSettings" BeforeTargets="CoreCompile">
|
||||
<Target Name="BuildXamlIndexBeforeSettings" BeforeTargets="CoreCompile" Condition="'$(DesignTimeBuild)' != 'true'">
|
||||
<Message Importance="high" Text="[Settings] Building XamlIndexBuilder prior to compile. Views='$(MSBuildProjectDirectory)\SettingsXAML\Views' Out='$(GeneratedJsonFile)'" />
|
||||
<MSBuild Projects="..\Settings.UI.XamlIndexBuilder\Settings.UI.XamlIndexBuilder.csproj" Targets="Build" Properties="Configuration=$(Configuration);Platform=Any CPU;TargetFramework=net9.0;XamlViewsDir=$(MSBuildProjectDirectory)\SettingsXAML\Views;GeneratedJsonFile=$(GeneratedJsonFile)" />
|
||||
</Target>
|
||||
|
||||