Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ aaaaabbb
aabbcc
ABANDONFONT
abbcc
abcc
abgr
ABORTIFHUNG
ACCESSTOKEN
Expand Down
77 changes: 42 additions & 35 deletions src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,74 +28,81 @@ namespace TerminalAppLocalTests
TEST_METHOD(VerifyCompareIgnoreCase);
};

static void _verifySegment(auto&& segments, uint32_t index, uint64_t start, uint64_t end)
{
const auto& segment{ segments.GetAt(index) };
VERIFY_ARE_EQUAL(segment.Start, start, NoThrowString().Format(L"segment %zu", index));
VERIFY_ARE_EQUAL(segment.End, end, NoThrowString().Format(L"segment %zu", index));
}

void FilteredCommandTests::VerifyHighlighting()
{
auto result = RunOnUIThread([]() {
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;

const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"AAAAAABBBBBBCCC") };
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);

{
Log::Comment(L"Testing command name segmentation with no filter");
auto segments = filteredCommand->HighlightedName().Segments();
VERIFY_ARE_EQUAL(segments.Size(), 1u);
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
const auto segments = filteredCommand->NameHighlights();

VERIFY_IS_NULL(segments); // No matches = no segments
}
{
Log::Comment(L"Testing command name segmentation with empty filter");
filteredCommand->UpdateFilter(std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"")));
auto segments = filteredCommand->HighlightedName().Segments();
VERIFY_ARE_EQUAL(segments.Size(), 1u);
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
const auto segments = filteredCommand->NameHighlights();

VERIFY_IS_NULL(segments); // No matches = no segments
}
{
Log::Comment(L"Testing command name segmentation with filter equal to the string");
filteredCommand->UpdateFilter(std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"AAAAAABBBBBBCCC")));
auto segments = filteredCommand->HighlightedName().Segments();
const auto segments = filteredCommand->NameHighlights();

VERIFY_ARE_EQUAL(segments.Size(), 1u);
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
_verifySegment(segments, 0, 0, 14); // one segment for the entire string
}
{
Log::Comment(L"Testing command name segmentation with filter with first character matching");
filteredCommand->UpdateFilter(std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"A")));
auto segments = filteredCommand->HighlightedName().Segments();
VERIFY_ARE_EQUAL(segments.Size(), 2u);
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC");
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
const auto segments = filteredCommand->NameHighlights();

VERIFY_ARE_EQUAL(segments.Size(), 1u); // only one bold segment
_verifySegment(segments, 0, 0, 0); // it only covers the first character
}
{
Log::Comment(L"Testing command name segmentation with filter with other case");
filteredCommand->UpdateFilter(std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"a")));
auto segments = filteredCommand->HighlightedName().Segments();
VERIFY_ARE_EQUAL(segments.Size(), 2u);
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC");
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
const auto segments = filteredCommand->NameHighlights();

VERIFY_ARE_EQUAL(segments.Size(), 1u); // only one bold segment
_verifySegment(segments, 0, 0, 0); // it only covers the first character
}
{
Log::Comment(L"Testing command name segmentation with filter matching several characters");
filteredCommand->UpdateFilter(std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"ab")));
auto segments = filteredCommand->HighlightedName().Segments();
VERIFY_ARE_EQUAL(segments.Size(), 3u);
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAA");
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AB");
VERIFY_IS_TRUE(segments.GetAt(1).IsHighlighted());
VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"BBBBBCCC");
VERIFY_IS_FALSE(segments.GetAt(2).IsHighlighted());
const auto segments = filteredCommand->NameHighlights();

VERIFY_ARE_EQUAL(segments.Size(), 1u); // one bold segment
_verifySegment(segments, 0, 5, 6); // middle 'ab'
}
{
Log::Comment(L"Testing command name segmentation with filter matching several regions");
filteredCommand->UpdateFilter(std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"abcc")));
const auto segments = filteredCommand->NameHighlights();

VERIFY_ARE_EQUAL(segments.Size(), 2u); // two bold segments
_verifySegment(segments, 0, 5, 6); // middle 'ab'
_verifySegment(segments, 1, 12, 13); // start of 'cc'
}
{
Log::Comment(L"Testing command name segmentation with non matching filter");
filteredCommand->UpdateFilter(std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"abcd")));
auto segments = filteredCommand->HighlightedName().Segments();
VERIFY_ARE_EQUAL(segments.Size(), 1u);
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
const auto segments = filteredCommand->NameHighlights();

VERIFY_IS_NULL(segments); // No matches = no segments
}
});

Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@
</ResourceDictionary.ThemeDictionaries>

</ResourceDictionary>

<ResourceDictionary Source="ms-resource:///Files/TerminalApp/HighlightedTextControlStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Expand Down
9 changes: 6 additions & 3 deletions src/cascadia/TerminalApp/CommandPalette.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@

<local:HighlightedTextControl Grid.Column="1"
HorizontalAlignment="Left"
Text="{x:Bind HighlightedName, Mode=OneWay}" />
HighlightedRuns="{x:Bind NameHighlights, Mode=OneWay}"
Text="{x:Bind Item.Name, Mode=OneWay}" />

<!--
The block for the key chord is only visible
Expand Down Expand Up @@ -123,7 +124,8 @@

<local:HighlightedTextControl Grid.Column="1"
HorizontalAlignment="Left"
Text="{x:Bind HighlightedName, Mode=OneWay}" />
HighlightedRuns="{x:Bind NameHighlights, Mode=OneWay}"
Text="{x:Bind Item.Name, Mode=OneWay}" />

<!--
The block for the key chord is only visible
Expand Down Expand Up @@ -192,7 +194,8 @@

<local:HighlightedTextControl Grid.Column="1"
HorizontalAlignment="Left"
Text="{x:Bind HighlightedName, Mode=OneWay}" />
HighlightedRuns="{x:Bind NameHighlights, Mode=OneWay}"
Text="{x:Bind Item.Name, Mode=OneWay}" />

<StackPanel Grid.Column="2"
HorizontalAlignment="Right"
Expand Down
55 changes: 22 additions & 33 deletions src/cascadia/TerminalApp/FilteredCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include "pch.h"
#include "CommandPalette.h"
#include "HighlightedText.h"
#include <LibraryResources.h>
#include "fzf/fzf.h"

Expand Down Expand Up @@ -61,49 +60,39 @@ namespace winrt::TerminalApp::implementation
}
}

void FilteredCommand::_update()
static std::tuple<std::vector<winrt::TerminalApp::HighlightedRun>, int32_t> _matchedSegmentsAndWeight(const std::shared_ptr<fzf::matcher::Pattern>& pattern, const winrt::hstring& haystack)
{
std::vector<winrt::TerminalApp::HighlightedTextSegment> segments;
const auto commandName = _Item.Name();
std::vector<winrt::TerminalApp::HighlightedRun> segments;
int32_t weight = 0;

if (!_pattern || _pattern->terms.empty())
if (pattern && !pattern->terms.empty())
{
segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(commandName, false));
if (auto match = fzf::matcher::Match(haystack, *pattern.get()); match)
{
auto& matchResult = *match;
weight = matchResult.Score;
segments.resize(matchResult.Runs.size());
std::transform(matchResult.Runs.begin(), matchResult.Runs.end(), segments.begin(), [](auto&& run) -> winrt::TerminalApp::HighlightedRun {
return { run.Start, run.End };
});
}
}
else if (auto match = fzf::matcher::Match(commandName, *_pattern.get()); !match)
return { std::move(segments), weight };
}

void FilteredCommand::_update()
{
auto [segments, weight] = _matchedSegmentsAndWeight(_pattern, _Item.Name());

if (segments.empty())
{
segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(commandName, false));
NameHighlights(nullptr);
}
else
{
auto& matchResult = *match;
weight = matchResult.Score;

size_t lastPos = 0;
for (const auto& run : matchResult.Runs)
{
const auto& [start, end] = run;
if (start > lastPos)
{
hstring nonMatch{ til::safe_slice_abs(commandName, lastPos, start) };
segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(nonMatch, false));
}

hstring matchSeg{ til::safe_slice_abs(commandName, start, end + 1) };
segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(matchSeg, true));

lastPos = end + 1;
}

if (lastPos < commandName.size())
{
hstring tail{ til::safe_slice_abs(commandName, lastPos, SIZE_T_MAX) };
segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(tail, false));
}
NameHighlights(winrt::single_threaded_vector(std::move(segments)));
}

HighlightedName(winrt::make<HighlightedText>(winrt::single_threaded_observable_vector(std::move(segments))));
Weight(weight);
}

Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/FilteredCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace winrt::TerminalApp::implementation

til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::PaletteItem, Item, PropertyChanged.raise, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, PropertyChanged.raise);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::Collections::IVector<winrt::TerminalApp::HighlightedRun>, NameHighlights, PropertyChanged.raise);
WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise);

protected:
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/FilteredCommand.idl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace TerminalApp
FilteredCommand(PaletteItem item);

PaletteItem Item { get; };
HighlightedText HighlightedName { get; };
IVector<HighlightedRun> NameHighlights { get; };
Int32 Weight;
}
}
31 changes: 0 additions & 31 deletions src/cascadia/TerminalApp/HighlightedText.cpp

This file was deleted.

35 changes: 0 additions & 35 deletions src/cascadia/TerminalApp/HighlightedText.h

This file was deleted.

22 changes: 0 additions & 22 deletions src/cascadia/TerminalApp/HighlightedText.idl

This file was deleted.

Loading
Loading