From f995e414b769bdef1f311e48a2e81a3264e8131c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Tue, 23 Sep 2025 17:39:08 +0200 Subject: [PATCH] CmdPal: Add setting to choose primary action for Clipboard History items (#41863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request Allows users to set a preference for the primary action—Paste or Copy—when interacting with Clipboard History entries. - Introduce `ClipboardListItem` as a subclass of `ListItem` - Build the item's details panel lazily to improve performance - Order Paste/Copy commands based on the selected preference - Update icons to visually reflect the chosen primary action Pictures? Pictures! image image image ## PR Checklist - [x] Closes: #41661 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed --- .../ic_fluent_clipboard_20_regular.dark.svg | 3 + .../ic_fluent_clipboard_20_regular.light.svg | 3 + ...fluent_clipboard_image_20_regular.dark.svg | 3 + ...luent_clipboard_image_20_regular.light.svg | 3 + ...luent_clipboard_letter_20_regular.dark.svg | 3 + ...uent_clipboard_letter_20_regular.light.svg | 3 + .../Icons/ic_fluent_copy_20_regular.dark.svg | 3 + .../Icons/ic_fluent_copy_20_regular.light.svg | 3 + ...c_fluent_document_copy_20_regular.dark.svg | 3 + ..._fluent_document_copy_20_regular.light.svg | 3 + .../ic_fluent_image_copy_20_regular.dark.svg | 3 + .../ic_fluent_image_copy_20_regular.light.svg | 3 + .../Helpers/PrimaryAction.cs | 12 ++ .../Helpers/SettingsManager.cs | 14 ++ .../Icons.cs | 19 +- ...crosoft.CmdPal.Ext.ClipboardHistory.csproj | 36 ++++ .../Models/ClipboardItem.cs | 128 +---------- .../Pages/ClipboardHistoryListPage.cs | 2 +- .../Pages/ClipboardListItem.cs | 201 ++++++++++++++++++ .../Properties/Resources.Designer.cs | 45 ++++ .../Properties/Resources.resx | 15 ++ 21 files changed, 380 insertions(+), 128 deletions(-) create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_20_regular.dark.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_20_regular.light.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_image_20_regular.dark.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_image_20_regular.light.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_letter_20_regular.dark.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_letter_20_regular.light.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_copy_20_regular.dark.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_copy_20_regular.light.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_document_copy_20_regular.dark.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_document_copy_20_regular.light.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_image_copy_20_regular.dark.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_image_copy_20_regular.light.svg create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/PrimaryAction.cs create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardListItem.cs diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_20_regular.dark.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_20_regular.dark.svg new file mode 100644 index 0000000000..8865472f05 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_20_regular.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_20_regular.light.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_20_regular.light.svg new file mode 100644 index 0000000000..f39c0b594d --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_20_regular.light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_image_20_regular.dark.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_image_20_regular.dark.svg new file mode 100644 index 0000000000..d2658c1fde --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_image_20_regular.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_image_20_regular.light.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_image_20_regular.light.svg new file mode 100644 index 0000000000..4485e39cfa --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_image_20_regular.light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_letter_20_regular.dark.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_letter_20_regular.dark.svg new file mode 100644 index 0000000000..3e5845fac9 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_letter_20_regular.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_letter_20_regular.light.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_letter_20_regular.light.svg new file mode 100644 index 0000000000..476f97953c --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_clipboard_letter_20_regular.light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_copy_20_regular.dark.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_copy_20_regular.dark.svg new file mode 100644 index 0000000000..f79782da20 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_copy_20_regular.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_copy_20_regular.light.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_copy_20_regular.light.svg new file mode 100644 index 0000000000..75bba0c080 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_copy_20_regular.light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_document_copy_20_regular.dark.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_document_copy_20_regular.dark.svg new file mode 100644 index 0000000000..6f34f9daa7 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_document_copy_20_regular.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_document_copy_20_regular.light.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_document_copy_20_regular.light.svg new file mode 100644 index 0000000000..fb380fe84f --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_document_copy_20_regular.light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_image_copy_20_regular.dark.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_image_copy_20_regular.dark.svg new file mode 100644 index 0000000000..162dedad90 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_image_copy_20_regular.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_image_copy_20_regular.light.svg b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_image_copy_20_regular.light.svg new file mode 100644 index 0000000000..7aff1a515e --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Assets/Icons/ic_fluent_image_copy_20_regular.light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/PrimaryAction.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/PrimaryAction.cs new file mode 100644 index 0000000000..11d7bd0d5d --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/PrimaryAction.cs @@ -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, +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/SettingsManager.cs index 6ccc987d52..40fda696a2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/SettingsManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/SettingsManager.cs @@ -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.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(); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Icons.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Icons.cs index 824a6a2233..4bb4c30586 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Icons.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Icons.cs @@ -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"); + } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Microsoft.CmdPal.Ext.ClipboardHistory.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Microsoft.CmdPal.Ext.ClipboardHistory.csproj index 9b5057ecae..bc55f536e0 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Microsoft.CmdPal.Ext.ClipboardHistory.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Microsoft.CmdPal.Ext.ClipboardHistory.csproj @@ -39,5 +39,41 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Models/ClipboardItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Models/ClipboardItem.cs index dd66410e6d..f6c89f53e6 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Models/ClipboardItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Models/ClipboardItem.cs @@ -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 ShiftLinesLeft(List 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 StripLeadingWhitespace(List 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 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); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardHistoryListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardHistoryListPage.cs index c0d564eb5e..d17f6f5844 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardHistoryListPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardHistoryListPage.cs @@ -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)); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardListItem.cs new file mode 100644 index 0000000000..ac19335bfa --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Pages/ClipboardListItem.cs @@ -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
_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 StripLeadingWhitespace(IEnumerable 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; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.Designer.cs index a0b1882d14..3fa9e8668e 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.Designer.cs @@ -212,5 +212,50 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory.Properties { return ResourceManager.GetString("settings_keep_after_paste_title", resourceCulture); } } + + /// + /// Looks up a localized string similar to Copy to Clipboard. + /// + public static string settings_primary_action_copy { + get { + return ResourceManager.GetString("settings_primary_action_copy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default (Copy to Clipboard). + /// + public static string settings_primary_action_default { + get { + return ResourceManager.GetString("settings_primary_action_default", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Primary action (Enter key). + /// + public static string settings_primary_action_description { + get { + return ResourceManager.GetString("settings_primary_action_description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Paste. + /// + public static string settings_primary_action_paste { + get { + return ResourceManager.GetString("settings_primary_action_paste", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Primary action. + /// + public static string settings_primary_action_title { + get { + return ResourceManager.GetString("settings_primary_action_title", resourceCulture); + } + } } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.resx index e67ba1747c..70226f7292 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Properties/Resources.resx @@ -168,4 +168,19 @@ Are you sure you want to delete this item from clipboard history? This action cannot be undone. + + Primary action + + + Primary action (Enter key) + + + Default (Copy to Clipboard) + + + Paste + + + Copy to Clipboard + \ No newline at end of file