-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds a basic command palette. ## Changes - `KeyModifiers` can now retrieve its constituent keys in a human-readable format, ordered by how keybindings are typically written down. - `Keybind` stores its constituent keys, and overrides `ToString`. - `VIRTUAL_KEY` has an extendion method to return it as a human-readable string (i.e., without the `VK_` prefix). - Moved `FocusIndicatorWindow`'s logic for creating a borderless window to an extension method of `Microsoft.UI.Xaml.Window`, in `WindowExtensions`. - `IWindow` can now focus and force windows to the top of the Z-index, with `FocusForceForeground`. ## Additions A basic command palette, which has configurable matching (ranking and filters). It takes advantage of the command system added in #79. The command palette can be activated on a given monitor. Currently, it activates on the currently focused monitor. ## General notes Performance is lacking in the following scenario: 1. Query the command palette. 2. Delete some characters from the query. There is a noticeable lag between deleting characters from the query, and the additional rows being shown.
- Loading branch information
Showing
28 changed files
with
1,315 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using Moq; | ||
using Windows.Win32.UI.Input.KeyboardAndMouse; | ||
using Xunit; | ||
|
||
namespace Whim.CommandPalette.Tests; | ||
|
||
public class MatchTests | ||
{ | ||
[Fact] | ||
public void Match_NoKeybind() | ||
{ | ||
Match match = new(new Mock<ICommand>().Object); | ||
|
||
Assert.Null(match.Keys); | ||
} | ||
|
||
[Fact] | ||
public void Match_Keybind() | ||
{ | ||
Match match = new(new Mock<ICommand>().Object, new Keybind(KeyModifiers.LWin, VIRTUAL_KEY.VK_A)); | ||
Assert.Equal("LWin + A", match.Keys); | ||
} | ||
} |
92 changes: 92 additions & 0 deletions
92
src/Whim.CommandPalette.Tests/MostOftenUsedMatcherTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using FluentAssertions; | ||
using Moq; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Xunit; | ||
|
||
namespace Whim.CommandPalette.Tests; | ||
|
||
public class MostOftenUsedMatcherTests | ||
{ | ||
private readonly List<Match> _items = new(); | ||
|
||
private readonly MostOftenUsedMatcher _matcher = new(); | ||
|
||
private void CreateMatch(string commandName, uint count) | ||
{ | ||
Mock<ICommand> commandMock = new(); | ||
commandMock.Setup(c => c.Identifier).Returns(commandName); | ||
commandMock.Setup(c => c.Title).Returns(commandName); | ||
|
||
Match match = new(commandMock.Object, new Mock<IKeybind>().Object); | ||
_items.Add(match); | ||
|
||
for (int i = 0; i < count; i++) | ||
{ | ||
_matcher.OnMatchExecuted(match); | ||
} | ||
} | ||
|
||
private static IList<HighlightedTextSegment> CreateHighlightedText(params HighlightedTextSegment[] segments) | ||
{ | ||
List<HighlightedTextSegment> results = new(); | ||
|
||
foreach (HighlightedTextSegment segment in segments) | ||
{ | ||
results.Add(segment); | ||
} | ||
|
||
return results; | ||
} | ||
|
||
public MostOftenUsedMatcherTests() | ||
{ | ||
CreateMatch("foo", 1); | ||
CreateMatch("bar", 2); | ||
CreateMatch("baz", 3); | ||
CreateMatch("qux", 4); | ||
CreateMatch("quux", 5); | ||
CreateMatch("corge", 6); | ||
CreateMatch("grault", 7); | ||
CreateMatch("garply", 8); | ||
CreateMatch("waldo", 9); | ||
CreateMatch("uxui", 10); | ||
} | ||
|
||
[Fact] | ||
public void Match_Returns_Most_Often_Used_Items() | ||
{ | ||
IEnumerable<PaletteItem> matches = _matcher.Match("", _items); | ||
|
||
List<(string MatchCommand, IList<HighlightedTextSegment> Title)> expectedItems = new() | ||
{ | ||
new("waldo", CreateHighlightedText(new HighlightedTextSegment("waldo", false))), | ||
new("garply", CreateHighlightedText(new HighlightedTextSegment("garply", false))), | ||
new("grault", CreateHighlightedText(new HighlightedTextSegment("grault", false))), | ||
new("corge", CreateHighlightedText(new HighlightedTextSegment("corge", false))), | ||
new("quux", CreateHighlightedText(new HighlightedTextSegment("quux", false))), | ||
new("qux", CreateHighlightedText(new HighlightedTextSegment("qux", false))), | ||
new("baz", CreateHighlightedText(new HighlightedTextSegment("baz", false))), | ||
new("bar", CreateHighlightedText(new HighlightedTextSegment("bar", false))), | ||
new("foo", CreateHighlightedText(new HighlightedTextSegment("foo", false))), | ||
new("uxui", CreateHighlightedText(new HighlightedTextSegment("uxui", false))), | ||
}; | ||
|
||
matches.Select(m => (m.Match.Command.Identifier, m.Title.Segments)).Should().BeEquivalentTo(expectedItems); | ||
} | ||
|
||
[Fact] | ||
public void Match_Returns_Most_Often_Used_Items_With_Highlighted_Text() | ||
{ | ||
IEnumerable<PaletteItem> matches = _matcher.Match("ux", _items); | ||
|
||
List<(string MatchCommand, IList<HighlightedTextSegment> Title)> expectedItems = new() | ||
{ | ||
("uxui", CreateHighlightedText(new HighlightedTextSegment("ux", true), new HighlightedTextSegment("ui", false))), | ||
("quux", CreateHighlightedText(new HighlightedTextSegment("qu", false), new HighlightedTextSegment("ux", true))), | ||
("qux", CreateHighlightedText(new HighlightedTextSegment("q", false), new HighlightedTextSegment("ux", true))), | ||
}; | ||
|
||
matches.Select(m => (m.Match.Command.Identifier, m.Title.Segments)).Should().BeEquivalentTo(expectedItems); | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
src/Whim.CommandPalette.Tests/MostRecentlyUsedMatcherTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using FluentAssertions; | ||
using Moq; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Xunit; | ||
|
||
namespace Whim.CommandPalette.Tests; | ||
|
||
public class MostRecentlyUsedMatcherTests | ||
{ | ||
private readonly List<Match> _items = new(); | ||
|
||
private readonly MostRecentlyUsedMatcher _matcher = new(); | ||
|
||
private void CreateMatch(string commandName) | ||
{ | ||
Mock<ICommand> commandMock = new(); | ||
commandMock.Setup(c => c.Identifier).Returns(commandName); | ||
commandMock.Setup(c => c.Title).Returns(commandName); | ||
|
||
Match match = new(commandMock.Object, new Mock<IKeybind>().Object); | ||
_items.Add(match); | ||
|
||
_matcher.OnMatchExecuted(match); | ||
} | ||
|
||
private static IList<HighlightedTextSegment> CreateHighlightedText(params HighlightedTextSegment[] segments) | ||
{ | ||
List<HighlightedTextSegment> results = new(); | ||
|
||
foreach (HighlightedTextSegment segment in segments) | ||
{ | ||
results.Add(segment); | ||
} | ||
|
||
return results; | ||
} | ||
|
||
public MostRecentlyUsedMatcherTests() | ||
{ | ||
CreateMatch("foo"); | ||
CreateMatch("bar"); | ||
CreateMatch("baz"); | ||
CreateMatch("qux"); | ||
CreateMatch("quux"); | ||
CreateMatch("corge"); | ||
CreateMatch("grault"); | ||
CreateMatch("garply"); | ||
CreateMatch("waldo"); | ||
CreateMatch("uxui"); | ||
} | ||
|
||
[Fact] | ||
public void Match_Returns_Most_Often_Used_Items() | ||
{ | ||
IEnumerable<PaletteItem> matches = _matcher.Match("", _items); | ||
|
||
List<(string MatchCommand, IList<HighlightedTextSegment> Title)> expectedItems = new() | ||
{ | ||
new("uxui", CreateHighlightedText(new HighlightedTextSegment("uxui", false))), | ||
new("waldo", CreateHighlightedText(new HighlightedTextSegment("waldo", false))), | ||
new("garply", CreateHighlightedText(new HighlightedTextSegment("garply", false))), | ||
new("grault", CreateHighlightedText(new HighlightedTextSegment("grault", false))), | ||
new("corge", CreateHighlightedText(new HighlightedTextSegment("corge", false))), | ||
new("quux", CreateHighlightedText(new HighlightedTextSegment("quux", false))), | ||
new("qux", CreateHighlightedText(new HighlightedTextSegment("qux", false))), | ||
new("baz", CreateHighlightedText(new HighlightedTextSegment("baz", false))), | ||
new("bar", CreateHighlightedText(new HighlightedTextSegment("bar", false))), | ||
new("foo", CreateHighlightedText(new HighlightedTextSegment("foo", false))), | ||
}; | ||
|
||
matches.Select(m => (m.Match.Command.Identifier, m.Title.Segments)).Should().BeEquivalentTo(expectedItems); | ||
} | ||
|
||
[Fact] | ||
public void Match_Returns_Most_Often_Used_Items_With_Highlighted_Text() | ||
{ | ||
IEnumerable<PaletteItem> matches = _matcher.Match("ux", _items); | ||
|
||
List<(string MatchCommand, IList<HighlightedTextSegment> Title)> expectedItems = new() | ||
{ | ||
("uxui", CreateHighlightedText(new HighlightedTextSegment("ux", true), new HighlightedTextSegment("ui", false))), | ||
("quux", CreateHighlightedText(new HighlightedTextSegment("qu", false), new HighlightedTextSegment("ux", true))), | ||
("qux", CreateHighlightedText(new HighlightedTextSegment("q", false), new HighlightedTextSegment("ux", true))), | ||
}; | ||
|
||
matches.Select(m => (m.Match.Command.Identifier, m.Title.Segments)).Should().BeEquivalentTo(expectedItems); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/Whim.CommandPalette.Tests/Whim.CommandPalette.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
|
||
<IsPackable>false</IsPackable> | ||
|
||
<Platforms>AnyCPU;x64</Platforms> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="6.3.0" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> | ||
<PackageReference Include="Moq" Version="4.16.1" /> | ||
<PackageReference Include="xunit" Version="2.4.1" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
<PackageReference Include="coverlet.collector" Version="3.1.0"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Whim.CommandPalette\Whim.CommandPalette.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
namespace Whim.CommandPalette; | ||
|
||
public class CommandPaletteConfig | ||
{ | ||
/// <summary> | ||
/// The title of the command palette window. | ||
/// </summary> | ||
internal const string Title = "Whim Command Palette"; | ||
|
||
/// <summary> | ||
/// The matcher to use when filtering for commands. | ||
/// </summary> | ||
public ICommandPaletteMatcher Matcher { get; set; } = new MostRecentlyUsedMatcher(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace Whim.CommandPalette; | ||
|
||
public class CommandPalettePlugin : IPlugin | ||
{ | ||
private readonly IConfigContext _configContext; | ||
private CommandPaletteWindow? _commandPaletteWindow; | ||
public CommandPaletteConfig Config { get; } | ||
|
||
public CommandPalettePlugin(IConfigContext configContext, CommandPaletteConfig commandPaletteConfig) | ||
{ | ||
_configContext = configContext; | ||
Config = commandPaletteConfig; | ||
} | ||
|
||
public void PreInitialize() | ||
{ | ||
_configContext.FilterManager.IgnoreTitleMatch(CommandPaletteConfig.Title); | ||
} | ||
|
||
public void PostInitialize() | ||
{ | ||
// The window must be created on the UI thread (so don't do it in the constructor). | ||
_commandPaletteWindow = new CommandPaletteWindow(_configContext, this); | ||
} | ||
|
||
/// <summary> | ||
/// Activate the command palette window. | ||
/// </summary> | ||
/// <param name="items"></param> | ||
public void Activate(IEnumerable<(ICommand, IKeybind?)>? items = null) | ||
{ | ||
_commandPaletteWindow?.Activate( | ||
items, | ||
_configContext.MonitorManager.FocusedMonitor | ||
); | ||
} | ||
|
||
/// <summary> | ||
/// Hide the command palette. | ||
/// </summary> | ||
public void Hide() | ||
{ | ||
_commandPaletteWindow?.Hide(); | ||
} | ||
|
||
/// <summary> | ||
/// Toggle the visibility of the command palette. | ||
/// </summary> | ||
public void Toggle() | ||
{ | ||
_commandPaletteWindow?.Toggle(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<Window | ||
x:Class="Whim.CommandPalette.CommandPaletteWindow" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:local="using:Whim.CommandPalette" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
mc:Ignorable="d"> | ||
|
||
<Grid Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}"> | ||
<Grid.RowDefinitions> | ||
<RowDefinition Height="auto" /> | ||
<RowDefinition Height="*" /> | ||
</Grid.RowDefinitions> | ||
|
||
<TextBox | ||
x:Name="TextEntry" | ||
KeyDown="TextEntry_KeyDown" | ||
PlaceholderText="Start typing..." | ||
TextChanged="TextEntry_TextChanged" /> | ||
|
||
<ListView | ||
x:Name="ListViewItems" | ||
Grid.Row="1" | ||
IsTabStop="False" | ||
ItemClick="CommandListItems_ItemClick" | ||
SelectionMode="Single" /> | ||
</Grid> | ||
</Window> |
Oops, something went wrong.