Skip to content

Commit

Permalink
Really basic fuzzy search in Pane
Browse files Browse the repository at this point in the history
  • Loading branch information
e82eric committed May 7, 2024
1 parent 378b659 commit 89a4e14
Show file tree
Hide file tree
Showing 27 changed files with 2,329 additions and 26 deletions.
15 changes: 15 additions & 0 deletions src/buffer/out/UTextAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,18 @@ til::point_span Microsoft::Console::ICU::BufferRangeFromMatch(UText* ut, URegula

return ret;
}

UText Microsoft::Console::ICU::UTextForWrappableRow(const TextBuffer& textBuffer, til::CoordType& row) noexcept
{
const auto startRow = row;
auto length = 0;
while (textBuffer.GetRowByOffset(row).WasWrapForced())
{
row++;
length += textBuffer.GetRowByOffset(row).size();
}
length += textBuffer.GetRowByOffset(row).size();
const auto ut = UTextFromTextBuffer(textBuffer, startRow, row + 1);

return ut;
}
1 change: 1 addition & 0 deletions src/buffer/out/UTextAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft::Console::ICU
using unique_uregex = wistd::unique_ptr<URegularExpression, wil::function_deleter<decltype(&uregex_close), &uregex_close>>;

UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd) noexcept;
UText UTextForWrappableRow(const TextBuffer& textBuffer, til::CoordType& row) noexcept;
unique_uregex CreateRegex(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept;
til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re);
}
20 changes: 20 additions & 0 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "TerminalPage.h"
#include "ScratchpadContent.h"
#include "FuzzySearchPane.h"
#include "../WinRTUtils/inc/WtExeUtils.h"
#include "../../types/inc/utils.hpp"
#include "Utils.h"
Expand Down Expand Up @@ -1467,6 +1468,25 @@ namespace winrt::TerminalApp::implementation
}
}

void TerminalPage::_HandleOpenFuzzySearch(const IInspectable& sender,
const ActionEventArgs& args)
{
if (Feature_FuzzySearch::IsEnabled())
{
const auto& fuzzySearchPane{ winrt::make_self<FuzzySearchPane>(_GetActiveControl()) };

// This is maybe a little wacky - add our key event handler to the pane
// we made. So that we can get actions for keys that the content didn't
// handle.
fuzzySearchPane->GetRoot().KeyDown({ this, &TerminalPage::_KeyDownHandler });

const auto resultPane = std::make_shared<Pane>(*fuzzySearchPane);
_SplitPane(_senderOrFocusedTab(sender), SplitDirection::Automatic, 0.5f, resultPane);
fuzzySearchPane->Focus();
args.Handled(true);
}
}

void TerminalPage::_HandleOpenAbout(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
Expand Down
171 changes: 171 additions & 0 deletions src/cascadia/TerminalApp/FuzzySearchPane.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
#include "FuzzySearchPane.h"

using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Microsoft::Terminal::Settings::Model;

namespace winrt::TerminalApp::implementation
{
FuzzySearchPane::FuzzySearchPane(const winrt::Microsoft::Terminal::Control::TermControl& control)
{
_control = control;
_root = winrt::Windows::UI::Xaml::Controls::Grid{};
// Vertical and HorizontalAlignment are Stretch by default

auto res = Windows::UI::Xaml::Application::Current().Resources();
auto bg = res.Lookup(winrt::box_value(L"UnfocusedBorderBrush"));
_root.Background(bg.try_as<Media::Brush>());

const Controls::RowDefinition row1;
row1.Height(GridLengthHelper::FromValueAndType(1, GridUnitType::Star));
_root.RowDefinitions().Append(row1);

const Controls::RowDefinition row2;
row2.Height(GridLengthHelper::Auto());
_root.RowDefinitions().Append(row2);

_listBox = Controls::ListBox{};
_listBox.Margin({ 10, 10, 10, 10 });
_root.Children().Append(_listBox);

_searchBox = Controls::TextBox{};
_root.Children().Append(_searchBox);

_searchBox.TextChanged({ this, &FuzzySearchPane::OnTextChanged });
_searchBox.KeyDown({ this, &FuzzySearchPane::OnKeyUp });

Controls::Grid::SetRow(_listBox, 0);
Controls::Grid::SetRow(_searchBox, 1);
}

void FuzzySearchPane::OnKeyUp(Windows::Foundation::IInspectable const&, Input::KeyRoutedEventArgs const& e)
{
if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Down || e.OriginalKey() == winrt::Windows::System::VirtualKey::Up)
{
auto selectedIndex = _listBox.SelectedIndex();

if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Down)
{
selectedIndex++;
}
else if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Up)
{
selectedIndex--;
}

if (selectedIndex >= 0 && selectedIndex < static_cast<int32_t>(_listBox.Items().Size()))
{
_listBox.SelectedIndex(selectedIndex);
_listBox.ScrollIntoView(Controls::ListBox().SelectedItem());
}

e.Handled(true);
}
else if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Enter)
{
if (const auto selectedItem = _listBox.SelectedItem())
{
if (const auto listBoxItem = selectedItem.try_as<Controls::ListBoxItem>())
{
if (const auto fuzzyMatch = listBoxItem.DataContext().try_as<winrt::Microsoft::Terminal::Control::FuzzySearchTextLine>())
{
_control.SelectChar(fuzzyMatch.FirstPosition());
_control.Focus(FocusState::Programmatic);
e.Handled(true);
}
}
}
}
else if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Escape)
{
Close();
e.Handled(true);
}
}

void FuzzySearchPane::OnTextChanged(Windows::Foundation::IInspectable const&, Controls::TextChangedEventArgs const&) const
{
_listBox.Items().Clear();
const auto needle = _searchBox.Text();
const auto fuzzySearchResult = _control.FuzzySearch(needle);
if (fuzzySearchResult.NumberOfResults() > 0)
{
for (auto fuzzyMatch : fuzzySearchResult.Results())
{
auto control = Controls::TextBlock{};
const auto inlinesCollection = control.Inlines();
inlinesCollection.Clear();

for (const auto& match : fuzzyMatch.Segments())
{
const auto matchText = match.TextSegment();
const auto fontWeight = match.IsHighlighted() ? Windows::UI::Text::FontWeights::Bold() : Windows::UI::Text::FontWeights::Normal();

Media::SolidColorBrush foregroundBrush;

if (match.IsHighlighted())
{
foregroundBrush.Color(Windows::UI::Colors::OrangeRed());
}
else
{
foregroundBrush.Color(Windows::UI::Colors::White());
}

Documents::Run run;
run.Text(matchText);
run.FontWeight(fontWeight);
run.Foreground(foregroundBrush);
inlinesCollection.Append(run);
}
auto lbi = Controls::ListBoxItem();
lbi.DataContext(fuzzyMatch);
lbi.Content(control);
_listBox.Items().Append(lbi);
}
_listBox.SelectedIndex(0);
}
}

void FuzzySearchPane::UpdateSettings(const CascadiaSettings& /*settings*/)
{
// Nothing to do.
}

winrt::Windows::UI::Xaml::FrameworkElement FuzzySearchPane::GetRoot()
{
return _root;
}
winrt::Windows::Foundation::Size FuzzySearchPane::MinimumSize()
{
return { 1, 1 };
}
void FuzzySearchPane::Focus(winrt::Windows::UI::Xaml::FocusState reason)
{
_searchBox.Focus(reason);
}
void FuzzySearchPane::Close()
{
CloseRequested.raise(*this, nullptr);
}

INewContentArgs FuzzySearchPane::GetNewTerminalArgs(const BuildStartupKind /* kind */) const
{
return BaseContentArgs(L"scratchpad");
}

winrt::hstring FuzzySearchPane::Icon() const
{
static constexpr std::wstring_view glyph{ L"\xe70b" }; // QuickNote
return winrt::hstring{ glyph };
}

winrt::Windows::UI::Xaml::Media::Brush FuzzySearchPane::BackgroundBrush()
{
return _root.Background();
}
}
50 changes: 50 additions & 0 deletions src/cascadia/TerminalApp/FuzzySearchPane.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once
#include "winrt/TerminalApp.h"

namespace winrt::TerminalApp::implementation
{
class FuzzySearchPane : public winrt::implements<FuzzySearchPane, IPaneContent>
{
public:
FuzzySearchPane(const winrt::Microsoft::Terminal::Control::TermControl& control);
void TextBoxTextChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/);
void OnTextChanged(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::Controls::TextChangedEventArgs const&) const;
void OnKeyUp(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::Input::KeyRoutedEventArgs const&);

winrt::Windows::UI::Xaml::FrameworkElement GetRoot();

void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);

winrt::Windows::Foundation::Size MinimumSize();

void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic);
void Close();
winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(BuildStartupKind kind) const;

winrt::hstring Title() { return L"FuzzySearch"; }
uint64_t TaskbarState() { return 0; }
uint64_t TaskbarProgress() { return 0; }
bool ReadOnly() { return false; }
winrt::hstring Icon() const;
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() const noexcept { return nullptr; }
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();

til::typed_event<> ConnectionStateChanged;
til::typed_event<IPaneContent> CloseRequested;
til::typed_event<IPaneContent, winrt::TerminalApp::BellEventArgs> BellRequested;
til::typed_event<IPaneContent> TitleChanged;
til::typed_event<IPaneContent> TabColorChanged;
til::typed_event<IPaneContent> TaskbarProgressChanged;
til::typed_event<IPaneContent> ReadOnlyChanged;
til::typed_event<IPaneContent> FocusRequested;

private:
winrt::Windows::UI::Xaml::Controls::Grid _root{ nullptr };
winrt::Windows::UI::Xaml::Controls::ListBox _listBox{ nullptr };
winrt::Windows::UI::Xaml::Controls::TextBox _searchBox{ nullptr };
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
};
}
11 changes: 7 additions & 4 deletions src/cascadia/TerminalApp/TerminalAppLib.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@
<ClInclude Include="ScratchpadContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="FuzzySearchPane.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="SettingsPaneContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
Expand Down Expand Up @@ -274,6 +277,9 @@
<ClCompile Include="ScratchpadContent.cpp">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="FuzzySearchPane.cpp">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="SettingsPaneContent.cpp">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
Expand Down Expand Up @@ -400,7 +406,6 @@
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\UIHelpers\UIHelpers.vcxproj">
<Project>{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}</Project>
</ProjectReference>

</ItemGroup>
<PropertyGroup>
<!-- This is a hack to get the ARM64 CI build working. See
Expand Down Expand Up @@ -466,10 +471,8 @@
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />

<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />

<!--
By default, the PRI file will contain resource paths beginning with the
project name. Since we enabled XBF embedding, this *also* includes App.xbf.
Expand All @@ -490,4 +493,4 @@
</ItemGroup>
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
</Project>
</Project>
Loading

2 comments on commit 89a4e14

@github-actions

This comment was marked as outdated.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@check-spelling-bot Report

🔴 Please review

See the 📜action log or 📝 job summary for details.

Unrecognized words (33)
conni
eidx
Extention
fzf
lbi
MAXASCII
pchar
pidx
sidx
stdbool
stddef
stdint
strdup
uappend
uascii
ubonus
ucalculate
UCALL
udelim
ufzf
uhas
uidx
uindex
uis
uleading
unormalize
ustr
ustrtok
utrailing
utrim
utry
UWhite
Wrappable
To accept these unrecognized words as correct, you could run the following commands

... in a clone of the git@github.com:e82eric/terminal.git repository
on the fuzzy-search branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/v0.0.22/apply.pl' |
perl - 'https://github.com/e82eric/terminal/actions/runs/9357379438/attempts/1'
Available 📚 dictionaries could cover words (expected and unrecognized) not in the 📘 dictionary

This includes both expected items (2202) from .github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt
.github/actions/spelling/expect/alphabet.txt
.github/actions/spelling/expect/expect.txt
.github/actions/spelling/expect/web.txt and unrecognized words (33)

Dictionary Entries Covers Uniquely
cspell:cpp/src/lang-jargon.txt 11 1 1
cspell:swift/src/swift.txt 53 1 1
cspell:gaming-terms/dict/gaming-terms.txt 59 1 1
cspell:monkeyc/src/monkeyc_keywords.txt 123 1 1
cspell:cryptocurrencies/cryptocurrencies.txt 125 1 1

Consider adding them (in .github/workflows/spelling2.yml) for uses: check-spelling/check-spelling@v0.0.22 in its with:

      with:
        extra_dictionaries:
          cspell:cpp/src/lang-jargon.txt
          cspell:swift/src/swift.txt
          cspell:gaming-terms/dict/gaming-terms.txt
          cspell:monkeyc/src/monkeyc_keywords.txt
          cspell:cryptocurrencies/cryptocurrencies.txt

To stop checking additional dictionaries, add (in .github/workflows/spelling2.yml) for uses: check-spelling/check-spelling@v0.0.22 in its with:

check_extra_dictionaries: ''
Errors (2)

See the 📜action log or 📝 job summary for details.

❌ Errors Count
❌ check-file-path 4
❌ ignored-expect-variant 3

See ❌ Event descriptions for more information.

✏️ Contributor please read this

By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.

If the listed items are:

  • ... misspelled, then please correct them instead of using the command.
  • ... names, please add them to .github/actions/spelling/allow/names.txt.
  • ... APIs, you can add them to a file in .github/actions/spelling/allow/.
  • ... just things you're using, please add them to an appropriate file in .github/actions/spelling/expect/.
  • ... tokens you only need in one place and shouldn't generally be used, you can add an item in an appropriate file in .github/actions/spelling/patterns/.

See the README.md in each directory for more information.

🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Please sign in to comment.