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:
Dustin L. Howett
2025-07-14 15:40:20 -05:00
committed by GitHub
parent f14718f738
commit 0cbb6b1f2f
21 changed files with 305 additions and 275 deletions

View File

@@ -2,6 +2,7 @@ aaaaabbb
aabbcc
ABANDONFONT
abbcc
abcc
abgr
ABORTIFHUNG
ACCESSTOKEN

View File

@@ -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
}
});

View File

@@ -243,6 +243,8 @@
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
<ResourceDictionary Source="ms-resource:///Files/TerminalApp/HighlightedTextControlStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -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"

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -12,7 +12,7 @@ namespace TerminalApp
FilteredCommand(PaletteItem item);
PaletteItem Item { get; };
HighlightedText HighlightedName { get; };
IVector<HighlightedRun> NameHighlights { get; };
Int32 Weight;
}
}

View File

@@ -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)
{
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
};
}

View File

@@ -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; };
}
}

View File

@@ -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>

View 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>

View File

@@ -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.

View File

@@ -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"

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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);

View File

@@ -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