mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-19 18:11:39 -05:00
Rewrite HighlightedTextControl and remove HighlightedText (#19130)
`HighlightedTextControl` is a XAML user control that contains a text block and takes vector of `HighlightedText`. `HighlightedText` uses tuples `(string, boolean)`. Allocating an entire object to store a string and an integer felt like a waste, especially when we were doing it thousands of times for the command palette _and just to pass them into another object that stores a string and a few more integers (`Run`)._ The new `HighlightedTextControl` is a standard templated control, and supports styling of both the inner text block and of the highlighted runs. It no longer takes a `HighlightedText`, but rather a standard string and a set of runs (tuple `(int start, int end)`); these can be stored more efficiently, and this change moves the construction of text and runs directly into `HighlightedTextControl` itself as an implementation detail rather than an API contract. ### XAML Properties - `Text`: the string to highlight - `HighlightedRuns`: a vector of `(start, end)` pairs, indicating which regions are intended to be highlighted. Can be empty (which indicates there is no highlight and that the entire string is styled normally.) - `TextBlockStyle`: the `Style` applied to the inner text block; optional; allows consumers to change how both normal and highlighted text looks. - `HighlightedRunStyle`: a `Style` applied only to the highlighted runs; optional; allows consumers to change how highlighted text looks. If left NULL, highlighted runs will be bold. `HighlightedRunStyle` is a little bodgy. It only applies to `Run` objects (which is fine, and XAML somewhat supports), but since `Run` is not a `FrameworkElement`, it _doesn't actually have a `Style` member._ We need to crack open the style and apply it manually, entry by entry. `FontWeight` is special because XAML is a special little flower.
This commit is contained in:
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
@@ -2,6 +2,7 @@ aaaaabbb
|
||||
aabbcc
|
||||
ABANDONFONT
|
||||
abbcc
|
||||
abcc
|
||||
abgr
|
||||
ABORTIFHUNG
|
||||
ACCESSTOKEN
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -243,6 +243,8 @@
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary Source="ms-resource:///Files/TerminalApp/HighlightedTextControlStyle.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "CommandPalette.h"
|
||||
#include "HighlightedText.h"
|
||||
#include <LibraryResources.h>
|
||||
#include "fzf/fzf.h"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace TerminalApp
|
||||
FilteredCommand(PaletteItem item);
|
||||
|
||||
PaletteItem Item { get; };
|
||||
HighlightedText HighlightedName { get; };
|
||||
IVector<HighlightedRun> NameHighlights { get; };
|
||||
Int32 Weight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "HighlightedText.h"
|
||||
#include "HighlightedTextSegment.g.cpp"
|
||||
#include "HighlightedText.g.cpp"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::UI::Text;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
HighlightedTextSegment::HighlightedTextSegment(const winrt::hstring& textSegment, bool isHighlighted) :
|
||||
_TextSegment(textSegment),
|
||||
_IsHighlighted(isHighlighted)
|
||||
{
|
||||
}
|
||||
|
||||
HighlightedText::HighlightedText(const Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::HighlightedTextSegment>& segments) :
|
||||
_Segments(segments)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "HighlightedTextSegment.g.h"
|
||||
#include "HighlightedText.g.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct HighlightedTextSegment : HighlightedTextSegmentT<HighlightedTextSegment>
|
||||
{
|
||||
HighlightedTextSegment() = default;
|
||||
HighlightedTextSegment(const winrt::hstring& text, bool isHighlighted);
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, TextSegment, PropertyChanged.raise);
|
||||
WINRT_OBSERVABLE_PROPERTY(bool, IsHighlighted, PropertyChanged.raise);
|
||||
};
|
||||
|
||||
struct HighlightedText : HighlightedTextT<HighlightedText>
|
||||
{
|
||||
HighlightedText() = default;
|
||||
HighlightedText(const Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::HighlightedTextSegment>& segments);
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::HighlightedTextSegment>, Segments, PropertyChanged.raise);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(HighlightedTextSegment);
|
||||
BASIC_FACTORY(HighlightedText);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass HighlightedTextSegment : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
HighlightedTextSegment();
|
||||
HighlightedTextSegment(String text, Boolean isMatched);
|
||||
|
||||
String TextSegment { get; };
|
||||
Boolean IsHighlighted { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass HighlightedText : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
HighlightedText();
|
||||
HighlightedText(Windows.Foundation.Collections.IObservableVector<HighlightedTextSegment> segments);
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<HighlightedTextSegment> Segments;
|
||||
}
|
||||
}
|
||||
@@ -22,70 +22,152 @@ namespace winrt::TerminalApp::implementation
|
||||
// Our control exposes a "Text" property to be used with Data Binding
|
||||
// To allow this we need to register a Dependency Property Identifier to be used by the property system
|
||||
// (https://docs.microsoft.com/en-us/windows/uwp/xaml-platform/custom-dependency-properties)
|
||||
DependencyProperty HighlightedTextControl::_textProperty = DependencyProperty::Register(
|
||||
L"Text",
|
||||
xaml_typename<winrt::TerminalApp::HighlightedText>(),
|
||||
xaml_typename<winrt::TerminalApp::HighlightedTextControl>(),
|
||||
PropertyMetadata(nullptr, HighlightedTextControl::_onTextChanged));
|
||||
DependencyProperty HighlightedTextControl::_TextProperty{ nullptr };
|
||||
DependencyProperty HighlightedTextControl::_HighlightedRunsProperty{ nullptr };
|
||||
DependencyProperty HighlightedTextControl::_TextBlockStyleProperty{ nullptr };
|
||||
DependencyProperty HighlightedTextControl::_HighlightedRunStyleProperty{ nullptr };
|
||||
|
||||
HighlightedTextControl::HighlightedTextControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
_InitializeProperties();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the Identifier of the "Text" dependency property
|
||||
DependencyProperty HighlightedTextControl::TextProperty()
|
||||
void HighlightedTextControl::_InitializeProperties()
|
||||
{
|
||||
return _textProperty;
|
||||
static auto [[maybe_unused]] registered = [] {
|
||||
_TextProperty = DependencyProperty::Register(
|
||||
L"Text",
|
||||
xaml_typename<winrt::hstring>(),
|
||||
xaml_typename<winrt::TerminalApp::HighlightedTextControl>(),
|
||||
PropertyMetadata(nullptr, HighlightedTextControl::_onPropertyChanged));
|
||||
|
||||
_HighlightedRunsProperty = DependencyProperty::Register(
|
||||
L"HighlightedRuns",
|
||||
xaml_typename<winrt::Windows::Foundation::Collections::IVector<winrt::TerminalApp::HighlightedRun>>(),
|
||||
xaml_typename<winrt::TerminalApp::HighlightedTextControl>(),
|
||||
PropertyMetadata(nullptr, HighlightedTextControl::_onPropertyChanged));
|
||||
|
||||
_TextBlockStyleProperty = DependencyProperty::Register(
|
||||
L"TextBlockStyle",
|
||||
xaml_typename<winrt::Windows::UI::Xaml::Style>(),
|
||||
xaml_typename<winrt::TerminalApp::HighlightedTextControl>(),
|
||||
PropertyMetadata{ nullptr });
|
||||
|
||||
_HighlightedRunStyleProperty = DependencyProperty::Register(
|
||||
L"HighlightedRunStyle",
|
||||
xaml_typename<winrt::Windows::UI::Xaml::Style>(),
|
||||
xaml_typename<winrt::TerminalApp::HighlightedTextControl>(),
|
||||
PropertyMetadata(nullptr, HighlightedTextControl::_onPropertyChanged));
|
||||
|
||||
return true;
|
||||
}();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the TextBlock view used to render the highlighted text
|
||||
// Can be used when the Text property change is triggered by the event system to update the view
|
||||
// We need to expose it rather than simply bind a data source because we update the runs in code-behind
|
||||
Controls::TextBlock HighlightedTextControl::TextView()
|
||||
{
|
||||
return _textView();
|
||||
}
|
||||
|
||||
winrt::TerminalApp::HighlightedText HighlightedTextControl::Text()
|
||||
{
|
||||
return winrt::unbox_value<winrt::TerminalApp::HighlightedText>(GetValue(_textProperty));
|
||||
}
|
||||
|
||||
void HighlightedTextControl::Text(const winrt::TerminalApp::HighlightedText& value)
|
||||
{
|
||||
SetValue(_textProperty, winrt::box_value(value));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This callback is triggered when the Text property is changed. Responsible for updating the view
|
||||
// Arguments:
|
||||
// - o - dependency object that was modified, expected to be an instance of this control
|
||||
// - e - event arguments of the property changed event fired by the event system upon Text property change.
|
||||
// The new value is expected to be an instance of HighlightedText
|
||||
void HighlightedTextControl::_onTextChanged(const DependencyObject& o, const DependencyPropertyChangedEventArgs& e)
|
||||
void HighlightedTextControl::_onPropertyChanged(const DependencyObject& o, const DependencyPropertyChangedEventArgs& /*e*/)
|
||||
{
|
||||
const auto control = o.try_as<winrt::TerminalApp::HighlightedTextControl>();
|
||||
const auto highlightedText = e.NewValue().try_as<winrt::TerminalApp::HighlightedText>();
|
||||
|
||||
if (control && highlightedText)
|
||||
if (control)
|
||||
{
|
||||
// Replace all the runs on the TextBlock
|
||||
// Use IsHighlighted to decide if the run should be highlighted.
|
||||
// To do - export the highlighting style into XAML
|
||||
const auto inlinesCollection = control.TextView().Inlines();
|
||||
inlinesCollection.Clear();
|
||||
winrt::get_self<HighlightedTextControl>(control)->_updateTextAndStyle();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& match : highlightedText.Segments())
|
||||
void HighlightedTextControl::OnApplyTemplate()
|
||||
{
|
||||
_updateTextAndStyle();
|
||||
}
|
||||
|
||||
static void _applyStyleToObject(const winrt::Windows::UI::Xaml::Style& style, const winrt::Windows::UI::Xaml::DependencyObject& object)
|
||||
{
|
||||
if (!style)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static const auto fontWeightProperty{ winrt::Windows::UI::Xaml::Documents::TextElement::FontWeightProperty() };
|
||||
|
||||
const auto setters{ style.Setters() };
|
||||
for (auto&& setterBase : setters)
|
||||
{
|
||||
const auto setter = setterBase.as<winrt::Windows::UI::Xaml::Setter>();
|
||||
const auto property = setter.Property();
|
||||
auto value = setter.Value();
|
||||
|
||||
if (property == fontWeightProperty) [[unlikely]]
|
||||
{
|
||||
const auto matchText = match.TextSegment();
|
||||
const auto fontWeight = match.IsHighlighted() ? FontWeights::Bold() : FontWeights::Normal();
|
||||
// BODGY - The XAML compiler emits a boxed int32, but the dependency property
|
||||
// here expects a boxed FontWeight (which also requires a u16. heh.)
|
||||
// FontWeight is one of the few properties that is broken like this, and on Run it's the
|
||||
// only one... so we can trivially check this case.
|
||||
const auto weight{ winrt::unbox_value_or<int32_t>(value, static_cast<int32_t>(400)) };
|
||||
value = winrt::box_value(winrt::Windows::UI::Text::FontWeight{ static_cast<uint16_t>(weight) });
|
||||
}
|
||||
|
||||
object.SetValue(property, value);
|
||||
}
|
||||
}
|
||||
|
||||
void HighlightedTextControl::_updateTextAndStyle()
|
||||
{
|
||||
const auto textBlock = GetTemplateChild(L"TextView").try_as<winrt::Windows::UI::Xaml::Controls::TextBlock>();
|
||||
if (!textBlock)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto text = Text();
|
||||
const auto runs = HighlightedRuns();
|
||||
|
||||
const auto inlinesCollection = textBlock.Inlines();
|
||||
inlinesCollection.Clear();
|
||||
|
||||
// The code below constructs local hstring instances because hstring is required to be null-terminated
|
||||
// and slicing _does not_ guarantee null termination. Passing a sliced wstring_view directly into run.Text()
|
||||
// (which is a winrt::param::hstring--different thing!--will result in an exception when the sliced portion
|
||||
// is not null-terminated.
|
||||
if (!text.empty())
|
||||
{
|
||||
size_t lastPos = 0;
|
||||
if (runs && runs.Size())
|
||||
{
|
||||
const auto runStyle = HighlightedRunStyle();
|
||||
|
||||
for (const auto& [start, end] : runs)
|
||||
{
|
||||
if (start > lastPos)
|
||||
{
|
||||
const hstring nonMatch{ til::safe_slice_abs(text, lastPos, static_cast<size_t>(start)) };
|
||||
Documents::Run run;
|
||||
run.Text(nonMatch);
|
||||
inlinesCollection.Append(run);
|
||||
}
|
||||
|
||||
const hstring matchSeg{ til::safe_slice_abs(text, static_cast<size_t>(start), static_cast<size_t>(end + 1)) };
|
||||
Documents::Run run;
|
||||
run.Text(matchSeg);
|
||||
|
||||
if (runStyle) [[unlikely]]
|
||||
{
|
||||
_applyStyleToObject(runStyle, run);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default style: bold
|
||||
run.FontWeight(FontWeights::Bold());
|
||||
}
|
||||
inlinesCollection.Append(run);
|
||||
|
||||
lastPos = static_cast<size_t>(end + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// This will also be true if there are no runs at all
|
||||
if (lastPos < text.size())
|
||||
{
|
||||
// checking lastPos here prevents a needless deep copy of the whole text in the no-match case
|
||||
const hstring tail{ lastPos == 0 ? text : hstring{ til::safe_slice_abs(text, lastPos, SIZE_T_MAX) } };
|
||||
Documents::Run run;
|
||||
run.Text(matchText);
|
||||
run.FontWeight(fontWeight);
|
||||
run.Text(tail);
|
||||
inlinesCollection.Append(run);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "winrt/Microsoft.UI.Xaml.Controls.h"
|
||||
|
||||
#include "HighlightedTextControl.g.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
@@ -13,16 +11,17 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
HighlightedTextControl();
|
||||
|
||||
static Windows::UI::Xaml::DependencyProperty TextProperty();
|
||||
void OnApplyTemplate();
|
||||
|
||||
winrt::TerminalApp::HighlightedText Text();
|
||||
void Text(const winrt::TerminalApp::HighlightedText& value);
|
||||
|
||||
Windows::UI::Xaml::Controls::TextBlock TextView();
|
||||
DEPENDENCY_PROPERTY(winrt::hstring, Text);
|
||||
DEPENDENCY_PROPERTY(winrt::Windows::Foundation::Collections::IVector<winrt::TerminalApp::HighlightedRun>, HighlightedRuns);
|
||||
DEPENDENCY_PROPERTY(winrt::Windows::UI::Xaml::Style, TextBlockStyle);
|
||||
DEPENDENCY_PROPERTY(winrt::Windows::UI::Xaml::Style, HighlightedRunStyle);
|
||||
|
||||
private:
|
||||
static Windows::UI::Xaml::DependencyProperty _textProperty;
|
||||
static void _onTextChanged(const Windows::UI::Xaml::DependencyObject& o, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
|
||||
static void _InitializeProperties();
|
||||
static void _onPropertyChanged(const Windows::UI::Xaml::DependencyObject& o, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
|
||||
void _updateTextAndStyle();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "HighlightedText.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
struct HighlightedRun
|
||||
{
|
||||
UInt64 Start;
|
||||
UInt64 End;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass HighlightedTextControl : Windows.UI.Xaml.Controls.Control
|
||||
{
|
||||
HighlightedTextControl();
|
||||
|
||||
String Text;
|
||||
Windows.UI.Xaml.DependencyProperty TextProperty { get; };
|
||||
HighlightedText Text;
|
||||
Windows.UI.Xaml.Controls.TextBlock TextView { get; };
|
||||
|
||||
IVector<HighlightedRun> HighlightedRuns;
|
||||
Windows.UI.Xaml.DependencyProperty HighlightedRunsProperty { get; };
|
||||
|
||||
Windows.UI.Xaml.Style TextBlockStyle;
|
||||
Windows.UI.Xaml.DependencyProperty TextBlockStyleProperty { get; };
|
||||
|
||||
Windows.UI.Xaml.Style HighlightedRunStyle;
|
||||
Windows.UI.Xaml.DependencyProperty HighlightedRunStyleProperty { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<UserControl x:Class="TerminalApp.HighlightedTextControl"
|
||||
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:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<TextBlock x:Name="_textView" />
|
||||
</UserControl>
|
||||
23
src/cascadia/TerminalApp/HighlightedTextControlStyle.xaml
Normal file
23
src/cascadia/TerminalApp/HighlightedTextControlStyle.xaml
Normal file
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<ResourceDictionary 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:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Style TargetType="local:HighlightedTextControl">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:HighlightedTextControl">
|
||||
<TextBlock x:Name="TextView"
|
||||
Style="{TemplateBinding TextBlockStyle}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -170,8 +170,9 @@
|
||||
|
||||
<local:HighlightedTextControl Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
HighlightedRuns="{x:Bind FilteredCommand.NameHighlights, Mode=OneWay}"
|
||||
IsTabStop="False"
|
||||
Text="{x:Bind FilteredCommand.HighlightedName, Mode=OneWay}" />
|
||||
Text="{x:Bind FilteredCommand.Item.Name, Mode=OneWay}" />
|
||||
|
||||
<!--
|
||||
BODGY: I can't choose different templates if this item is nested or not.
|
||||
|
||||
@@ -57,7 +57,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}" />
|
||||
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -84,7 +85,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}" />
|
||||
|
||||
<FontIcon Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
|
||||
@@ -60,7 +60,8 @@
|
||||
<Page Include="TabHeaderControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="HighlightedTextControl.xaml">
|
||||
<Page Include="HighlightedTextControlStyle.xaml">
|
||||
<Type>DefaultStyle</Type>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="ColorPickupFlyout.xaml">
|
||||
@@ -127,9 +128,8 @@
|
||||
<DependentUpon>TabHeaderControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HighlightedTextControl.h">
|
||||
<DependentUpon>HighlightedTextControl.xaml</DependentUpon>
|
||||
<DependentUpon>HighlightedTextControl.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HighlightedText.h" />
|
||||
<ClInclude Include="ColorPickupFlyout.h">
|
||||
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -247,9 +247,8 @@
|
||||
<DependentUpon>TabHeaderControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HighlightedTextControl.cpp">
|
||||
<DependentUpon>HighlightedTextControl.xaml</DependentUpon>
|
||||
<DependentUpon>HighlightedTextControl.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HighlightedText.cpp" />
|
||||
<ClCompile Include="ColorPickupFlyout.cpp">
|
||||
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -365,10 +364,8 @@
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="HighlightedTextControl.idl">
|
||||
<DependentUpon>HighlightedTextControl.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="HighlightedText.idl" />
|
||||
<Midl Include="ColorPickupFlyout.idl">
|
||||
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
<ClCompile Include="CommandLinePaletteItem.cpp">
|
||||
<Filter>commandPalette</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HighlightedText.cpp">
|
||||
<Filter>highlightedText</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="fzf/fzf.cpp">
|
||||
<Filter>fzf</Filter>
|
||||
</ClCompile>
|
||||
@@ -77,9 +74,6 @@
|
||||
<ClInclude Include="CommandLinePaletteItem.h">
|
||||
<Filter>commandPalette</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HighlightedText.h">
|
||||
<Filter>highlightedText</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="fzf/fzf.h">
|
||||
<Filter>fzf</Filter>
|
||||
</ClInclude>
|
||||
@@ -108,9 +102,6 @@
|
||||
<Midl Include="FilteredCommand.idl">
|
||||
<Filter>commandPalette</Filter>
|
||||
</Midl>
|
||||
<Midl Include="HighlightedText.idl">
|
||||
<Filter>highlightedText</Filter>
|
||||
</Midl>
|
||||
<Midl Include="TerminalTabStatus.idl">
|
||||
<Filter>tab</Filter>
|
||||
</Midl>
|
||||
@@ -156,7 +147,7 @@
|
||||
<Midl Include="CommandLinePaletteItem.idl">
|
||||
<Filter>commandPalette</Filter>
|
||||
</Midl>
|
||||
<Page Include="HighlightedTextControl.xaml">
|
||||
<Page Include="HighlightedTextControlStyle.xaml">
|
||||
<Filter>highlightedText</Filter>
|
||||
</Page>
|
||||
<Page Include="AboutDialog.xaml" />
|
||||
|
||||
@@ -74,27 +74,6 @@ private:
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<winrt::Microsoft::Terminal::Settings::Editor::EnumEntry> _##name##List; \
|
||||
winrt::Windows::Foundation::Collections::IMap<enumType, winrt::Microsoft::Terminal::Settings::Editor::EnumEntry> _##name##Map;
|
||||
|
||||
// This macro defines a dependency property for a WinRT class.
|
||||
// Use this in your class' header file after declaring it in the idl.
|
||||
// Remember to register your dependency property in the respective cpp file.
|
||||
#define DEPENDENCY_PROPERTY(type, name) \
|
||||
public: \
|
||||
static winrt::Windows::UI::Xaml::DependencyProperty name##Property() \
|
||||
{ \
|
||||
return _##name##Property; \
|
||||
} \
|
||||
type name() const \
|
||||
{ \
|
||||
return winrt::unbox_value<type>(GetValue(_##name##Property)); \
|
||||
} \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
SetValue(_##name##Property, winrt::box_value(value)); \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
static winrt::Windows::UI::Xaml::DependencyProperty _##name##Property;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings
|
||||
{
|
||||
winrt::hstring GetSelectedItemTag(const winrt::Windows::Foundation::IInspectable& comboBoxAsInspectable);
|
||||
|
||||
@@ -213,6 +213,46 @@ protected:
|
||||
_##name = value; \
|
||||
};
|
||||
|
||||
// This macro defines a dependency property for a WinRT class.
|
||||
// Use this in your class' header file after declaring it in the idl.
|
||||
// Remember to register your dependency property in the respective cpp file.
|
||||
#ifndef DEPENDENCY_PROPERTY
|
||||
#define DEPENDENCY_PROPERTY(type, name) \
|
||||
public: \
|
||||
static winrt::Windows::UI::Xaml::DependencyProperty name##Property() \
|
||||
{ \
|
||||
return _##name##Property; \
|
||||
} \
|
||||
type name() const \
|
||||
{ \
|
||||
auto&& temp{ GetValue(_##name##Property) }; \
|
||||
if (temp) \
|
||||
{ \
|
||||
return winrt::unbox_value<type>(temp); \
|
||||
} \
|
||||
\
|
||||
if constexpr (std::is_same_v<type, winrt::hstring>) \
|
||||
{ \
|
||||
return winrt::hstring{}; \
|
||||
} \
|
||||
else if constexpr (std::is_base_of_v<winrt::Windows::Foundation::IInspectable, type>) \
|
||||
{ \
|
||||
return { nullptr }; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
return {}; \
|
||||
} \
|
||||
} \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
SetValue(_##name##Property, winrt::box_value(value)); \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
static winrt::Windows::UI::Xaml::DependencyProperty _##name##Property;
|
||||
#endif
|
||||
|
||||
// Use this macro for quickly defining the factory_implementation part of a
|
||||
// class. CppWinrt requires these for the compiler, but more often than not,
|
||||
// they require no customization. See
|
||||
|
||||
Reference in New Issue
Block a user