Bold matching text in the command palette (#7977)
* Created a ViewModel class in the Command Palette called FilteredCommand, aggregating the Command, the filter and the highlighted presentation of the command name * This ListView of the filtered commands is bound to the vector of FilteredCommands * Introduced HighlightedTextControl user control with HighlightedText view model * Added this control to the ListView Item's grid * Bound the FilteredCommand's highlighted command name to the user control ## Validation Steps Performed * UT for matching algorithm * Only manual tests * Searching in CommandLine, SwitchTab and Nested Command modes * Checking for bot matching an non matching filters * Dogfooding Closes #6646
This commit is contained in:
parent
015675d87c
commit
1aff3bc216
|
@ -2825,3 +2825,7 @@ zsh
|
|||
zu
|
||||
zxcvbnm
|
||||
zy
|
||||
AAAAABBBBBBCCC
|
||||
AAAAA
|
||||
BBBBBCCC
|
||||
abcd
|
|
@ -0,0 +1,271 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "../TerminalApp/TerminalSettings.h"
|
||||
#include "../TerminalApp/CommandPalette.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
class FilteredCommandTests
|
||||
{
|
||||
BEGIN_TEST_CLASS(FilteredCommandTests)
|
||||
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(VerifyHighlighting);
|
||||
TEST_METHOD(VerifyWeight);
|
||||
TEST_METHOD(VerifyCompare);
|
||||
};
|
||||
|
||||
void FilteredCommandTests::VerifyHighlighting()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" }, "name": "AAAAAABBBBBBCCC" }
|
||||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
const auto commands = settings.GlobalSettings().Commands();
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
const auto command = commands.Lookup(L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_ARE_EQUAL(command.Name(), L"AAAAAABBBBBBCCC");
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter equals to the string");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"AAAAAABBBBBBCCC";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter with first character matching");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"A";
|
||||
auto segments = filteredCommand->_computeHighlightedName().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());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter with other case");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"a";
|
||||
auto segments = filteredCommand->_computeHighlightedName().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());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter matching several characters");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"ab";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 4u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAA");
|
||||
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"B");
|
||||
VERIFY_IS_TRUE(segments.GetAt(2).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(3).TextSegment(), L"BBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(3).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with non matching filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"abcd";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
}
|
||||
|
||||
void FilteredCommandTests::VerifyWeight()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" }, "name": "AAAAAABBBBBBCCC" }
|
||||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
const auto commands = settings.GlobalSettings().Commands();
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
const auto command = commands.Lookup(L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_ARE_EQUAL(command.Name(), L"AAAAAABBBBBBCCC");
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 0);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 0);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter equals to the string");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"AAAAAABBBBBBCCC";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 30); // 1 point for the first char and 2 points for the 14 consequent ones + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter with first character matching");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"A";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter with other case");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"a";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter matching several characters");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"ab";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 3); // 1 point for the first char match + 1 point for the beginning of the word + 1 point for the match of "b"
|
||||
}
|
||||
}
|
||||
|
||||
void FilteredCommandTests::VerifyCompare()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" }, "name": "AAAAAABBBBBBCCC" },
|
||||
{ "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "horizontal" }, "name": "BBBBBCCC" }
|
||||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
const auto commands = settings.GlobalSettings().Commands();
|
||||
VERIFY_ARE_EQUAL(2u, commands.Size());
|
||||
|
||||
const auto command = commands.Lookup(L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_ARE_EQUAL(command.Name(), L"AAAAAABBBBBBCCC");
|
||||
|
||||
const auto command2 = commands.Lookup(L"BBBBBCCC");
|
||||
VERIFY_IS_NOT_NULL(command2);
|
||||
VERIFY_ARE_EQUAL(command2.Name(), L"BBBBBCCC");
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command2);
|
||||
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
filteredCommand->_Weight = filteredCommand->_computeWeight();
|
||||
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command2);
|
||||
filteredCommand2->_Filter = L"";
|
||||
filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName();
|
||||
filteredCommand2->_Weight = filteredCommand2->_computeWeight();
|
||||
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with different weights");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command);
|
||||
filteredCommand->_Filter = L"B";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
filteredCommand->_Weight = filteredCommand->_computeWeight();
|
||||
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(command2);
|
||||
filteredCommand2->_Filter = L"B";
|
||||
filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName();
|
||||
filteredCommand2->_Weight = filteredCommand2->_computeWeight();
|
||||
|
||||
VERIFY_IS_TRUE(filteredCommand->Weight() < filteredCommand2->Weight()); // Second command gets more points due to the beginning of the word
|
||||
VERIFY_IS_FALSE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,6 +45,7 @@
|
|||
<ClCompile Include="CommandlineTest.cpp" />
|
||||
<ClCompile Include="SettingsTests.cpp" />
|
||||
<ClCompile Include="TabTests.cpp" />
|
||||
<ClCompile Include="FilteredCommandTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
|
|
@ -24,11 +24,11 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
InitializeComponent();
|
||||
|
||||
_filteredActions = winrt::single_threaded_observable_vector<Command>();
|
||||
_nestedActionStack = winrt::single_threaded_vector<Command>();
|
||||
_currentNestedCommands = winrt::single_threaded_vector<Command>();
|
||||
_allCommands = winrt::single_threaded_vector<Command>();
|
||||
_tabActions = winrt::single_threaded_vector<Command>();
|
||||
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
_nestedActionStack = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
_currentNestedCommands = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
_allCommands = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
_tabActions = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
|
||||
_switchToMode(CommandPaletteMode::ActionMode);
|
||||
|
||||
|
@ -176,10 +176,10 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
const auto& selectedCommand = _filteredActionsView().SelectedItem();
|
||||
if (const auto& command = selectedCommand.try_as<Command>())
|
||||
const auto selectedCommand = _filteredActionsView().SelectedItem();
|
||||
if (const auto filteredCommand = selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>())
|
||||
{
|
||||
const auto& actionAndArgs = command.Action();
|
||||
const auto& actionAndArgs = filteredCommand.Command().Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
}
|
||||
}
|
||||
|
@ -267,9 +267,10 @@ namespace winrt::TerminalApp::implementation
|
|||
// Action, TabSwitch or TabSearchMode Mode: Dispatch the action of the selected command.
|
||||
if (_currentMode != CommandPaletteMode::CommandlineMode)
|
||||
{
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
const auto selectedCommand = _filteredActionsView().SelectedItem();
|
||||
if (const auto filteredCommand = selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>())
|
||||
{
|
||||
_dispatchCommand(selectedItem.try_as<Command>());
|
||||
_dispatchCommand(filteredCommand);
|
||||
}
|
||||
}
|
||||
// Commandline Mode: Use the input to synthesize an ExecuteCommandline action
|
||||
|
@ -376,12 +377,10 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
if (!ctrlDown && !altDown && !shiftDown)
|
||||
{
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
const auto selectedCommand = _filteredActionsView().SelectedItem();
|
||||
if (const auto filteredCommand = selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>())
|
||||
{
|
||||
if (const auto data = selectedItem.try_as<Command>())
|
||||
{
|
||||
_dispatchCommand(data);
|
||||
}
|
||||
_dispatchCommand(filteredCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -425,7 +424,11 @@ namespace winrt::TerminalApp::implementation
|
|||
void CommandPalette::_listItemClicked(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Controls::ItemClickEventArgs const& e)
|
||||
{
|
||||
_dispatchCommand(e.ClickedItem().try_as<Command>());
|
||||
const auto selectedCommand = e.ClickedItem();
|
||||
if (const auto filteredCommand = selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>())
|
||||
{
|
||||
_dispatchCommand(filteredCommand);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -468,6 +471,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// Changing the value of the search box will trigger _filterTextChanged,
|
||||
// which will cause us to refresh the list of filterable commands.
|
||||
_searchBox().Text(L"");
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -479,7 +483,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// - <none>
|
||||
// Return Value:
|
||||
// - A list of Commands to filter.
|
||||
Collections::IVector<Command> CommandPalette::_commandsToFilter()
|
||||
Collections::IVector<winrt::TerminalApp::FilteredCommand> CommandPalette::_commandsToFilter()
|
||||
{
|
||||
switch (_currentMode)
|
||||
{
|
||||
|
@ -495,7 +499,7 @@ namespace winrt::TerminalApp::implementation
|
|||
case CommandPaletteMode::TabSwitchMode:
|
||||
return _tabActions;
|
||||
case CommandPaletteMode::CommandlineMode:
|
||||
return winrt::single_threaded_vector<Command>();
|
||||
return winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
default:
|
||||
return _allCommands;
|
||||
}
|
||||
|
@ -510,21 +514,23 @@ namespace winrt::TerminalApp::implementation
|
|||
// - command: the Command to dispatch. This might be null.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_dispatchCommand(const Command& command)
|
||||
void CommandPalette::_dispatchCommand(winrt::TerminalApp::FilteredCommand const& filteredCommand)
|
||||
{
|
||||
if (command)
|
||||
if (filteredCommand)
|
||||
{
|
||||
if (command.HasNestedCommands())
|
||||
if (filteredCommand.Command().HasNestedCommands())
|
||||
{
|
||||
// If this Command had subcommands, then don't dispatch the
|
||||
// action. Instead, display a new list of commands for the user
|
||||
// to pick from.
|
||||
_nestedActionStack.Append(command);
|
||||
ParentCommandName(command.Name());
|
||||
_nestedActionStack.Append(filteredCommand);
|
||||
ParentCommandName(filteredCommand.Command().Name());
|
||||
_currentNestedCommands.Clear();
|
||||
for (const auto& nameAndCommand : command.NestedCommands())
|
||||
for (const auto& nameAndCommand : filteredCommand.Command().NestedCommands())
|
||||
{
|
||||
_currentNestedCommands.Append(nameAndCommand.Value());
|
||||
const auto action = nameAndCommand.Value();
|
||||
auto nestedFilteredCommand{ winrt::make<FilteredCommand>(action) };
|
||||
_currentNestedCommands.Append(nestedFilteredCommand);
|
||||
}
|
||||
|
||||
_updateUIForStackChange();
|
||||
|
@ -541,7 +547,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// palette like the Tab Switcher will be able to have the last laugh.
|
||||
_close();
|
||||
|
||||
const auto actionAndArgs = command.Action();
|
||||
const auto actionAndArgs = filteredCommand.Command().Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
|
||||
TraceLoggingWrite(
|
||||
|
@ -688,7 +694,7 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
Collections::IObservableVector<Command> CommandPalette::FilteredActions()
|
||||
Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> CommandPalette::FilteredActions()
|
||||
{
|
||||
return _filteredActions;
|
||||
}
|
||||
|
@ -700,13 +706,13 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
void CommandPalette::SetCommands(Collections::IVector<Command> const& actions)
|
||||
{
|
||||
_allCommands = actions;
|
||||
_populateFilteredActions(_allCommands, actions);
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
void CommandPalette::SetTabActions(Collections::IVector<Command> const& tabs, const bool clearList)
|
||||
{
|
||||
_tabActions = tabs;
|
||||
_populateFilteredActions(_tabActions, tabs);
|
||||
// The smooth remove/add animations that happen during
|
||||
// UpdateFilteredActions don't work very well with changing the tab
|
||||
// order, because of the sheer amount of remove/adds. So, let's just
|
||||
|
@ -721,6 +727,24 @@ namespace winrt::TerminalApp::implementation
|
|||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This helper function is responsible to update a collection of filtered commands (e.g., tab switcher commands)
|
||||
// with the new values
|
||||
// Arguments:
|
||||
// - vectorToPopulate - the vector of filtered commands to populate
|
||||
// - actions - the raw commands to use
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_populateFilteredActions(Collections::IVector<winrt::TerminalApp::FilteredCommand> const& vectorToPopulate, Collections::IVector<Command> const& actions)
|
||||
{
|
||||
vectorToPopulate.Clear();
|
||||
for (const auto& action : actions)
|
||||
{
|
||||
auto filteredCommand{ winrt::make<FilteredCommand>(action) };
|
||||
vectorToPopulate.Append(filteredCommand);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPalette::EnableCommandPaletteMode()
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::ActionMode);
|
||||
|
@ -777,146 +801,42 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
static bool _compareCommandNames(const Command& lhs, const Command& rhs)
|
||||
{
|
||||
std::wstring_view leftName{ lhs.Name() };
|
||||
std::wstring_view rightName{ rhs.Name() };
|
||||
return leftName.compare(rightName) < 0;
|
||||
}
|
||||
|
||||
// This is a helper struct to aid in sorting Commands by a given weighting.
|
||||
struct WeightedCommand
|
||||
{
|
||||
Command command;
|
||||
int weight;
|
||||
int inOrderCounter;
|
||||
|
||||
bool operator<(const WeightedCommand& other) const
|
||||
{
|
||||
if (weight == other.weight)
|
||||
{
|
||||
// If two commands have the same weight, then we'll sort them alphabetically.
|
||||
// If they both have the same name, fall back to the order in which they were
|
||||
// pushed into the heap.
|
||||
if (command.Name() == other.command.Name())
|
||||
{
|
||||
return inOrderCounter > other.inOrderCounter;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !_compareCommandNames(command, other.command);
|
||||
}
|
||||
}
|
||||
return weight < other.weight;
|
||||
}
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
// - Produce a list of filtered actions to reflect the current contents of
|
||||
// the input box. For more details on which commands will be displayed,
|
||||
// see `_getWeight`.
|
||||
// the input box.
|
||||
// Arguments:
|
||||
// - A collection that will receive the filtered actions
|
||||
// Return Value:
|
||||
// - <none>
|
||||
std::vector<Command> CommandPalette::_collectFilteredActions()
|
||||
std::vector<winrt::TerminalApp::FilteredCommand> CommandPalette::_collectFilteredActions()
|
||||
{
|
||||
std::vector<Command> actions;
|
||||
std::vector<winrt::TerminalApp::FilteredCommand> actions;
|
||||
|
||||
winrt::hstring searchText{ _getTrimmedInput() };
|
||||
const bool addAll = searchText.empty();
|
||||
|
||||
auto commandsToFilter = _commandsToFilter();
|
||||
|
||||
// If there's no filter text, then just add all the commands in order to the list.
|
||||
// - TODO GH#6647:Possibly add the MRU commands first in order, followed
|
||||
// by the rest of the commands.
|
||||
if (addAll)
|
||||
for (const auto& action : commandsToFilter)
|
||||
{
|
||||
// If TabSwitcherMode, just add all as is. We don't want
|
||||
// them to be sorted alphabetically.
|
||||
if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
for (auto action : commandsToFilter)
|
||||
{
|
||||
actions.push_back(action);
|
||||
}
|
||||
// Update filter for all commands
|
||||
// This will modify the highlighting but will also lead to recomputation of weight (and consequently sorting).
|
||||
// Pay attention that it already updates the highlighting in the UI
|
||||
action.UpdateFilter(searchText);
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Add all the commands, but make sure they're sorted alphabetically.
|
||||
std::vector<Command> sortedCommands;
|
||||
sortedCommands.reserve(commandsToFilter.Size());
|
||||
|
||||
for (auto action : commandsToFilter)
|
||||
{
|
||||
sortedCommands.push_back(action);
|
||||
}
|
||||
std::sort(sortedCommands.begin(),
|
||||
sortedCommands.end(),
|
||||
_compareCommandNames);
|
||||
|
||||
for (auto action : sortedCommands)
|
||||
// if there is active search we skip commands with 0 weight
|
||||
if (searchText.empty() || action.Weight() > 0)
|
||||
{
|
||||
actions.push_back(action);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Here, there was some filter text.
|
||||
// Show these actions in a weighted order.
|
||||
// - Matching the first character of a word, then the first char of a
|
||||
// subsequent word seems better than just "the order they appear in
|
||||
// the list".
|
||||
// - TODO GH#6647:"Recently used commands" ordering also seems valuable.
|
||||
// * This could be done by weighting the recently used commands
|
||||
// higher the more recently they were used, then weighting all
|
||||
// the unused commands as 1
|
||||
|
||||
// Use a priority queue to order commands so that "better" matches
|
||||
// appear first in the list. The ordering will be determined by the
|
||||
// match weight produced by _getWeight.
|
||||
std::priority_queue<WeightedCommand> heap;
|
||||
|
||||
// TODO GH#7205: Find a better way to ensure that WCs of the same
|
||||
// weight and name stay in the order in which they were pushed onto
|
||||
// the PQ.
|
||||
uint32_t counter = 0;
|
||||
for (auto action : commandsToFilter)
|
||||
{
|
||||
const auto weight = CommandPalette::_getWeight(searchText, action.Name());
|
||||
if (weight > 0)
|
||||
{
|
||||
WeightedCommand wc;
|
||||
wc.command = action;
|
||||
wc.weight = weight;
|
||||
wc.inOrderCounter = counter++;
|
||||
|
||||
heap.push(wc);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, all the commands in heap are matches. We've also
|
||||
// sorted commands with the same weight alphabetically.
|
||||
// Remove everything in-order from the queue, and add to the list of
|
||||
// filtered actions.
|
||||
while (!heap.empty())
|
||||
{
|
||||
auto top = heap.top();
|
||||
heap.pop();
|
||||
actions.push_back(top.command);
|
||||
}
|
||||
|
||||
// Add all the commands, but make sure they're sorted.
|
||||
std::sort(actions.begin(), actions.end(), FilteredCommand::Compare);
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update our list of filtered actions to reflect the current contents of
|
||||
// the input box. For more details on which commands will be displayed,
|
||||
// see `_getWeight`.
|
||||
// the input box.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
|
@ -937,7 +857,7 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
for (uint32_t j = i; j < _filteredActions.Size(); j++)
|
||||
{
|
||||
if (_filteredActions.GetAt(j) == actions[i])
|
||||
if (_filteredActions.GetAt(j).Command() == actions[i].Command())
|
||||
{
|
||||
for (uint32_t k = i; k < j; k++)
|
||||
{
|
||||
|
@ -947,7 +867,7 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
if (_filteredActions.GetAt(i) != actions[i])
|
||||
if (_filteredActions.GetAt(i).Command() != actions[i].Command())
|
||||
{
|
||||
_filteredActions.InsertAt(i, actions[i]);
|
||||
}
|
||||
|
@ -966,92 +886,6 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Calculates a "weighting" by which should be used to order a command
|
||||
// name relative to other names, given a specific search string.
|
||||
// Currently, this is based off of two factors:
|
||||
// * The weight is incremented once for each matched character of the
|
||||
// search text.
|
||||
// * If a matching character from the search text was found at the start
|
||||
// of a word in the name, then we increment the weight again.
|
||||
// * For example, for a search string "sp", we want "Split Pane" to
|
||||
// appear in the list before "Close Pane"
|
||||
// * Consecutive matches will be weighted higher than matches with
|
||||
// characters in between the search characters.
|
||||
// - This will return 0 if the command should not be shown. If all the
|
||||
// characters of search text appear in order in `name`, then this function
|
||||
// will return a positive number. There can be any number of characters
|
||||
// separating consecutive characters in searchText.
|
||||
// * For example:
|
||||
// "name": "New Tab"
|
||||
// "name": "Close Tab"
|
||||
// "name": "Close Pane"
|
||||
// "name": "[-] Split Horizontal"
|
||||
// "name": "[ | ] Split Vertical"
|
||||
// "name": "Next Tab"
|
||||
// "name": "Prev Tab"
|
||||
// "name": "Open Settings"
|
||||
// "name": "Open Media Controls"
|
||||
// * "open" should return both "**Open** Settings" and "**Open** Media Controls".
|
||||
// * "Tab" would return "New **Tab**", "Close **Tab**", "Next **Tab**" and "Prev
|
||||
// **Tab**".
|
||||
// * "P" would return "Close **P**ane", "[-] S**p**lit Horizontal", "[ | ]
|
||||
// S**p**lit Vertical", "**P**rev Tab", "O**p**en Settings" and "O**p**en Media
|
||||
// Controls".
|
||||
// * "sv" would return "[ | ] Split Vertical" (by matching the **S** in
|
||||
// "Split", then the **V** in "Vertical").
|
||||
// Arguments:
|
||||
// - searchText: the string of text to search for in `name`
|
||||
// - name: the name to check
|
||||
// Return Value:
|
||||
// - the relative weight of this match
|
||||
int CommandPalette::_getWeight(const winrt::hstring& searchText,
|
||||
const winrt::hstring& name)
|
||||
{
|
||||
int totalWeight = 0;
|
||||
bool lastWasSpace = true;
|
||||
|
||||
auto it = name.cbegin();
|
||||
|
||||
for (auto searchChar : searchText)
|
||||
{
|
||||
searchChar = std::towlower(searchChar);
|
||||
// Advance the iterator to the next character that we're looking
|
||||
// for.
|
||||
|
||||
bool lastWasMatch = true;
|
||||
while (true)
|
||||
{
|
||||
// If we are at the end of the name string, we haven't found
|
||||
// it.
|
||||
if (it == name.cend())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// found it
|
||||
if (std::towlower(*it) == searchChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
lastWasSpace = *it == L' ';
|
||||
++it;
|
||||
lastWasMatch = false;
|
||||
}
|
||||
|
||||
// Advance the iterator by one character so that we don't
|
||||
// end up on the same character in the next iteration.
|
||||
++it;
|
||||
|
||||
totalWeight += 1;
|
||||
totalWeight += lastWasSpace ? 1 : 0;
|
||||
totalWeight += (lastWasMatch) ? 1 : 0;
|
||||
}
|
||||
|
||||
return totalWeight;
|
||||
}
|
||||
|
||||
void CommandPalette::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
|
||||
{
|
||||
_dispatch = dispatch;
|
||||
|
|
|
@ -3,9 +3,16 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "FilteredCommand.h"
|
||||
#include "CommandPalette.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
class FilteredCommandTests;
|
||||
};
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
enum class CommandPaletteMode
|
||||
|
@ -20,7 +27,7 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
CommandPalette();
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Model::Command> FilteredActions();
|
||||
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> FilteredActions();
|
||||
|
||||
void SetCommands(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> const& actions);
|
||||
void SetTabActions(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> const& tabs, const bool clearList);
|
||||
|
@ -52,13 +59,13 @@ namespace winrt::TerminalApp::implementation
|
|||
private:
|
||||
friend struct CommandPaletteT<CommandPalette>; // for Xaml to bind events
|
||||
|
||||
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _allCommands{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _currentNestedCommands{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Model::Command> _filteredActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _nestedActionStack{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _allCommands{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _currentNestedCommands{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> _filteredActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _nestedActionStack{ nullptr };
|
||||
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _commandsToFilter();
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _commandsToFilter();
|
||||
|
||||
bool _lastFilterTextWasEmpty{ true };
|
||||
|
||||
|
@ -85,7 +92,10 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
void _updateFilteredActions();
|
||||
|
||||
std::vector<Microsoft::Terminal::Settings::Model::Command> _collectFilteredActions();
|
||||
void _populateFilteredActions(Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> const& vectorToPopulate,
|
||||
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> const& actions);
|
||||
|
||||
std::vector<winrt::TerminalApp::FilteredCommand> _collectFilteredActions();
|
||||
|
||||
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
|
||||
void _close();
|
||||
|
@ -99,13 +109,13 @@ namespace winrt::TerminalApp::implementation
|
|||
Microsoft::Terminal::TerminalControl::IKeyBindings _bindings;
|
||||
|
||||
// Tab Switcher
|
||||
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _tabActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _tabActions{ nullptr };
|
||||
uint32_t _switcherStartIdx;
|
||||
void _anchorKeyUpHandler();
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker;
|
||||
|
||||
void _dispatchCommand(const Microsoft::Terminal::Settings::Model::Command& command);
|
||||
void _dispatchCommand(winrt::TerminalApp::FilteredCommand const& command);
|
||||
void _dispatchCommandline();
|
||||
void _dismissPalette();
|
||||
};
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import "IDirectKeyListener.idl";
|
||||
import "ShortcutActionDispatch.idl";
|
||||
import "HighlightedTextControl.idl";
|
||||
import "FilteredCommand.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
|
@ -16,7 +18,7 @@ namespace TerminalApp
|
|||
String ControlName { get; };
|
||||
String ParentCommandName { get; };
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Model.Command> FilteredActions { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<FilteredCommand> FilteredActions { get; };
|
||||
|
||||
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
|
||||
void SetTabActions(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> tabs, Boolean clearList);
|
||||
|
|
|
@ -114,7 +114,6 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="6*" />
|
||||
|
@ -240,16 +239,15 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
ItemsSource="{x:Bind FilteredActions}">
|
||||
|
||||
<ItemsControl.ItemTemplate >
|
||||
<DataTemplate x:DataType="SettingsModel:Command">
|
||||
<DataTemplate x:DataType="local:FilteredCommand">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}">
|
||||
AutomationProperties.Name="{x:Bind Command.Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind Command.KeyChordText, Mode=OneWay}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/> <!-- icon -->
|
||||
<ColumnDefinition Width="Auto"/> <!-- command label -->
|
||||
|
@ -261,20 +259,21 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind Icon,
|
||||
IconSource="{x:Bind Command.Icon,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource IconSourceConverter}}"/>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
|
||||
<local:HighlightedTextControl
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind HighlightedName, Mode=OneWay}"/>
|
||||
|
||||
<!-- The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details. -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Visibility="{x:Bind KeyChordText,
|
||||
Visibility="{x:Bind Command.KeyChordText,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource CommandKeyChordVisibilityConverter}}"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
|
@ -285,7 +284,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
<TextBlock
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}" />
|
||||
Text="{x:Bind Command.KeyChordText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
<!-- xE70E is ChevronUp. Rotated 90 degrees, it's _ChevronRight_ -->
|
||||
|
@ -293,7 +292,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
FontFamily="Segoe MDL2 Assets"
|
||||
Glyph=""
|
||||
HorizontalAlignment="Right"
|
||||
Visibility="{x:Bind HasNestedCommands,
|
||||
Visibility="{x:Bind Command.HasNestedCommands,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource HasNestedCommandsVisibilityConverter}}"
|
||||
Grid.Column="2">
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "CommandPalette.h"
|
||||
#include "HighlightedText.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "FilteredCommand.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::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// This is a view model class that extends the Command model,
|
||||
// by managing a highlighted text that is computed by matching search filter characters to command name
|
||||
FilteredCommand::FilteredCommand(Microsoft::Terminal::Settings::Model::Command const& command) :
|
||||
_Command(command),
|
||||
_Filter(L""),
|
||||
_Weight(0)
|
||||
{
|
||||
_HighlightedName = _computeHighlightedName();
|
||||
|
||||
// Recompute the highlighted name if the command name changes
|
||||
_commandChangedRevoker = _Command.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](Windows::Foundation::IInspectable const& /*sender*/, Data::PropertyChangedEventArgs const& e) {
|
||||
auto filteredCommand{ weakThis.get() };
|
||||
if (filteredCommand && e.PropertyName() == L"Name")
|
||||
{
|
||||
filteredCommand->HighlightedName(filteredCommand->_computeHighlightedName());
|
||||
filteredCommand->Weight(filteredCommand->_computeWeight());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FilteredCommand::UpdateFilter(winrt::hstring const& filter)
|
||||
{
|
||||
// If the filter was not changed we want to prevent the re-computation of matching
|
||||
// that might result in triggering a notification event
|
||||
if (filter != _Filter)
|
||||
{
|
||||
Filter(filter);
|
||||
HighlightedName(_computeHighlightedName());
|
||||
Weight(_computeWeight());
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Looks up the filter characters within the command name.
|
||||
// Iterating through the filter and the command name it tries to associate the next filter character
|
||||
// with the first appearance of this character in the command name suffix.
|
||||
//
|
||||
// E.g., for filter="c l t s" and name="close all tabs after this", the match will be "CLose TabS after this".
|
||||
//
|
||||
// The command name is then split into segments (groupings of matched and non matched characters).
|
||||
//
|
||||
// E.g., the segments were the example above will be "CL", "ose ", "T", "ab", "S", "after this".
|
||||
//
|
||||
// The segments matching the filter characters are marked as highlighted.
|
||||
//
|
||||
// E.g., ("CL", true) ("ose ", false), ("T", true), ("ab", false), ("S", true), ("after this", false)
|
||||
//
|
||||
// TODO: we probably need to merge this logic with _getWeight computation?
|
||||
//
|
||||
// Return Value:
|
||||
// - The HighlightedText object initialized with the segments computed according to the algorithm above.
|
||||
winrt::TerminalApp::HighlightedText FilteredCommand::_computeHighlightedName()
|
||||
{
|
||||
const auto segments = winrt::single_threaded_observable_vector<winrt::TerminalApp::HighlightedTextSegment>();
|
||||
auto commandName = _Command.Name();
|
||||
bool isProcessingMatchedSegment = false;
|
||||
uint32_t nextOffsetToReport = 0;
|
||||
uint32_t currentOffset = 0;
|
||||
|
||||
for (const auto searchChar : _Filter)
|
||||
{
|
||||
const auto lowerCaseSearchChar = std::towlower(searchChar);
|
||||
while (true)
|
||||
{
|
||||
if (currentOffset == commandName.size())
|
||||
{
|
||||
// There are still unmatched filter characters but we finished scanning the name.
|
||||
// In this case we return the entire command name as unmatched
|
||||
auto entireNameSegment{ winrt::make<HighlightedTextSegment>(commandName, false) };
|
||||
segments.Clear();
|
||||
segments.Append(entireNameSegment);
|
||||
return winrt::make<HighlightedText>(segments);
|
||||
}
|
||||
|
||||
auto isCurrentCharMatched = std::towlower(commandName[currentOffset]) == lowerCaseSearchChar;
|
||||
if (isProcessingMatchedSegment != isCurrentCharMatched)
|
||||
{
|
||||
// We reached the end of the region (matched character came after a series of unmatched or vice versa).
|
||||
// Conclude the segment and add it to the list.
|
||||
// Skip segment if it is empty (might happen when the first character of the name is matched)
|
||||
auto sizeToReport = currentOffset - nextOffsetToReport;
|
||||
if (sizeToReport > 0)
|
||||
{
|
||||
winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport };
|
||||
auto highlightedSegment{ winrt::make<HighlightedTextSegment>(segment, isProcessingMatchedSegment) };
|
||||
segments.Append(highlightedSegment);
|
||||
nextOffsetToReport = currentOffset;
|
||||
}
|
||||
isProcessingMatchedSegment = isCurrentCharMatched;
|
||||
}
|
||||
|
||||
currentOffset++;
|
||||
|
||||
if (isCurrentCharMatched)
|
||||
{
|
||||
// We have matched this filter character, let's move to matching the next filter char
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Either the filter or the command name were fully processed.
|
||||
// If we were in the middle of the matched segment - add it.
|
||||
if (isProcessingMatchedSegment)
|
||||
{
|
||||
auto sizeToReport = currentOffset - nextOffsetToReport;
|
||||
if (sizeToReport > 0)
|
||||
{
|
||||
winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport };
|
||||
auto highlightedSegment{ winrt::make<HighlightedTextSegment>(segment, true) };
|
||||
segments.Append(highlightedSegment);
|
||||
nextOffsetToReport = currentOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// Now create a segment for all remaining characters.
|
||||
// We will have remaining characters as long as the filter is shorter than the command name.
|
||||
auto sizeToReport = commandName.size() - nextOffsetToReport;
|
||||
if (sizeToReport > 0)
|
||||
{
|
||||
winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport };
|
||||
auto highlightedSegment{ winrt::make<HighlightedTextSegment>(segment, false) };
|
||||
segments.Append(highlightedSegment);
|
||||
}
|
||||
|
||||
return winrt::make<HighlightedText>(segments);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Calculates a "weighting" by which should be used to order a command
|
||||
// name relative to other names, given a specific search string.
|
||||
// Currently, this is based off of two factors:
|
||||
// * The weight is incremented once for each matched character of the
|
||||
// search text.
|
||||
// * If a matching character from the search text was found at the start
|
||||
// of a word in the name, then we increment the weight again.
|
||||
// * For example, for a search string "sp", we want "Split Pane" to
|
||||
// appear in the list before "Close Pane"
|
||||
// * Consecutive matches will be weighted higher than matches with
|
||||
// characters in between the search characters.
|
||||
// - This will return 0 if the command should not be shown. If all the
|
||||
// characters of search text appear in order in `name`, then this function
|
||||
// will return a positive number. There can be any number of characters
|
||||
// separating consecutive characters in searchText.
|
||||
// * For example:
|
||||
// "name": "New Tab"
|
||||
// "name": "Close Tab"
|
||||
// "name": "Close Pane"
|
||||
// "name": "[-] Split Horizontal"
|
||||
// "name": "[ | ] Split Vertical"
|
||||
// "name": "Next Tab"
|
||||
// "name": "Prev Tab"
|
||||
// "name": "Open Settings"
|
||||
// "name": "Open Media Controls"
|
||||
// * "open" should return both "**Open** Settings" and "**Open** Media Controls".
|
||||
// * "Tab" would return "New **Tab**", "Close **Tab**", "Next **Tab**" and "Prev
|
||||
// **Tab**".
|
||||
// * "P" would return "Close **P**ane", "[-] S**p**lit Horizontal", "[ | ]
|
||||
// S**p**lit Vertical", "**P**rev Tab", "O**p**en Settings" and "O**p**en Media
|
||||
// Controls".
|
||||
// * "sv" would return "[ | ] Split Vertical" (by matching the **S** in
|
||||
// "Split", then the **V** in "Vertical").
|
||||
// Arguments:
|
||||
// - searchText: the string of text to search for in `name`
|
||||
// - name: the name to check
|
||||
// Return Value:
|
||||
// - the relative weight of this match
|
||||
int FilteredCommand::_computeWeight()
|
||||
{
|
||||
int result = 0;
|
||||
bool isNextSegmentWordBeginning = true;
|
||||
|
||||
for (const auto& segment : _HighlightedName.Segments())
|
||||
{
|
||||
const auto& segmentText = segment.TextSegment();
|
||||
const auto segmentSize = segmentText.size();
|
||||
|
||||
if (segment.IsHighlighted())
|
||||
{
|
||||
// Give extra point for each consecutive match
|
||||
result += (segmentSize <= 1) ? segmentSize : 1 + 2 * (segmentSize - 1);
|
||||
|
||||
// Give extra point if this segment is at the beginning of a word
|
||||
if (isNextSegmentWordBeginning)
|
||||
{
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
isNextSegmentWordBeginning = segmentSize > 0 && segmentText[segmentSize - 1] == L' ';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Implementation of Compare for FilteredCommand interface.
|
||||
// Compares firs command with the second command, first by weight, then by name.
|
||||
// In the case of a tie prefers the first command
|
||||
// Arguments:
|
||||
// - other: another instance of Filtered Command interface
|
||||
// Return Value:
|
||||
// - Returns true if the first is "bigger" (aka should appear first)
|
||||
int FilteredCommand::Compare(winrt::TerminalApp::FilteredCommand const& first, winrt::TerminalApp::FilteredCommand const& second)
|
||||
{
|
||||
auto firstWeight{ first.Weight() };
|
||||
auto secondWeight{ second.Weight() };
|
||||
|
||||
if (firstWeight == secondWeight)
|
||||
{
|
||||
std::wstring_view firstName{ first.Command().Name() };
|
||||
std::wstring_view secondName{ second.Command().Name() };
|
||||
return firstName.compare(secondName) < 0;
|
||||
}
|
||||
|
||||
return firstWeight > secondWeight;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "HighlightedTextControl.h"
|
||||
#include "FilteredCommand.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
class FilteredCommandTests;
|
||||
};
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct FilteredCommand : FilteredCommandT<FilteredCommand>
|
||||
{
|
||||
FilteredCommand() = default;
|
||||
FilteredCommand(Microsoft::Terminal::Settings::Model::Command const& command);
|
||||
|
||||
void UpdateFilter(winrt::hstring const& filter);
|
||||
|
||||
static int Compare(winrt::TerminalApp::FilteredCommand const& first, winrt::TerminalApp::FilteredCommand const& second);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Filter, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(int, Weight, _PropertyChangedHandlers);
|
||||
|
||||
private:
|
||||
winrt::TerminalApp::HighlightedText _computeHighlightedName();
|
||||
int _computeWeight();
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _commandChangedRevoker;
|
||||
|
||||
friend class TerminalAppLocalTests::FilteredCommandTests;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(FilteredCommand);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "IDirectKeyListener.idl";
|
||||
import "ShortcutActionDispatch.idl";
|
||||
import "HighlightedTextControl.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass FilteredCommand : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
FilteredCommand();
|
||||
FilteredCommand(Microsoft.Terminal.Settings.Model.Command command);
|
||||
|
||||
Microsoft.Terminal.Settings.Model.Command Command { get; };
|
||||
String Filter;
|
||||
HighlightedText HighlightedName { get; };
|
||||
Int32 Weight;
|
||||
|
||||
void UpdateFilter(String filter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 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(winrt::hstring const& textSegment, bool isHighlighted) :
|
||||
_TextSegment(textSegment),
|
||||
_IsHighlighted(isHighlighted)
|
||||
{
|
||||
}
|
||||
|
||||
HighlightedText::HighlightedText(Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::HighlightedTextSegment> const& segments) :
|
||||
_Segments(segments)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "winrt/Microsoft.UI.Xaml.Controls.h"
|
||||
|
||||
#include "HighlightedTextSegment.g.h"
|
||||
#include "HighlightedText.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct HighlightedTextSegment : HighlightedTextSegmentT<HighlightedTextSegment>
|
||||
{
|
||||
HighlightedTextSegment() = default;
|
||||
HighlightedTextSegment(winrt::hstring const& text, bool isHighlighted);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, TextSegment, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(bool, IsHighlighted, _PropertyChangedHandlers);
|
||||
};
|
||||
|
||||
struct HighlightedText : HighlightedTextT<HighlightedText>
|
||||
{
|
||||
HighlightedText() = default;
|
||||
HighlightedText(Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::HighlightedTextSegment> const& segments);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::HighlightedTextSegment>, Segments, _PropertyChangedHandlers);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(HighlightedTextSegment);
|
||||
BASIC_FACTORY(HighlightedText);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "winrt/Windows.UI.Xaml.Interop.h"
|
||||
#include "HighlightedTextControl.h"
|
||||
|
||||
#include "HighlightedTextControl.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
|
||||
{
|
||||
// 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));
|
||||
|
||||
HighlightedTextControl::HighlightedTextControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the Identifier of the "Text" dependency property
|
||||
DependencyProperty HighlightedTextControl::TextProperty()
|
||||
{
|
||||
return _textProperty;
|
||||
}
|
||||
|
||||
// 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(winrt::TerminalApp::HighlightedText const& 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(DependencyObject const& o, DependencyPropertyChangedEventArgs const& e)
|
||||
{
|
||||
const auto control = o.try_as<winrt::TerminalApp::HighlightedTextControl>();
|
||||
const auto highlightedText = e.NewValue().try_as<winrt::TerminalApp::HighlightedText>();
|
||||
|
||||
if (control && highlightedText)
|
||||
{
|
||||
// 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();
|
||||
|
||||
for (const auto& match : highlightedText.Segments())
|
||||
{
|
||||
const auto matchText = match.TextSegment();
|
||||
const auto fontWeight = match.IsHighlighted() ? FontWeights::Bold() : FontWeights::Normal();
|
||||
|
||||
Documents::Run run;
|
||||
run.Text(matchText);
|
||||
run.FontWeight(fontWeight);
|
||||
inlinesCollection.Append(run);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "winrt/Microsoft.UI.Xaml.Controls.h"
|
||||
|
||||
#include "HighlightedTextControl.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct HighlightedTextControl : HighlightedTextControlT<HighlightedTextControl>
|
||||
{
|
||||
HighlightedTextControl();
|
||||
|
||||
static Windows::UI::Xaml::DependencyProperty TextProperty();
|
||||
|
||||
winrt::TerminalApp::HighlightedText Text();
|
||||
void Text(winrt::TerminalApp::HighlightedText const& value);
|
||||
|
||||
Windows::UI::Xaml::Controls::TextBlock TextView();
|
||||
|
||||
private:
|
||||
static Windows::UI::Xaml::DependencyProperty _textProperty;
|
||||
static void _onTextChanged(Windows::UI::Xaml::DependencyObject const& o, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(HighlightedTextControl);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "HighlightedText.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass HighlightedTextControl : Windows.UI.Xaml.Controls.Control
|
||||
{
|
||||
HighlightedTextControl();
|
||||
Windows.UI.Xaml.DependencyProperty TextProperty { get; };
|
||||
HighlightedText Text;
|
||||
Windows.UI.Xaml.Controls.TextBlock TextView { get; };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<UserControl
|
||||
x:Class="TerminalApp.HighlightedTextControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="Transparent">
|
||||
|
||||
<TextBlock
|
||||
x:Name="_textView"/>
|
||||
</UserControl>
|
|
@ -21,8 +21,8 @@
|
|||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
|
||||
<ClCompile>
|
||||
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
|
||||
on being compiled with RTTI (/GR). -->
|
||||
|
@ -54,6 +54,9 @@
|
|||
<Page Include="TabRowControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="HighlightedTextControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="ColorPickupFlyout.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
|
@ -86,12 +89,17 @@
|
|||
<ClInclude Include="TabRowControl.h">
|
||||
<DependentUpon>TabRowControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HighlightedTextControl.h">
|
||||
<DependentUpon>HighlightedTextControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HighlightedText.h" />
|
||||
<ClInclude Include="ColorPickupFlyout.h">
|
||||
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CommandPalette.h">
|
||||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FilteredCommand.h" />
|
||||
<ClInclude Include="EmptyStringVisibilityConverter.h">
|
||||
<DependentUpon>EmptyStringVisibilityConverter.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
|
@ -146,12 +154,17 @@
|
|||
<ClCompile Include="TabRowControl.cpp">
|
||||
<DependentUpon>TabRowControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HighlightedTextControl.cpp">
|
||||
<DependentUpon>HighlightedTextControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HighlightedText.cpp" />
|
||||
<ClCompile Include="ColorPickupFlyout.cpp">
|
||||
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CommandPalette.cpp">
|
||||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FilteredCommand.cpp" />
|
||||
<ClCompile Include="EmptyStringVisibilityConverter.cpp">
|
||||
<DependentUpon>EmptyStringVisibilityConverter.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
|
@ -217,6 +230,11 @@
|
|||
<DependentUpon>TabRowControl.xaml</DependentUpon>
|
||||
<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>
|
||||
|
@ -225,6 +243,7 @@
|
|||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="FilteredCommand.idl" />
|
||||
<Midl Include="EmptyStringVisibilityConverter.idl" />
|
||||
<Midl Include="HasNestedCommandsVisibilityConverter.idl" />
|
||||
<Midl Include="IconPathConverter.idl" />
|
||||
|
|
|
@ -31,6 +31,12 @@
|
|||
<ClCompile Include="Tab.cpp">
|
||||
<Filter>tab</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FilteredCommand.cpp">
|
||||
<Filter>commandPalette</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HighlightedText.cpp">
|
||||
<Filter>highlightedText</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Utils.h" />
|
||||
|
@ -52,10 +58,16 @@
|
|||
<ClInclude Include="TerminalSettings.h">
|
||||
<Filter>settings</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Jumplist.h" />
|
||||
<ClInclude Include="Jumplist.h" />
|
||||
<ClInclude Include="Tab.h">
|
||||
<Filter>tab</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FilteredCommand.h">
|
||||
<Filter>commandPalette</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HighlightedText.h">
|
||||
<Filter>highlightedText</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="AppLogic.idl">
|
||||
|
@ -77,6 +89,12 @@
|
|||
<Midl Include="TerminalTab.idl">
|
||||
<Filter>tab</Filter>
|
||||
</Midl>
|
||||
<Midl Include="FilteredCommand.idl">
|
||||
<Filter>commandPalette</Filter>
|
||||
</Midl>
|
||||
<Midl Include="HighlightedText.idl">
|
||||
<Filter>highlightedText</Filter>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
@ -100,6 +118,9 @@
|
|||
<Page Include="CommandPalette.xaml">
|
||||
<Filter>commandPalette</Filter>
|
||||
</Page>
|
||||
<Page Include="HighlightedTextControl.xaml">
|
||||
<Filter>highlightedText</Filter>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="app">
|
||||
|
@ -120,10 +141,13 @@
|
|||
<Filter Include="commandPalette">
|
||||
<UniqueIdentifier>{2ad498e1-d8ea-4381-9464-a74c141bd7dd}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="highlightedText">
|
||||
<UniqueIdentifier>{e490f626-547d-4b5b-b22d-c6d33c9e3210}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Filter>app</Filter>
|
||||
</ApplicationDefinition>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
Loading…
Reference in New Issue