mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-18 18:56:25 +00:00
Compare commits
71 Commits
dev/duhowe
...
dev/cazamo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5a816da87 | ||
|
|
1eb311e440 | ||
|
|
3c8e2624cf | ||
|
|
49a2074e23 | ||
|
|
42ef28a382 | ||
|
|
c584a517b6 | ||
|
|
dca187667e | ||
|
|
e32956dee0 | ||
|
|
07dc3cd760 | ||
|
|
7cd9f7da44 | ||
|
|
eecb32d297 | ||
|
|
23a04324f8 | ||
|
|
72e9b64e2d | ||
|
|
8d0f947e94 | ||
|
|
63ec868455 | ||
|
|
acad3880d0 | ||
|
|
ce8b31b825 | ||
|
|
ed88c698b1 | ||
|
|
2f6108da6a | ||
|
|
d9738363b7 | ||
|
|
88e99598a1 | ||
|
|
d42af1f847 | ||
|
|
bddd619536 | ||
|
|
aa52042cb3 | ||
|
|
fd8072a0bd | ||
|
|
a282164c35 | ||
|
|
8db1805e17 | ||
|
|
27e1aee881 | ||
|
|
5ae13b3e42 | ||
|
|
831313c959 | ||
|
|
2d077ca892 | ||
|
|
3353323880 | ||
|
|
fbf76f2d26 | ||
|
|
d1c4c9428b | ||
|
|
83664eb4cb | ||
|
|
0bd9a028d7 | ||
|
|
244550044a | ||
|
|
34c6115a59 | ||
|
|
fc0eb78f5f | ||
|
|
debaceee2f | ||
|
|
057128e661 | ||
|
|
36d28e2ba9 | ||
|
|
0fe444e07a | ||
|
|
cc1d632598 | ||
|
|
a7d6e27a48 | ||
|
|
2e7e37ae09 | ||
|
|
92bccdfa4a | ||
|
|
f25e6fe2d1 | ||
|
|
e9f83fc4eb | ||
|
|
c371c484a3 | ||
|
|
1072d69fb7 | ||
|
|
b472d9fed9 | ||
|
|
7fcdeaac40 | ||
|
|
2766f21d31 | ||
|
|
0af4eb0e21 | ||
|
|
b0a7ef1d39 | ||
|
|
f08c287b64 | ||
|
|
7cdbb7c795 | ||
|
|
5afc9bc86a | ||
|
|
6b83fa705a | ||
|
|
e6c43c0d4c | ||
|
|
75d02c29bd | ||
|
|
f11515e692 | ||
|
|
7f0c9e5374 | ||
|
|
019bb766db | ||
|
|
f4a355d9b1 | ||
|
|
1bbdd4e002 | ||
|
|
7a4919bb6d | ||
|
|
0e7d7ec0d6 | ||
|
|
04691f4b3a | ||
|
|
c9c997e117 |
1
.github/actions/spelling/excludes.txt
vendored
1
.github/actions/spelling/excludes.txt
vendored
@@ -133,3 +133,4 @@ Resources/(?!en)
|
||||
^\Qsrc/terminal/parser/ft_fuzzwrapper/run.bat\E$
|
||||
^\Qsrc/tools/lnkd/lnkd.bat\E$
|
||||
^\Qsrc/tools/pixels/pixels.bat\E$
|
||||
^\Qsrc/cascadia/ut_app/FzfTests.cpp\E$
|
||||
|
||||
4
.github/actions/spelling/expect/expect.txt
vendored
4
.github/actions/spelling/expect/expect.txt
vendored
@@ -651,6 +651,7 @@ FONTSTRING
|
||||
FONTTYPE
|
||||
FONTWIDTH
|
||||
FONTWINDOW
|
||||
foob
|
||||
FORCEOFFFEEDBACK
|
||||
FORCEONFEEDBACK
|
||||
FRAMECHANGED
|
||||
@@ -668,9 +669,11 @@ fuzzer
|
||||
fuzzmain
|
||||
fuzzmap
|
||||
fuzzwrapper
|
||||
fuzzyfinder
|
||||
fwdecl
|
||||
fwe
|
||||
fwlink
|
||||
fzf
|
||||
gci
|
||||
gcx
|
||||
gdi
|
||||
@@ -1248,6 +1251,7 @@ onecoreuuid
|
||||
ONECOREWINDOWS
|
||||
onehalf
|
||||
oneseq
|
||||
oob
|
||||
openbash
|
||||
opencode
|
||||
opencon
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 943 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/cascadia/CascadiaPackage/ProfileGeneratorIcons/SSH.png
Normal file
BIN
src/cascadia/CascadiaPackage/ProfileGeneratorIcons/SSH.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 787 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/cascadia/CascadiaPackage/ProfileGeneratorIcons/WSL.png
Normal file
BIN
src/cascadia/CascadiaPackage/ProfileGeneratorIcons/WSL.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -21,6 +21,11 @@
|
||||
<DeploymentContent>true</DeploymentContent>
|
||||
<Link>ProfileIcons\%(RecursiveDir)%(FileName)%(Extension)</Link>
|
||||
</Content>
|
||||
<!-- Profile Generator Icons -->
|
||||
<Content Include="$(OpenConsoleDir)src\cascadia\CascadiaPackage\ProfileGeneratorIcons\**\*">
|
||||
<DeploymentContent>true</DeploymentContent>
|
||||
<Link>ProfileGeneratorIcons\%(RecursiveDir)%(FileName)%(Extension)</Link>
|
||||
</Content>
|
||||
<!-- Default Settings -->
|
||||
<Content Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsModel\defaults.json">
|
||||
<DeploymentContent>true</DeploymentContent>
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
filteredCommand->_update();
|
||||
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());
|
||||
@@ -43,8 +44,9 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L""));
|
||||
filteredCommand->_update();
|
||||
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());
|
||||
@@ -52,8 +54,9 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter equal to the string");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"AAAAAABBBBBBCCC";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"AAAAAABBBBBBCCC"));
|
||||
filteredCommand->_update();
|
||||
auto segments = filteredCommand->HighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
@@ -61,8 +64,9 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter with first character matching");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"A";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"A"));
|
||||
filteredCommand->_update();
|
||||
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());
|
||||
@@ -72,8 +76,9 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter with other case");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"a";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"a"));
|
||||
filteredCommand->_update();
|
||||
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());
|
||||
@@ -83,8 +88,9 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter matching several characters");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"ab";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"ab"));
|
||||
filteredCommand->_update();
|
||||
auto segments = filteredCommand->HighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 4u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
@@ -98,8 +104,9 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with non matching filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"abcd";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"abcd"));
|
||||
filteredCommand->_update();
|
||||
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());
|
||||
@@ -116,48 +123,48 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
filteredCommand->_update();
|
||||
auto weight = filteredCommand->Weight();
|
||||
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>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L""));
|
||||
filteredCommand->_update();
|
||||
auto weight = filteredCommand->Weight();
|
||||
VERIFY_ARE_EQUAL(weight, 0);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter equal to the string");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"AAAAAABBBBBBCCC";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"AAAAAABBBBBBCCC"));
|
||||
filteredCommand->_update();
|
||||
auto weight = filteredCommand->Weight();
|
||||
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>(paletteItem);
|
||||
filteredCommand->_Filter = L"A";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"A"));
|
||||
filteredCommand->_update();
|
||||
auto weight = filteredCommand->Weight();
|
||||
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>(paletteItem);
|
||||
filteredCommand->_Filter = L"a";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"a"));
|
||||
filteredCommand->_update();
|
||||
auto weight = filteredCommand->Weight();
|
||||
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>(paletteItem);
|
||||
filteredCommand->_Filter = L"ab";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"ab"));
|
||||
filteredCommand->_update();
|
||||
auto weight = filteredCommand->Weight();
|
||||
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"
|
||||
}
|
||||
});
|
||||
@@ -181,14 +188,12 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
filteredCommand->_Weight = filteredCommand->_computeWeight();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L""));
|
||||
filteredCommand->_update();
|
||||
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
filteredCommand2->_Filter = L"";
|
||||
filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName();
|
||||
filteredCommand2->_Weight = filteredCommand2->_computeWeight();
|
||||
filteredCommand2->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L""));
|
||||
filteredCommand->_update();
|
||||
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
@@ -196,14 +201,12 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with different weights");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"B";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
filteredCommand->_Weight = filteredCommand->_computeWeight();
|
||||
filteredCommand->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"B"));
|
||||
filteredCommand->_update();
|
||||
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
filteredCommand2->_Filter = L"B";
|
||||
filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName();
|
||||
filteredCommand2->_Weight = filteredCommand2->_computeWeight();
|
||||
filteredCommand2->_pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(L"B"));
|
||||
filteredCommand->_update();
|
||||
|
||||
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));
|
||||
|
||||
@@ -1174,12 +1174,15 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
else if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode || _currentMode == CommandPaletteMode::CommandlineMode)
|
||||
{
|
||||
auto pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(searchText));
|
||||
|
||||
for (const auto& action : commandsToFilter)
|
||||
{
|
||||
// Update filter for all commands
|
||||
// This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting).
|
||||
// Pay attention that it already updates the highlighting in the UI
|
||||
action.UpdateFilter(searchText);
|
||||
auto impl = winrt::get_self<implementation::FilteredCommand>(action);
|
||||
impl->UpdateFilter(pattern);
|
||||
|
||||
// if there is active search we skip commands with 0 weight
|
||||
if (searchText.empty() || action.Weight() > 0)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "CommandPalette.h"
|
||||
#include "HighlightedText.h"
|
||||
#include <LibraryResources.h>
|
||||
#include "fzf/fzf.h"
|
||||
|
||||
#include "FilteredCommand.g.cpp"
|
||||
|
||||
@@ -35,197 +36,103 @@ namespace winrt::TerminalApp::implementation
|
||||
void FilteredCommand::_constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item)
|
||||
{
|
||||
_Item = item;
|
||||
_Filter = L"";
|
||||
_Weight = 0;
|
||||
_HighlightedName = _computeHighlightedName();
|
||||
|
||||
_update();
|
||||
|
||||
// Recompute the highlighted name if the item name changes
|
||||
_itemChangedRevoker = _Item.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& /*sender*/, auto& e) {
|
||||
auto filteredCommand{ weakThis.get() };
|
||||
if (filteredCommand && e.PropertyName() == L"Name")
|
||||
{
|
||||
filteredCommand->HighlightedName(filteredCommand->_computeHighlightedName());
|
||||
filteredCommand->Weight(filteredCommand->_computeWeight());
|
||||
filteredCommand->_update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FilteredCommand::UpdateFilter(const winrt::hstring& filter)
|
||||
void FilteredCommand::UpdateFilter(std::shared_ptr<fzf::matcher::Pattern> pattern)
|
||||
{
|
||||
// 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)
|
||||
if (pattern != _pattern)
|
||||
{
|
||||
Filter(filter);
|
||||
HighlightedName(_computeHighlightedName());
|
||||
Weight(_computeWeight());
|
||||
_pattern = pattern;
|
||||
_update();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Looks up the filter characters within the item name.
|
||||
// Iterating through the filter and the item name it tries to associate the next filter character
|
||||
// with the first appearance of this character in the item 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 item 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()
|
||||
void FilteredCommand::_update()
|
||||
{
|
||||
const auto segments = winrt::single_threaded_observable_vector<winrt::TerminalApp::HighlightedTextSegment>();
|
||||
auto segments = winrt::single_threaded_observable_vector<winrt::TerminalApp::HighlightedTextSegment>();
|
||||
auto commandName = _Item.Name();
|
||||
auto isProcessingMatchedSegment = false;
|
||||
uint32_t nextOffsetToReport = 0;
|
||||
uint32_t currentOffset = 0;
|
||||
|
||||
for (const auto searchChar : _Filter)
|
||||
auto weight = 0;
|
||||
if (!_pattern || !_pattern->terms.empty())
|
||||
{
|
||||
const WCHAR searchCharAsString[] = { searchChar, L'\0' };
|
||||
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 item name as unmatched
|
||||
auto entireNameSegment{ winrt::make<HighlightedTextSegment>(commandName, false) };
|
||||
segments.Clear();
|
||||
segments.Append(entireNameSegment);
|
||||
return winrt::make<HighlightedText>(segments);
|
||||
}
|
||||
segments.Append(winrt::TerminalApp::HighlightedTextSegment(commandName, false));
|
||||
}
|
||||
else if (auto match = fzf::matcher::Match(commandName, *_pattern.get()); !match)
|
||||
{
|
||||
segments.Append(winrt::TerminalApp::HighlightedTextSegment(commandName, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& matchResult = *match;
|
||||
weight = matchResult.Score;
|
||||
auto positions = matchResult.Pos;
|
||||
// positions are returned is sorted pairs by search term. E.g. sp anta {5,4,11,10,9,8}
|
||||
// sorting these in ascending order so it is easier to build the text segments
|
||||
std::ranges::sort(positions);
|
||||
// a position can be matched in multiple terms, removed duplicates to simplify segments
|
||||
positions.erase(std::unique(positions.begin(), positions.end()), positions.end());
|
||||
|
||||
// GH#9941: search should be locale-aware as well
|
||||
// We use the same comparison method as upon sorting to guarantee consistent behavior
|
||||
const WCHAR currentCharAsString[] = { commandName[currentOffset], L'\0' };
|
||||
auto isCurrentCharMatched = lstrcmpi(searchCharAsString, currentCharAsString) == 0;
|
||||
if (isProcessingMatchedSegment != isCurrentCharMatched)
|
||||
std::vector<std::pair<size_t, size_t>> runs;
|
||||
if (!positions.empty())
|
||||
{
|
||||
size_t runStart = positions[0];
|
||||
size_t runEnd = runStart;
|
||||
for (size_t i = 1; i < positions.size(); ++i)
|
||||
{
|
||||
// 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)
|
||||
if (positions[i] == static_cast<int32_t>(runEnd + 1))
|
||||
{
|
||||
winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport };
|
||||
auto highlightedSegment{ winrt::make<HighlightedTextSegment>(segment, isProcessingMatchedSegment) };
|
||||
segments.Append(highlightedSegment);
|
||||
nextOffsetToReport = currentOffset;
|
||||
runEnd = positions[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
runs.emplace_back(runStart, runEnd);
|
||||
runStart = positions[i];
|
||||
runEnd = runStart;
|
||||
}
|
||||
isProcessingMatchedSegment = isCurrentCharMatched;
|
||||
}
|
||||
|
||||
currentOffset++;
|
||||
|
||||
if (isCurrentCharMatched)
|
||||
{
|
||||
// We have matched this filter character, let's move to matching the next filter char
|
||||
break;
|
||||
}
|
||||
runs.emplace_back(runStart, runEnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Either the filter or the item 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)
|
||||
size_t lastPos = 0;
|
||||
for (auto [start, end] : runs)
|
||||
{
|
||||
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 item 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 item
|
||||
// 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 item 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()
|
||||
{
|
||||
auto result = 0;
|
||||
auto 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)
|
||||
if (start > lastPos)
|
||||
{
|
||||
result++;
|
||||
hstring nonMatch{ commandName.data() + lastPos,
|
||||
static_cast<unsigned>(start - lastPos) };
|
||||
segments.Append(winrt::TerminalApp::HighlightedTextSegment(nonMatch, false));
|
||||
}
|
||||
|
||||
hstring matchSeg{ commandName.data() + start,
|
||||
static_cast<unsigned>(end - start + 1) };
|
||||
segments.Append(winrt::TerminalApp::HighlightedTextSegment(matchSeg, true));
|
||||
|
||||
lastPos = end + 1;
|
||||
}
|
||||
|
||||
isNextSegmentWordBeginning = segmentSize > 0 && segmentText[segmentSize - 1] == L' ';
|
||||
if (lastPos < commandName.size())
|
||||
{
|
||||
hstring tail{ commandName.data() + lastPos,
|
||||
static_cast<unsigned>(commandName.size() - lastPos) };
|
||||
segments.Append(winrt::TerminalApp::HighlightedTextSegment(tail, false));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
HighlightedName(winrt::make<HighlightedText>(segments));
|
||||
Weight(weight);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "HighlightedTextControl.h"
|
||||
#include "FilteredCommand.g.h"
|
||||
#include "fzf/fzf.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
@@ -19,13 +20,12 @@ namespace winrt::TerminalApp::implementation
|
||||
FilteredCommand() = default;
|
||||
FilteredCommand(const winrt::TerminalApp::PaletteItem& item);
|
||||
|
||||
virtual void UpdateFilter(const winrt::hstring& filter);
|
||||
virtual void UpdateFilter(std::shared_ptr<fzf::matcher::Pattern> pattern);
|
||||
|
||||
static int Compare(const winrt::TerminalApp::FilteredCommand& first, const winrt::TerminalApp::FilteredCommand& second);
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::PaletteItem, Item, PropertyChanged.raise, nullptr);
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Filter, PropertyChanged.raise);
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, PropertyChanged.raise);
|
||||
WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise);
|
||||
|
||||
@@ -33,8 +33,8 @@ namespace winrt::TerminalApp::implementation
|
||||
void _constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item);
|
||||
|
||||
private:
|
||||
winrt::TerminalApp::HighlightedText _computeHighlightedName();
|
||||
int _computeWeight();
|
||||
std::shared_ptr<fzf::matcher::Pattern> _pattern;
|
||||
void _update();
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _itemChangedRevoker;
|
||||
|
||||
friend class TerminalAppLocalTests::FilteredCommandTests;
|
||||
|
||||
@@ -12,10 +12,7 @@ namespace TerminalApp
|
||||
FilteredCommand(PaletteItem item);
|
||||
|
||||
PaletteItem Item { get; };
|
||||
String Filter;
|
||||
HighlightedText HighlightedName { get; };
|
||||
Int32 Weight;
|
||||
|
||||
void UpdateFilter(String filter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void SnippetsPaneContent::_updateFilteredCommands()
|
||||
{
|
||||
const auto& queryString = _filterBox().Text();
|
||||
auto pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(queryString));
|
||||
|
||||
// DON'T replace the itemSource here. If you do, it'll un-expand all the
|
||||
// nested items the user has expanded. Instead, just update the filter.
|
||||
@@ -39,7 +40,7 @@ namespace winrt::TerminalApp::implementation
|
||||
for (const auto& t : _allTasks)
|
||||
{
|
||||
auto impl = winrt::get_self<implementation::FilteredTask>(t);
|
||||
impl->UpdateFilter(queryString);
|
||||
impl->UpdateFilter(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,13 +77,14 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateFilter(const winrt::hstring& filter)
|
||||
void UpdateFilter(std::shared_ptr<fzf::matcher::Pattern> pattern)
|
||||
{
|
||||
_filteredCommand->UpdateFilter(filter);
|
||||
_pattern = pattern;
|
||||
_filteredCommand->UpdateFilter(pattern);
|
||||
for (const auto& c : _children)
|
||||
{
|
||||
auto impl = winrt::get_self<implementation::FilteredTask>(c);
|
||||
impl->UpdateFilter(filter);
|
||||
impl->UpdateFilter(pattern);
|
||||
}
|
||||
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Visibility" });
|
||||
@@ -108,6 +109,7 @@ namespace winrt::TerminalApp::implementation
|
||||
bool HasChildren() { return _children.Size() > 0; }
|
||||
winrt::Microsoft::Terminal::Settings::Model::Command Command() { return _command; }
|
||||
winrt::TerminalApp::FilteredCommand FilteredCommand() { return *_filteredCommand; }
|
||||
std::shared_ptr<fzf::matcher::Pattern> _pattern;
|
||||
|
||||
int32_t Row() { return HasChildren() ? 2 : 1; } // See the BODGY comment in the .XAML for explanation
|
||||
|
||||
@@ -117,7 +119,7 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::Windows::UI::Xaml::Visibility Visibility()
|
||||
{
|
||||
// Is there no filter, or do we match it?
|
||||
if (_filteredCommand->Filter().empty() || _filteredCommand->Weight() > 0)
|
||||
if ((!_pattern || _pattern->terms.empty() || _filteredCommand->Weight() > 0))
|
||||
{
|
||||
return winrt::Windows::UI::Xaml::Visibility::Visible;
|
||||
}
|
||||
|
||||
@@ -936,12 +936,15 @@ namespace winrt::TerminalApp::implementation
|
||||
auto commandsToFilter = _commandsToFilter();
|
||||
|
||||
{
|
||||
auto pattern = std::make_shared<fzf::matcher::Pattern>(fzf::matcher::ParsePattern(searchText));
|
||||
|
||||
for (const auto& action : commandsToFilter)
|
||||
{
|
||||
// Update filter for all commands
|
||||
// This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting).
|
||||
// Pay attention that it already updates the highlighting in the UI
|
||||
action.UpdateFilter(searchText);
|
||||
auto impl = winrt::get_self<implementation::FilteredCommand>(action);
|
||||
impl->UpdateFilter(pattern);
|
||||
|
||||
// if there is active search we skip commands with 0 weight
|
||||
if (searchText.empty() || action.Weight() > 0)
|
||||
|
||||
@@ -77,6 +77,16 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
|
||||
|
||||
if (profile.Source() == L"Windows.Terminal.InstallPowerShell")
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"InstallPowerShellStubInvoked",
|
||||
TraceLoggingDescription("Event emitted when the 'Install Latest PowerShell' stub was invoked"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
}
|
||||
|
||||
// Try to handle auto-elevation
|
||||
if (_maybeElevate(newTerminalArgs, settings, profile))
|
||||
{
|
||||
|
||||
@@ -138,6 +138,8 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="FilteredCommand.h" />
|
||||
<ClInclude Include="Pane.h" />
|
||||
<ClInclude Include="fzf/fzf.h" />
|
||||
<ClInclude Include="fzf/LICENSE" />
|
||||
<ClInclude Include="ColorHelper.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="ShortcutActionDispatch.h">
|
||||
@@ -212,6 +214,7 @@
|
||||
<ClCompile Include="TabBase.cpp">
|
||||
<DependentUpon>TabBase.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="fzf/fzf.cpp" />
|
||||
<ClCompile Include="TabPaletteItem.cpp" />
|
||||
<ClCompile Include="TaskbarState.cpp">
|
||||
<DependentUpon>TaskbarState.idl</DependentUpon>
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
<ClCompile Include="HighlightedText.cpp">
|
||||
<Filter>highlightedText</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="fzf/fzf.cpp">
|
||||
<Filter>fzf</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Toast.cpp" />
|
||||
<ClCompile Include="LanguageProfileNotifier.cpp" />
|
||||
<ClCompile Include="Monarch.cpp" />
|
||||
@@ -77,6 +80,12 @@
|
||||
<ClInclude Include="HighlightedText.h">
|
||||
<Filter>highlightedText</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="fzf/fzf.h">
|
||||
<Filter>fzf</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="fzf/LICENSE">
|
||||
<Filter>fzf</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Toast.h" />
|
||||
<ClInclude Include="LanguageProfileNotifier.h" />
|
||||
<ClInclude Include="WindowsPackageManagerFactory.h" />
|
||||
@@ -176,6 +185,9 @@
|
||||
<Filter Include="highlightedText">
|
||||
<UniqueIdentifier>{e490f626-547d-4b5b-b22d-c6d33c9e3210}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="fzf">
|
||||
<UniqueIdentifier>{e4588ff4-c80a-40f7-be57-3e81f570a93d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
|
||||
22
src/cascadia/TerminalApp/fzf/LICENSE
Normal file
22
src/cascadia/TerminalApp/fzf/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2024 Junegunn Choi
|
||||
Copyright (c) 2021-2025 Simon Hauser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
491
src/cascadia/TerminalApp/fzf/fzf.cpp
Normal file
491
src/cascadia/TerminalApp/fzf/fzf.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
#include "pch.h"
|
||||
#include "fzf.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace fzf
|
||||
{
|
||||
namespace matcher
|
||||
{
|
||||
constexpr int16_t ScoreMatch = 16;
|
||||
constexpr int16_t ScoreGapStart = -3;
|
||||
constexpr int16_t ScoreGapExtension = -1;
|
||||
constexpr int16_t BoundaryBonus = ScoreMatch / 2;
|
||||
constexpr int16_t NonWordBonus = ScoreMatch / 2;
|
||||
constexpr int16_t CamelCaseBonus = BoundaryBonus + ScoreGapExtension;
|
||||
constexpr int16_t BonusConsecutive = -(ScoreGapStart + ScoreGapExtension);
|
||||
constexpr int16_t BonusFirstCharMultiplier = 2;
|
||||
|
||||
enum CharClass : uint8_t
|
||||
{
|
||||
NonWord = 0,
|
||||
CharLower = 1,
|
||||
CharUpper = 2,
|
||||
Digit = 3,
|
||||
};
|
||||
|
||||
std::wstring_view TrimStart(const std::wstring_view str)
|
||||
{
|
||||
const auto off = str.find_first_not_of(L' ');
|
||||
return str.substr(std::min(off, str.size()));
|
||||
}
|
||||
|
||||
std::wstring_view TrimSuffixSpaces(std::wstring_view input)
|
||||
{
|
||||
size_t end = input.size();
|
||||
while (end > 0 && input[end - 1] == L' ')
|
||||
{
|
||||
--end;
|
||||
}
|
||||
return input.substr(0, end);
|
||||
}
|
||||
|
||||
UChar32 FoldCase(UChar32 c) noexcept
|
||||
{
|
||||
return u_foldCase(c, U_FOLD_CASE_DEFAULT);
|
||||
}
|
||||
|
||||
int32_t IndexOfChar(const std::vector<UChar32>& input, const UChar32 searchChar, int32_t startIndex)
|
||||
{
|
||||
for (int32_t i = startIndex; i < static_cast<int32_t>(input.size()); ++i)
|
||||
{
|
||||
if (input[i] == searchChar)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t FuzzyIndexOf(const std::vector<UChar32>& input, const std::vector<UChar32>& pattern)
|
||||
{
|
||||
int32_t idx = 0;
|
||||
int32_t firstIdx = 0;
|
||||
for (int32_t pi = 0; pi < static_cast<int32_t>(pattern.size()); ++pi)
|
||||
{
|
||||
idx = IndexOfChar(input, pattern[pi], idx);
|
||||
if (idx < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pi == 0 && idx > 0)
|
||||
{
|
||||
firstIdx = idx - 1;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
return firstIdx;
|
||||
}
|
||||
|
||||
int16_t CalculateBonus(CharClass prevClass, CharClass currentClass)
|
||||
{
|
||||
if (prevClass == NonWord && currentClass != NonWord)
|
||||
{
|
||||
return BoundaryBonus;
|
||||
}
|
||||
if ((prevClass == CharLower && currentClass == CharUpper) ||
|
||||
(prevClass != Digit && currentClass == Digit))
|
||||
{
|
||||
return CamelCaseBonus;
|
||||
}
|
||||
if (currentClass == NonWord)
|
||||
{
|
||||
return NonWordBonus;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr auto s_charClassLut = []() {
|
||||
std::array<CharClass, U_CHAR_CATEGORY_COUNT> lut{};
|
||||
lut.fill(CharClass::NonWord);
|
||||
lut[U_UPPERCASE_LETTER] = CharUpper;
|
||||
lut[U_LOWERCASE_LETTER] = CharLower;
|
||||
lut[U_MODIFIER_LETTER] = CharLower;
|
||||
lut[U_OTHER_LETTER] = CharLower;
|
||||
lut[U_DECIMAL_DIGIT_NUMBER] = Digit;
|
||||
return lut;
|
||||
}();
|
||||
|
||||
CharClass ClassOf(UChar32 ch)
|
||||
{
|
||||
return s_charClassLut[u_charType(ch)];
|
||||
}
|
||||
|
||||
FzfResult FzfFuzzyMatchV2(const std::vector<UChar32>& text, const std::vector<UChar32>& pattern, std::vector<int32_t>* pos)
|
||||
{
|
||||
int32_t patternSize = static_cast<int32_t>(pattern.size());
|
||||
int32_t textSize = static_cast<int32_t>(text.size());
|
||||
|
||||
if (patternSize == 0)
|
||||
{
|
||||
return { 0, 0, 0 };
|
||||
}
|
||||
|
||||
std::vector<UChar32> foldedText;
|
||||
foldedText.reserve(text.size());
|
||||
for (auto cp : text)
|
||||
{
|
||||
auto foldedCp = u_foldCase(cp, U_FOLD_CASE_DEFAULT);
|
||||
foldedText.push_back(foldedCp);
|
||||
}
|
||||
|
||||
int32_t firstIndexOf = FuzzyIndexOf(foldedText, pattern);
|
||||
if (firstIndexOf < 0)
|
||||
{
|
||||
return { -1, -1, 0 };
|
||||
}
|
||||
|
||||
auto initialScores = std::vector<int16_t>(textSize);
|
||||
auto consecutiveScores = std::vector<int16_t>(textSize);
|
||||
auto firstOccurrenceOfEachChar = std::vector<int32_t>(patternSize);
|
||||
auto bonusesSpan = std::vector<int16_t>(textSize);
|
||||
|
||||
int16_t maxScore = 0;
|
||||
int32_t maxScorePos = 0;
|
||||
int32_t patternIndex = 0;
|
||||
int32_t lastIndex = 0;
|
||||
UChar32 firstPatternChar = pattern[0];
|
||||
UChar32 currentPatternChar = pattern[0];
|
||||
int16_t previousInitialScore = 0;
|
||||
CharClass previousClass = NonWord;
|
||||
bool inGap = false;
|
||||
|
||||
std::span<const UChar32> lowerText(foldedText);
|
||||
auto lowerTextSlice = lowerText.subspan(firstIndexOf);
|
||||
auto initialScoresSlice = std::span(initialScores).subspan(firstIndexOf);
|
||||
auto consecutiveScoresSlice = std::span(consecutiveScores).subspan(firstIndexOf);
|
||||
auto bonusesSlice = std::span(bonusesSpan).subspan(firstIndexOf, textSize - firstIndexOf);
|
||||
|
||||
for (int32_t i = 0; i < static_cast<int32_t>(lowerTextSlice.size()); i++)
|
||||
{
|
||||
UChar32 currentChar = lowerTextSlice[i];
|
||||
CharClass currentClass = ClassOf(currentChar);
|
||||
int16_t bonus = CalculateBonus(previousClass, currentClass);
|
||||
bonusesSlice[i] = bonus;
|
||||
previousClass = currentClass;
|
||||
|
||||
//currentPatternChar was already folded in ParsePattern
|
||||
if (currentChar == currentPatternChar)
|
||||
{
|
||||
if (patternIndex < static_cast<int32_t>(pattern.size()))
|
||||
{
|
||||
firstOccurrenceOfEachChar[patternIndex] = firstIndexOf + static_cast<int32_t>(i);
|
||||
patternIndex++;
|
||||
if (patternIndex < patternSize)
|
||||
{
|
||||
currentPatternChar = pattern[patternIndex];
|
||||
}
|
||||
}
|
||||
lastIndex = firstIndexOf + static_cast<int32_t>(i);
|
||||
}
|
||||
if (currentChar == firstPatternChar)
|
||||
{
|
||||
int16_t score = ScoreMatch + bonus * BonusFirstCharMultiplier;
|
||||
initialScoresSlice[i] = score;
|
||||
consecutiveScoresSlice[i] = 1;
|
||||
if (patternSize == 1 && (score > maxScore))
|
||||
{
|
||||
maxScore = score;
|
||||
maxScorePos = firstIndexOf + static_cast<int32_t>(i);
|
||||
if (bonus == BoundaryBonus)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
inGap = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
initialScoresSlice[i] = inGap ? std::max<int16_t>(previousInitialScore + ScoreGapExtension, 0) : std::max<int16_t>(previousInitialScore + ScoreGapStart, 0);
|
||||
consecutiveScoresSlice[i] = 0;
|
||||
inGap = true;
|
||||
}
|
||||
previousInitialScore = initialScoresSlice[i];
|
||||
}
|
||||
|
||||
if (patternIndex != static_cast<int32_t>(pattern.size()))
|
||||
{
|
||||
return { -1, -1, 0 };
|
||||
}
|
||||
|
||||
if (pattern.size() == 1)
|
||||
{
|
||||
if (pos)
|
||||
{
|
||||
pos->push_back(maxScorePos);
|
||||
}
|
||||
int32_t end = maxScorePos + 1;
|
||||
return { maxScorePos, end, maxScore };
|
||||
}
|
||||
|
||||
int32_t firstOccurrenceOfFirstChar = firstOccurrenceOfEachChar[0];
|
||||
int32_t width = lastIndex - firstOccurrenceOfFirstChar + 1;
|
||||
int32_t rows = static_cast<int32_t>(pattern.size());
|
||||
auto consecutiveCharMatrixSize = width * patternSize;
|
||||
|
||||
std::vector<int16_t> scoreMatrix(width * rows);
|
||||
std::copy_n(
|
||||
initialScores.begin() + firstOccurrenceOfFirstChar,
|
||||
width,
|
||||
scoreMatrix.begin());
|
||||
auto scoreSpan = std::span<int16_t>(scoreMatrix);
|
||||
|
||||
std::vector<int16_t> consecutiveCharMatrix(width * rows);
|
||||
std::copy_n(
|
||||
consecutiveScores.begin() + firstOccurrenceOfFirstChar,
|
||||
width,
|
||||
consecutiveCharMatrix.begin());
|
||||
auto consecutiveCharMatrixSpan = std::span(consecutiveCharMatrix);
|
||||
|
||||
auto patternSliceStr = std::span(pattern).subspan(1);
|
||||
|
||||
for (int32_t off = 0; off < patternSize - 1; off++)
|
||||
{
|
||||
auto patternCharOffset = firstOccurrenceOfEachChar[off + 1];
|
||||
auto sliceLen = lastIndex - patternCharOffset + 1;
|
||||
currentPatternChar = patternSliceStr[off];
|
||||
patternIndex = off + 1;
|
||||
int32_t row = patternIndex * width;
|
||||
inGap = false;
|
||||
auto tmp = lowerText.subspan(patternCharOffset, sliceLen);
|
||||
std::span<const UChar32> textSlice = lowerText.subspan(patternCharOffset, sliceLen);
|
||||
std::span<int16_t> bonusSlice(bonusesSpan.begin() + patternCharOffset, textSlice.size());
|
||||
std::span<int16_t> consecutiveCharMatrixSlice = consecutiveCharMatrixSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar, textSlice.size());
|
||||
std::span<int16_t> consecutiveCharMatrixDiagonalSlice = consecutiveCharMatrixSpan.subspan(
|
||||
+row + patternCharOffset - firstOccurrenceOfFirstChar - 1 - width, textSlice.size());
|
||||
std::span<int16_t> scoreMatrixSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar, textSlice.size());
|
||||
std::span<int16_t> scoreMatrixDiagonalSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar - 1 - width, textSlice.size());
|
||||
std::span<int16_t> scoreMatrixLeftSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar - 1, textSlice.size());
|
||||
if (!scoreMatrixLeftSlice.empty())
|
||||
{
|
||||
scoreMatrixLeftSlice[0] = 0;
|
||||
}
|
||||
for (int32_t j = 0; j < static_cast<int32_t>(textSlice.size()); j++)
|
||||
{
|
||||
UChar32 currentChar = textSlice[j];
|
||||
int32_t column = patternCharOffset + static_cast<int32_t>(j);
|
||||
int16_t score = inGap ? scoreMatrixLeftSlice[j] + ScoreGapExtension : scoreMatrixLeftSlice[j] + ScoreGapStart;
|
||||
int16_t diagonalScore = 0;
|
||||
int16_t consecutive = 0;
|
||||
if (currentChar == currentPatternChar)
|
||||
{
|
||||
diagonalScore = scoreMatrixDiagonalSlice[j] + ScoreMatch;
|
||||
int16_t bonus = bonusSlice[j];
|
||||
consecutive = consecutiveCharMatrixDiagonalSlice[j] + 1;
|
||||
if (bonus == BoundaryBonus)
|
||||
{
|
||||
consecutive = 1;
|
||||
}
|
||||
else if (consecutive > 1)
|
||||
{
|
||||
bonus = std::max({ bonus, BonusConsecutive, (bonusesSpan[column - consecutive + 1]) });
|
||||
}
|
||||
if (diagonalScore + bonus < score)
|
||||
{
|
||||
diagonalScore += bonusSlice[j];
|
||||
consecutive = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
diagonalScore += bonus;
|
||||
}
|
||||
}
|
||||
consecutiveCharMatrixSlice[j] = consecutive;
|
||||
inGap = (diagonalScore < score);
|
||||
int16_t cellScore = std::max(int16_t{ 0 }, std::max(diagonalScore, score));
|
||||
if (off == patternSize - 2 && cellScore > maxScore)
|
||||
{
|
||||
maxScore = cellScore;
|
||||
maxScorePos = column;
|
||||
}
|
||||
scoreMatrixSlice[j] = cellScore;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t currentColIndex = maxScorePos;
|
||||
if (pos)
|
||||
{
|
||||
patternIndex = patternSize - 1;
|
||||
bool preferCurrentMatch = true;
|
||||
while (true)
|
||||
{
|
||||
int32_t rowStartIndex = patternIndex * width;
|
||||
int colOffset = currentColIndex - firstOccurrenceOfFirstChar;
|
||||
int cellScore = scoreMatrix[rowStartIndex + colOffset];
|
||||
int diagonalCellScore = 0, leftCellScore = 0;
|
||||
|
||||
if (patternIndex > 0 && currentColIndex >= firstOccurrenceOfEachChar[patternIndex])
|
||||
{
|
||||
diagonalCellScore = scoreMatrix[rowStartIndex - width + colOffset - 1];
|
||||
}
|
||||
if (currentColIndex > firstOccurrenceOfEachChar[patternIndex])
|
||||
{
|
||||
leftCellScore = scoreMatrix[rowStartIndex + colOffset - 1];
|
||||
}
|
||||
|
||||
if (cellScore > diagonalCellScore &&
|
||||
(cellScore > leftCellScore || (cellScore == leftCellScore && preferCurrentMatch)))
|
||||
{
|
||||
pos->push_back(currentColIndex);
|
||||
if (patternIndex == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
patternIndex--;
|
||||
}
|
||||
|
||||
currentColIndex--;
|
||||
if (rowStartIndex + colOffset >= consecutiveCharMatrixSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
preferCurrentMatch = (consecutiveCharMatrix[rowStartIndex + colOffset] > 1) ||
|
||||
((rowStartIndex + width + colOffset + 1 <
|
||||
consecutiveCharMatrixSize) &&
|
||||
(consecutiveCharMatrix[rowStartIndex + width + colOffset + 1] > 0));
|
||||
}
|
||||
}
|
||||
int32_t end = maxScorePos + 1;
|
||||
return { currentColIndex, end, maxScore };
|
||||
}
|
||||
|
||||
int BonusAt(std::wstring_view input, int idx)
|
||||
{
|
||||
if (idx == 0)
|
||||
{
|
||||
return BoundaryBonus;
|
||||
}
|
||||
return CalculateBonus(ClassOf(input[idx - 1]), ClassOf(input[idx]));
|
||||
}
|
||||
|
||||
int32_t utf32Length(std::wstring_view str)
|
||||
{
|
||||
return u_countChar32(reinterpret_cast<const UChar*>(str.data()), static_cast<int32_t>(str.size()));
|
||||
}
|
||||
|
||||
static std::vector<UChar32> ConvertUtf16ToCodePoints(
|
||||
std::wstring_view text,
|
||||
bool fold,
|
||||
std::vector<int32_t>* utf16OffsetsOut = nullptr)
|
||||
{
|
||||
const UChar* data = reinterpret_cast<const UChar*>(text.data());
|
||||
int32_t dataLen = static_cast<int32_t>(text.size());
|
||||
int32_t cpCount = utf32Length(text);
|
||||
|
||||
std::vector<UChar32> out;
|
||||
out.reserve(cpCount);
|
||||
|
||||
if (utf16OffsetsOut)
|
||||
{
|
||||
utf16OffsetsOut->clear();
|
||||
utf16OffsetsOut->reserve(cpCount);
|
||||
}
|
||||
|
||||
int32_t src = 0;
|
||||
while (src < dataLen)
|
||||
{
|
||||
auto startUnit = src;
|
||||
if (utf16OffsetsOut)
|
||||
{
|
||||
utf16OffsetsOut->push_back(startUnit);
|
||||
}
|
||||
|
||||
UChar32 cp;
|
||||
U16_NEXT(data, src, dataLen, cp);
|
||||
|
||||
if (fold)
|
||||
{
|
||||
cp = u_foldCase(cp, U_FOLD_CASE_DEFAULT);
|
||||
}
|
||||
|
||||
out.push_back(cp);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Pattern ParsePattern(const std::wstring_view patternStr)
|
||||
{
|
||||
Pattern patObj;
|
||||
if (patternStr.empty())
|
||||
{
|
||||
return patObj;
|
||||
}
|
||||
|
||||
auto trimmed = TrimStart(patternStr);
|
||||
trimmed = TrimSuffixSpaces(trimmed);
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < trimmed.size())
|
||||
{
|
||||
size_t found = trimmed.find(L' ', pos);
|
||||
auto slice = (found == std::wstring_view::npos) ? trimmed.substr(pos) : trimmed.substr(pos, found - pos);
|
||||
|
||||
patObj.terms.push_back(ConvertUtf16ToCodePoints(slice, true));
|
||||
|
||||
if (found == std::wstring_view::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
pos = found + 1;
|
||||
}
|
||||
|
||||
return patObj;
|
||||
}
|
||||
|
||||
static std::vector<int32_t> MapCodepointsToUtf16(
|
||||
std::vector<int32_t> const& cpPos,
|
||||
std::vector<int32_t> const& cpMap,
|
||||
size_t dataLen)
|
||||
{
|
||||
std::vector<int32_t> utf16pos;
|
||||
utf16pos.reserve(cpPos.size() * 2);
|
||||
|
||||
for (int32_t cpIndex : cpPos)
|
||||
{
|
||||
int32_t start = cpMap[cpIndex];
|
||||
int32_t end = cpIndex + int32_t{ 1 } < static_cast<int32_t>(cpMap.size()) ? cpMap[cpIndex + 1] : static_cast<int32_t>(dataLen);
|
||||
|
||||
for (int32_t cu = end - 1; cu >= start; --cu)
|
||||
{
|
||||
utf16pos.push_back(cu);
|
||||
}
|
||||
}
|
||||
return utf16pos;
|
||||
}
|
||||
|
||||
std::optional<MatchResult> Match(std::wstring_view text, const Pattern& pattern)
|
||||
{
|
||||
if (pattern.terms.empty())
|
||||
{
|
||||
return MatchResult{};
|
||||
;
|
||||
}
|
||||
|
||||
int16_t totalScore = 0;
|
||||
std::vector<int32_t> pos;
|
||||
for (const auto& term : pattern.terms)
|
||||
{
|
||||
std::vector<int32_t> utf16map;
|
||||
std::vector<int32_t> codePointPos;
|
||||
auto textCodePoints = ConvertUtf16ToCodePoints(text, false, &utf16map);
|
||||
FzfResult res = FzfFuzzyMatchV2(textCodePoints, term, &codePointPos);
|
||||
if (res.Score <= 0)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto termUtf16Pos = MapCodepointsToUtf16(codePointPos, utf16map, text.size());
|
||||
for (auto t : termUtf16Pos)
|
||||
{
|
||||
pos.push_back(t);
|
||||
}
|
||||
totalScore += res.Score;
|
||||
}
|
||||
return MatchResult{ totalScore, pos };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
32
src/cascadia/TerminalApp/fzf/fzf.h
Normal file
32
src/cascadia/TerminalApp/fzf/fzf.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <icu.h>
|
||||
|
||||
namespace fzf
|
||||
{
|
||||
namespace matcher
|
||||
{
|
||||
struct FzfResult
|
||||
{
|
||||
int32_t Start;
|
||||
int32_t End;
|
||||
int16_t Score;
|
||||
};
|
||||
|
||||
struct MatchResult
|
||||
{
|
||||
int16_t Score = 0;
|
||||
std::vector<int32_t> Pos;
|
||||
};
|
||||
|
||||
class Pattern
|
||||
{
|
||||
public:
|
||||
std::vector<std::vector<UChar32>> terms;
|
||||
};
|
||||
|
||||
Pattern ParsePattern(const std::wstring_view patternStr);
|
||||
std::optional<MatchResult> Match(std::wstring_view text, const Pattern& pattern);
|
||||
}
|
||||
}
|
||||
@@ -955,6 +955,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void ControlCore::SetHighContrastInfo(const bool enabled)
|
||||
{
|
||||
_terminal->SetHighContrastInfo(enabled);
|
||||
}
|
||||
|
||||
Control::IControlSettings ControlCore::Settings()
|
||||
{
|
||||
return *_settings;
|
||||
|
||||
@@ -94,6 +94,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
void UpdateSettings(const Control::IControlSettings& settings, const IControlAppearance& newAppearance);
|
||||
void ApplyAppearance(const bool focused);
|
||||
void SetHighContrastInfo(const bool enabled);
|
||||
Control::IControlSettings Settings();
|
||||
Control::IControlAppearance FocusedAppearance() const;
|
||||
Control::IControlAppearance UnfocusedAppearance() const;
|
||||
|
||||
@@ -102,6 +102,7 @@ namespace Microsoft.Terminal.Control
|
||||
IControlAppearance FocusedAppearance { get; };
|
||||
IControlAppearance UnfocusedAppearance { get; };
|
||||
Boolean HasUnfocusedAppearance();
|
||||
void SetHighContrastInfo(Boolean enabled);
|
||||
|
||||
UInt64 SwapChainHandle { get; };
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@ static Microsoft::Console::TSF::Handle& GetTSFHandle()
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
Windows::UI::ViewManagement::AccessibilitySettings TermControl::_accessibilitySettings{};
|
||||
|
||||
static void _translatePathInPlace(std::wstring& fullPath, PathTranslationStyle translationStyle)
|
||||
{
|
||||
static constexpr wil::zwstring_view s_pathPrefixes[] = {
|
||||
@@ -276,6 +278,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
_core = _interactivity.Core();
|
||||
|
||||
// If high contrast mode was changed, update the appearance appropriately.
|
||||
_core.SetHighContrastInfo(_accessibilitySettings.HighContrast());
|
||||
_revokers.HighContrastChanged = _accessibilitySettings.HighContrastChanged(winrt::auto_revoke, [weakThis{ get_weak() }](const Windows::UI::ViewManagement::AccessibilitySettings& a11ySettings, auto&&) {
|
||||
if (auto termControl = weakThis.get())
|
||||
{
|
||||
termControl->_core.SetHighContrastInfo(a11ySettings.HighContrast());
|
||||
termControl->_core.ApplyAppearance(termControl->_focused);
|
||||
}
|
||||
});
|
||||
|
||||
// This event is specifically triggered by the renderer thread, a BG thread. Use a weak ref here.
|
||||
_revokers.RendererEnteredErrorState = _core.RendererEnteredErrorState(winrt::auto_revoke, { get_weak(), &TermControl::_RendererEnteredErrorState });
|
||||
|
||||
|
||||
@@ -238,6 +238,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
|
||||
friend struct TsfDataProvider;
|
||||
|
||||
static Windows::UI::ViewManagement::AccessibilitySettings _accessibilitySettings;
|
||||
|
||||
// NOTE: _uiaEngine must be ordered before _core.
|
||||
//
|
||||
// ControlCore::AttachUiaEngine receives a IRenderEngine as a raw pointer, which we own.
|
||||
@@ -362,7 +364,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
};
|
||||
bool _InitializeTerminal(const InitializeReason reason);
|
||||
safe_void_coroutine _restoreInBackground();
|
||||
void _SetFontSize(int fontSize);
|
||||
void _TappedHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& e);
|
||||
void _KeyDownHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
|
||||
void _KeyUpHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
|
||||
@@ -394,8 +395,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _SwapChainSizeChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::SizeChangedEventArgs& e);
|
||||
void _SwapChainScaleChanged(const Windows::UI::Xaml::Controls::SwapChainPanel& sender, const Windows::Foundation::IInspectable& args);
|
||||
|
||||
void _TerminalTabColorChanged(const std::optional<til::color> color);
|
||||
|
||||
void _ScrollPositionChanged(const IInspectable& sender, const Control::ScrollPositionChangedArgs& args);
|
||||
|
||||
bool _CapturePointer(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
|
||||
@@ -480,6 +479,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// These are set up in _InitializeTerminal
|
||||
Control::ControlCore::RendererWarning_revoker RendererWarning;
|
||||
Control::ControlCore::SwapChainChanged_revoker SwapChainChanged;
|
||||
Windows::UI::ViewManagement::AccessibilitySettings::HighContrastChanged_revoker HighContrastChanged;
|
||||
|
||||
Control::ControlInteractivity::OpenHyperlink_revoker interactivityOpenHyperlink;
|
||||
Control::ControlInteractivity::ScrollPositionChanged_revoker interactivityScrollPositionChanged;
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace Microsoft.Terminal.Core
|
||||
{
|
||||
Never,
|
||||
Indexed,
|
||||
Always
|
||||
Always,
|
||||
Automatic
|
||||
};
|
||||
|
||||
// TerminalCore declares its own Color struct to avoid depending
|
||||
|
||||
@@ -143,7 +143,15 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance)
|
||||
renderSettings.SetRenderMode(RenderSettings::Mode::IntenseIsBold, appearance.IntenseIsBold());
|
||||
renderSettings.SetRenderMode(RenderSettings::Mode::IntenseIsBright, appearance.IntenseIsBright());
|
||||
|
||||
switch (appearance.AdjustIndistinguishableColors())
|
||||
// If AIC is set to Automatic,
|
||||
// update the value based on if high contrast mode is enabled.
|
||||
AdjustTextMode deducedAIC = appearance.AdjustIndistinguishableColors();
|
||||
if (deducedAIC == AdjustTextMode::Automatic)
|
||||
{
|
||||
deducedAIC = _highContrastMode ? AdjustTextMode::Indexed : AdjustTextMode::Never;
|
||||
}
|
||||
|
||||
switch (deducedAIC)
|
||||
{
|
||||
case AdjustTextMode::Always:
|
||||
renderSettings.SetRenderMode(RenderSettings::Mode::IndexedDistinguishableColors, false);
|
||||
@@ -213,6 +221,11 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance)
|
||||
_NotifyScrollEvent();
|
||||
}
|
||||
|
||||
void Terminal::SetHighContrastInfo(bool hc) noexcept
|
||||
{
|
||||
_highContrastMode = hc;
|
||||
}
|
||||
|
||||
void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
|
||||
{
|
||||
auto& engine = reinterpret_cast<OutputStateMachineEngine&>(_stateMachine->Engine());
|
||||
|
||||
@@ -92,6 +92,7 @@ public:
|
||||
|
||||
void UpdateSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings);
|
||||
void UpdateAppearance(const winrt::Microsoft::Terminal::Core::ICoreAppearance& appearance);
|
||||
void SetHighContrastInfo(bool hc) noexcept;
|
||||
void SetFontInfo(const FontInfo& fontInfo);
|
||||
void SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle);
|
||||
void SetVtChecksumReportSupport(const bool enabled);
|
||||
@@ -382,6 +383,7 @@ private:
|
||||
|
||||
std::wstring _answerbackMessage;
|
||||
std::wstring _workingDirectory;
|
||||
bool _highContrastMode = false;
|
||||
|
||||
// This default fake font value is only used to check if the font is a raster font.
|
||||
// Otherwise, the font is changed to a real value with the renderer via TriggerFontChange.
|
||||
|
||||
@@ -19,57 +19,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
Automation::AutomationProperties::SetName(AddNewButton(), RS_(L"Actions_AddNewTextBlock/Text"));
|
||||
}
|
||||
|
||||
Automation::Peers::AutomationPeer Actions::OnCreateAutomationPeer()
|
||||
{
|
||||
_ViewModel.OnAutomationPeerAttached();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Actions::OnNavigatedTo(const NavigationEventArgs& e)
|
||||
{
|
||||
_ViewModel = e.Parameter().as<Editor::ActionsViewModel>();
|
||||
|
||||
// Subscribe to the view model's FocusContainer event.
|
||||
// Use the KeyBindingViewModel or index provided in the event to focus the corresponding container
|
||||
_ViewModel.FocusContainer([this](const auto& /*sender*/, const auto& args) {
|
||||
if (auto kbdVM{ args.try_as<KeyBindingViewModel>() })
|
||||
{
|
||||
if (const auto& container = KeyBindingsListView().ContainerFromItem(*kbdVM))
|
||||
{
|
||||
container.as<Controls::ListViewItem>().Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
else if (const auto& index = args.try_as<uint32_t>())
|
||||
{
|
||||
if (const auto& container = KeyBindingsListView().ContainerFromIndex(*index))
|
||||
{
|
||||
container.as<Controls::ListViewItem>().Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to the view model's UpdateBackground event.
|
||||
// The view model does not have access to the page resources, so it asks us
|
||||
// to update the key binding's container background
|
||||
_ViewModel.UpdateBackground([this](const auto& /*sender*/, const auto& args) {
|
||||
if (auto kbdVM{ args.try_as<KeyBindingViewModel>() })
|
||||
{
|
||||
if (kbdVM->IsInEditMode())
|
||||
{
|
||||
const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as<Windows::UI::Xaml::Media::Brush>() };
|
||||
kbdVM->ContainerBackground(containerBackground);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackground")).as<Windows::UI::Xaml::Media::Brush>() };
|
||||
kbdVM->ContainerBackground(containerBackground);
|
||||
}
|
||||
}
|
||||
});
|
||||
_ViewModel.CurrentPage(ActionsSubPage::Base);
|
||||
}
|
||||
|
||||
void Actions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
_ViewModel.AddNewKeybinding();
|
||||
_ViewModel.AddNewCommand();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
Actions();
|
||||
|
||||
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
|
||||
|
||||
void AddNew_Click(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
<Style x:Key="KeyBindingContainerStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="4" />
|
||||
<Setter Property="Padding" Value="12,4,4,4" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="XYFocusKeyboardNavigation" Value="Enabled" />
|
||||
</Style>
|
||||
@@ -137,24 +137,6 @@
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<x:Int32 x:Key="EditButtonSize">32</x:Int32>
|
||||
<x:Double x:Key="EditButtonIconSize">15</x:Double>
|
||||
<Style x:Key="EditButtonStyle"
|
||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Height" Value="{StaticResource EditButtonSize}" />
|
||||
<Setter Property="Width" Value="{StaticResource EditButtonSize}" />
|
||||
</Style>
|
||||
<Style x:Key="AccentEditButtonStyle"
|
||||
BasedOn="{StaticResource AccentButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Padding" Value="3" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Height" Value="{StaticResource EditButtonSize}" />
|
||||
<Setter Property="Width" Value="{StaticResource EditButtonSize}" />
|
||||
</Style>
|
||||
|
||||
<!-- Converters & Misc. -->
|
||||
<SolidColorBrush x:Key="ActionContainerBackgroundEditing"
|
||||
@@ -163,22 +145,16 @@
|
||||
Color="Transparent" />
|
||||
|
||||
<!-- Templates -->
|
||||
<DataTemplate x:Key="KeyBindingTemplate"
|
||||
x:DataType="local:KeyBindingViewModel">
|
||||
<ListViewItem AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}"
|
||||
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
Background="{x:Bind ContainerBackground, Mode=OneWay}"
|
||||
GotFocus="{x:Bind ActionGotFocus}"
|
||||
LostFocus="{x:Bind ActionLostFocus}"
|
||||
PointerEntered="{x:Bind EnterHoverMode}"
|
||||
PointerExited="{x:Bind ExitHoverMode}"
|
||||
<DataTemplate x:Key="CommandTemplate"
|
||||
x:DataType="local:CommandViewModel">
|
||||
<ListViewItem AutomationProperties.Name="{x:Bind DisplayName, Mode=OneWay}"
|
||||
Style="{StaticResource KeyBindingContainerStyle}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<!-- command name -->
|
||||
<ColumnDefinition Width="*" />
|
||||
<!-- key chord -->
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="150" />
|
||||
<!-- edit buttons -->
|
||||
<!--
|
||||
This needs to be 112 because that is the width of the row of buttons in edit mode + padding.
|
||||
@@ -192,133 +168,7 @@
|
||||
<!-- Command Name -->
|
||||
<TextBlock Grid.Column="0"
|
||||
Style="{StaticResource KeyBindingNameTextBlockStyle}"
|
||||
Text="{x:Bind Name, Mode=OneWay}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}" />
|
||||
|
||||
<!-- Edit Mode: Action Combo-box -->
|
||||
<ComboBox x:Uid="Actions_ActionComboBox"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind AvailableActions, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ProposedAction, Mode=TwoWay}"
|
||||
Visibility="{x:Bind IsInEditMode, Mode=OneWay}" />
|
||||
|
||||
<!-- Key Chord Text -->
|
||||
<Border Grid.Column="1"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}">
|
||||
|
||||
<TextBlock FontSize="14"
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</Border>
|
||||
|
||||
<!-- Edit Mode: Key Chord Listener -->
|
||||
<local:KeyChordListener Grid.Column="1"
|
||||
Keys="{x:Bind ProposedKeys, Mode=TwoWay}"
|
||||
Style="{StaticResource KeyChordEditorStyle}"
|
||||
Visibility="{x:Bind IsInEditMode, Mode=OneWay}" />
|
||||
|
||||
<!-- Edit Button -->
|
||||
<Button x:Uid="Actions_EditButton"
|
||||
Grid.Column="2"
|
||||
AutomationProperties.Name="{x:Bind EditButtonName}"
|
||||
Background="Transparent"
|
||||
Click="{x:Bind ToggleEditMode}"
|
||||
GettingFocus="{x:Bind EditButtonGettingFocus}"
|
||||
LosingFocus="{x:Bind EditButtonLosingFocus}"
|
||||
Style="{StaticResource EditButtonStyle}"
|
||||
Visibility="{x:Bind ShowEditButton, Mode=OneWay}">
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
|
||||
Glyph="" />
|
||||
</Button.Content>
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="ButtonForegroundPointerOver"
|
||||
Color="{StaticResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="ButtonForegroundPressed"
|
||||
Color="{StaticResource SystemAccentColor}" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="ButtonForegroundPointerOver"
|
||||
Color="{StaticResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="ButtonForegroundPressed"
|
||||
Color="{StaticResource SystemAccentColor}" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="ButtonBackground"
|
||||
Color="{ThemeResource SystemColorButtonFaceColor}" />
|
||||
<SolidColorBrush x:Key="ButtonBackgroundPointerOver"
|
||||
Color="{ThemeResource SystemColorHighlightColor}" />
|
||||
<SolidColorBrush x:Key="ButtonBackgroundPressed"
|
||||
Color="{ThemeResource SystemColorHighlightColor}" />
|
||||
<SolidColorBrush x:Key="ButtonForeground"
|
||||
Color="{ThemeResource SystemColorButtonTextColor}" />
|
||||
<SolidColorBrush x:Key="ButtonForegroundPointerOver"
|
||||
Color="{ThemeResource SystemColorHighlightTextColor}" />
|
||||
<SolidColorBrush x:Key="ButtonForegroundPressed"
|
||||
Color="{ThemeResource SystemColorHighlightTextColor}" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Button.Resources>
|
||||
</Button>
|
||||
|
||||
<!-- Edit Mode: Buttons -->
|
||||
<StackPanel Grid.Column="2"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{x:Bind IsInEditMode, Mode=OneWay}">
|
||||
|
||||
<!-- Cancel editing the action -->
|
||||
<Button x:Uid="Actions_CancelButton"
|
||||
AutomationProperties.Name="{x:Bind CancelButtonName}"
|
||||
Click="{x:Bind CancelChanges}"
|
||||
Style="{StaticResource EditButtonStyle}">
|
||||
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
|
||||
<!-- Accept changes -->
|
||||
<Button x:Uid="Actions_AcceptButton"
|
||||
Margin="8,0,0,0"
|
||||
AutomationProperties.Name="{x:Bind AcceptButtonName}"
|
||||
Click="{x:Bind AttemptAcceptChanges}"
|
||||
Flyout="{x:Bind AcceptChangesFlyout, Mode=OneWay}"
|
||||
Style="{StaticResource AccentEditButtonStyle}">
|
||||
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
|
||||
<!-- Delete the current key binding -->
|
||||
<Button x:Uid="Actions_DeleteButton"
|
||||
Margin="8,0,0,0"
|
||||
AutomationProperties.Name="{x:Bind DeleteButtonName}"
|
||||
Style="{StaticResource DeleteSmallButtonStyle}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsNewlyAdded), Mode=OneWay}">
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
|
||||
Glyph="" />
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<Flyout FlyoutPresenterStyle="{StaticResource CustomFlyoutPresenterStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="Actions_DeleteConfirmationMessage"
|
||||
Style="{StaticResource CustomFlyoutTextStyle}" />
|
||||
<Button x:Uid="Actions_DeleteConfirmationButton"
|
||||
Click="{x:Bind DeleteKeyBinding}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
Text="{x:Bind DisplayName, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
@@ -330,6 +180,13 @@
|
||||
HorizontalAlignment="Left"
|
||||
Spacing="8"
|
||||
Style="{StaticResource SettingsStackStyle}">
|
||||
<TextBlock Style="{StaticResource DisclaimerStyle}"
|
||||
TextWrapping="WrapWholeWords">
|
||||
<Hyperlink NavigateUri="https://learn.microsoft.com/en-us/windows/terminal/customize-settings/actions"
|
||||
TextDecorations="None">
|
||||
<Run x:Uid="Actions_Disclaimer" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<!-- Add New Button -->
|
||||
<Button x:Name="AddNewButton"
|
||||
Click="AddNew_Click">
|
||||
@@ -344,10 +201,12 @@
|
||||
</Button>
|
||||
|
||||
<!-- Keybindings -->
|
||||
<ListView x:Name="KeyBindingsListView"
|
||||
ItemTemplate="{StaticResource KeyBindingTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.KeyBindingList, Mode=OneWay}"
|
||||
SelectionMode="None" />
|
||||
<ListView x:Name="CommandsListView"
|
||||
Margin="-8,0,0,0"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="{x:Bind ViewModel.CmdListItemClicked}"
|
||||
ItemTemplate="{StaticResource CommandTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.CommandList, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Page>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,127 +4,248 @@
|
||||
#pragma once
|
||||
|
||||
#include "ActionsViewModel.g.h"
|
||||
#include "KeyBindingViewModel.g.h"
|
||||
#include "ModifyKeyBindingEventArgs.g.h"
|
||||
#include "NavigateToCommandArgs.g.h"
|
||||
#include "CommandViewModel.g.h"
|
||||
#include "ArgWrapper.g.h"
|
||||
#include "ActionArgsViewModel.g.h"
|
||||
#include "KeyChordViewModel.g.h"
|
||||
#include "ModifyKeyChordEventArgs.g.h"
|
||||
#include "Utils.h"
|
||||
#include "ViewModelHelpers.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct KeyBindingViewModelComparator
|
||||
struct CommandViewModelComparator
|
||||
{
|
||||
bool operator()(const Editor::KeyBindingViewModel& lhs, const Editor::KeyBindingViewModel& rhs) const
|
||||
bool operator()(const Editor::CommandViewModel& lhs, const Editor::CommandViewModel& rhs) const
|
||||
{
|
||||
return lhs.Name() < rhs.Name();
|
||||
return lhs.DisplayName() < rhs.DisplayName();
|
||||
}
|
||||
};
|
||||
|
||||
struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT<ModifyKeyBindingEventArgs>
|
||||
struct NavigateToCommandArgs : NavigateToCommandArgsT<NavigateToCommandArgs>
|
||||
{
|
||||
public:
|
||||
ModifyKeyBindingEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys, const hstring oldActionName, const hstring newActionName) :
|
||||
NavigateToCommandArgs(CommandViewModel command, Editor::IHostedInWindow windowRoot) :
|
||||
_Command(command),
|
||||
_WindowRoot(windowRoot) {}
|
||||
|
||||
Editor::IHostedInWindow WindowRoot() const noexcept { return _WindowRoot; }
|
||||
Editor::CommandViewModel Command() const noexcept { return _Command; }
|
||||
|
||||
private:
|
||||
Editor::IHostedInWindow _WindowRoot;
|
||||
Editor::CommandViewModel _Command{ nullptr };
|
||||
};
|
||||
|
||||
struct ModifyKeyChordEventArgs : ModifyKeyChordEventArgsT<ModifyKeyChordEventArgs>
|
||||
{
|
||||
public:
|
||||
ModifyKeyChordEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys) :
|
||||
_OldKeys{ oldKeys },
|
||||
_NewKeys{ newKeys },
|
||||
_OldActionName{ std::move(oldActionName) },
|
||||
_NewActionName{ std::move(newActionName) } {}
|
||||
_NewKeys{ newKeys } {}
|
||||
|
||||
WINRT_PROPERTY(Control::KeyChord, OldKeys, nullptr);
|
||||
WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr);
|
||||
WINRT_PROPERTY(hstring, OldActionName);
|
||||
WINRT_PROPERTY(hstring, NewActionName);
|
||||
};
|
||||
|
||||
struct KeyBindingViewModel : KeyBindingViewModelT<KeyBindingViewModel>, ViewModelHelper<KeyBindingViewModel>
|
||||
struct CommandViewModel : CommandViewModelT<CommandViewModel>, ViewModelHelper<CommandViewModel>
|
||||
{
|
||||
public:
|
||||
KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector<hstring>& availableActions);
|
||||
KeyBindingViewModel(const Control::KeyChord& keys, const hstring& name, const Windows::Foundation::Collections::IObservableVector<hstring>& availableActions);
|
||||
CommandViewModel(winrt::Microsoft::Terminal::Settings::Model::Command cmd,
|
||||
std::vector<Control::KeyChord> keyChordList,
|
||||
const Editor::ActionsViewModel actionsPageVM,
|
||||
const Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring>& availableShortcutActionsAndNames);
|
||||
void Initialize();
|
||||
|
||||
hstring Name() const { return _CurrentAction; }
|
||||
hstring KeyChordText() const { return _KeyChordText; }
|
||||
winrt::hstring DisplayName();
|
||||
winrt::hstring Name();
|
||||
void Name(const winrt::hstring& newName);
|
||||
|
||||
// UIA Text
|
||||
hstring EditButtonName() const noexcept;
|
||||
hstring CancelButtonName() const noexcept;
|
||||
hstring AcceptButtonName() const noexcept;
|
||||
hstring DeleteButtonName() const noexcept;
|
||||
winrt::hstring ID();
|
||||
void ID(const winrt::hstring& newID);
|
||||
|
||||
void EnterHoverMode() { IsHovered(true); };
|
||||
void ExitHoverMode() { IsHovered(false); };
|
||||
void ActionGotFocus() { IsContainerFocused(true); };
|
||||
void ActionLostFocus() { IsContainerFocused(false); };
|
||||
void EditButtonGettingFocus() { IsEditButtonFocused(true); };
|
||||
void EditButtonLosingFocus() { IsEditButtonFocused(false); };
|
||||
bool ShowEditButton() const noexcept;
|
||||
void ToggleEditMode();
|
||||
void DisableEditMode() { IsInEditMode(false); }
|
||||
void AttemptAcceptChanges();
|
||||
void AttemptAcceptChanges(const Control::KeyChord newKeys);
|
||||
void CancelChanges();
|
||||
void DeleteKeyBinding() { DeleteKeyBindingRequested.raise(*this, _CurrentKeys); }
|
||||
bool IsUserAction();
|
||||
|
||||
// ProposedAction: the entry selected by the combo box; may disagree with the settings model.
|
||||
// CurrentAction: the combo box item that maps to the settings model value.
|
||||
// AvailableActions: the list of options in the combo box; both actions above must be in this list.
|
||||
// NOTE: ProposedAction and CurrentAction may disagree mainly due to the "edit mode" system in place.
|
||||
// Current Action serves as...
|
||||
// 1 - a record of what to set ProposedAction to on a cancellation
|
||||
// 2 - a form of translation between ProposedAction and the settings model
|
||||
// We would also need an ActionMap reference to remove this, but this is a better separation
|
||||
// of responsibilities.
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedAction);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentAction);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<hstring>, AvailableActions, nullptr);
|
||||
void Edit_Click();
|
||||
til::typed_event<Editor::CommandViewModel, IInspectable> EditRequested;
|
||||
|
||||
// ProposedKeys: the keys proposed by the control; may disagree with the settings model.
|
||||
// CurrentKeys: the key chord bound in the settings model.
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, CurrentKeys, nullptr);
|
||||
void Delete_Click();
|
||||
til::typed_event<Editor::CommandViewModel, IInspectable> DeleteRequested;
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsNewlyAdded, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsAutomationPeerAttached, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsHovered, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsContainerFocused, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsEditButtonFocused, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Media::Brush, ContainerBackground, nullptr);
|
||||
void AddKeybinding_Click();
|
||||
|
||||
public:
|
||||
til::typed_event<Editor::KeyBindingViewModel, Editor::ModifyKeyBindingEventArgs> ModifyKeyBindingRequested;
|
||||
til::typed_event<Editor::KeyBindingViewModel, Terminal::Control::KeyChord> DeleteKeyBindingRequested;
|
||||
til::typed_event<Editor::KeyBindingViewModel, IInspectable> DeleteNewlyAddedKeyBinding;
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateColorSchemeRequested;
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateColorSchemeNamesRequested;
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateWindowRootRequested;
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedShortcutAction);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ActionArgsViewModel, ActionArgsVM, nullptr);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<hstring>, AvailableShortcutActions, nullptr);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::KeyChordViewModel>, KeyChordViewModelList, nullptr);
|
||||
WINRT_PROPERTY(bool, IsNewCommand, false);
|
||||
|
||||
private:
|
||||
hstring _KeyChordText{};
|
||||
winrt::Microsoft::Terminal::Settings::Model::Command _command;
|
||||
std::vector<Control::KeyChord> _keyChordList;
|
||||
weak_ref<Editor::ActionsViewModel> _actionsPageVM{ nullptr };
|
||||
void _RegisterKeyChordVMEvents(Editor::KeyChordViewModel kcVM);
|
||||
void _RegisterActionArgsVMEvents(Editor::ActionArgsViewModel actionArgsVM);
|
||||
void _ReplaceCommandWithUserCopy(bool reinitialize);
|
||||
void _CreateAndInitializeActionArgsVMHelper();
|
||||
Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring> _AvailableActionsAndNamesMap;
|
||||
std::unordered_map<winrt::hstring, Model::ShortcutAction> _NameToActionMap;
|
||||
};
|
||||
|
||||
struct ArgWrapper : ArgWrapperT<ArgWrapper>, ViewModelHelper<ArgWrapper>
|
||||
{
|
||||
public:
|
||||
ArgWrapper(const winrt::hstring& name, const winrt::hstring& type, const bool required, const Model::ArgTag tag, const Windows::Foundation::IInspectable& value);
|
||||
void Initialize();
|
||||
|
||||
winrt::hstring Name() const noexcept { return _name; };
|
||||
winrt::hstring Type() const noexcept { return _type; };
|
||||
Model::ArgTag Tag() const noexcept { return _tag; };
|
||||
bool Required() const noexcept { return _required; };
|
||||
|
||||
// We cannot use the macro here because we need to implement additional logic for the setter
|
||||
Windows::Foundation::IInspectable EnumValue() const noexcept { return _EnumValue; };
|
||||
void EnumValue(const Windows::Foundation::IInspectable& value);
|
||||
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::EnumEntry> EnumList() const noexcept { return _EnumList; };
|
||||
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::FlagEntry> FlagList() const noexcept { return _FlagList; };
|
||||
|
||||
// unboxing functions
|
||||
winrt::hstring UnboxString(const Windows::Foundation::IInspectable& value);
|
||||
winrt::hstring UnboxGuid(const Windows::Foundation::IInspectable& value);
|
||||
int32_t UnboxInt32(const Windows::Foundation::IInspectable& value);
|
||||
float UnboxInt32Optional(const Windows::Foundation::IInspectable& value);
|
||||
uint32_t UnboxUInt32(const Windows::Foundation::IInspectable& value);
|
||||
float UnboxUInt32Optional(const Windows::Foundation::IInspectable& value);
|
||||
float UnboxUInt64(const Windows::Foundation::IInspectable& value);
|
||||
float UnboxFloat(const Windows::Foundation::IInspectable& value);
|
||||
bool UnboxBool(const Windows::Foundation::IInspectable& value);
|
||||
winrt::Windows::Foundation::IReference<bool> UnboxBoolOptional(const Windows::Foundation::IInspectable& value);
|
||||
winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> UnboxTerminalCoreColorOptional(const Windows::Foundation::IInspectable& value);
|
||||
winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> UnboxWindowsUIColorOptional(const Windows::Foundation::IInspectable& value);
|
||||
|
||||
// bind back functions
|
||||
void StringBindBack(const winrt::hstring& newValue);
|
||||
void GuidBindBack(const winrt::hstring& newValue);
|
||||
void Int32BindBack(const double newValue);
|
||||
void Int32OptionalBindBack(const double newValue);
|
||||
void UInt32BindBack(const double newValue);
|
||||
void UInt32OptionalBindBack(const double newValue);
|
||||
void UInt64BindBack(const double newValue);
|
||||
void FloatBindBack(const double newValue);
|
||||
void BoolOptionalBindBack(const Windows::Foundation::IReference<bool> newValue);
|
||||
void TerminalCoreColorBindBack(const winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> newValue);
|
||||
void WindowsUIColorBindBack(const winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> newValue);
|
||||
|
||||
safe_void_coroutine Browse_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
|
||||
// some argWrappers need to know additional information (like the default color scheme or the list of all color scheme names)
|
||||
// to avoid populating all ArgWrappers with that information, instead we emit an event when we need that information
|
||||
// (these events then get propagated up to the ActionsVM) and then the actionsVM will populate the value in us
|
||||
// since there's an actionArgsVM above us and a commandVM above that, the event does get propagated through a few times but that's
|
||||
// probably better than having every argWrapper contain the information by default
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> ColorSchemeRequested;
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> ColorSchemeNamesRequested;
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> WindowRootRequested;
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ColorSchemeViewModel, DefaultColorScheme, nullptr);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::IInspectable, Value, nullptr);
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IVector<winrt::hstring>, ColorSchemeNamesList, nullptr);
|
||||
WINRT_PROPERTY(Editor::IHostedInWindow, WindowRoot, nullptr);
|
||||
|
||||
private:
|
||||
winrt::hstring _name;
|
||||
winrt::hstring _type;
|
||||
Model::ArgTag _tag;
|
||||
bool _required;
|
||||
Windows::Foundation::IInspectable _EnumValue{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::EnumEntry> _EnumList;
|
||||
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::FlagEntry> _FlagList;
|
||||
};
|
||||
|
||||
struct ActionArgsViewModel : ActionArgsViewModelT<ActionArgsViewModel>, ViewModelHelper<ActionArgsViewModel>
|
||||
{
|
||||
public:
|
||||
ActionArgsViewModel(const Microsoft::Terminal::Settings::Model::ActionAndArgs actionAndArgs);
|
||||
void Initialize();
|
||||
|
||||
bool HasArgs() const noexcept;
|
||||
void ReplaceActionAndArgs(Model::ActionAndArgs newActionAndArgs);
|
||||
|
||||
til::typed_event<IInspectable, IInspectable> WrapperValueChanged;
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateColorSchemeRequested;
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateColorSchemeNamesRequested;
|
||||
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateWindowRootRequested;
|
||||
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::ArgWrapper>, ArgValues, nullptr);
|
||||
|
||||
private:
|
||||
Model::ActionAndArgs _actionAndArgs{ nullptr };
|
||||
};
|
||||
|
||||
struct KeyChordViewModel : KeyChordViewModelT<KeyChordViewModel>, ViewModelHelper<KeyChordViewModel>
|
||||
{
|
||||
public:
|
||||
KeyChordViewModel(Control::KeyChord CurrentKeys);
|
||||
|
||||
void CurrentKeys(const Control::KeyChord& newKeys);
|
||||
Control::KeyChord CurrentKeys() const noexcept;
|
||||
|
||||
void ToggleEditMode();
|
||||
void AttemptAcceptChanges();
|
||||
void CancelChanges();
|
||||
void DeleteKeyChord();
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(winrt::hstring, KeyChordText);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr);
|
||||
|
||||
public:
|
||||
til::typed_event<Editor::KeyChordViewModel, Terminal::Control::KeyChord> AddKeyChordRequested;
|
||||
til::typed_event<Editor::KeyChordViewModel, Editor::ModifyKeyChordEventArgs> ModifyKeyChordRequested;
|
||||
til::typed_event<Editor::KeyChordViewModel, Terminal::Control::KeyChord> DeleteKeyChordRequested;
|
||||
|
||||
private:
|
||||
Control::KeyChord _currentKeys;
|
||||
};
|
||||
|
||||
struct ActionsViewModel : ActionsViewModelT<ActionsViewModel>, ViewModelHelper<ActionsViewModel>
|
||||
{
|
||||
public:
|
||||
ActionsViewModel(Model::CascadiaSettings settings);
|
||||
void UpdateSettings(const Model::CascadiaSettings& settings);
|
||||
|
||||
void OnAutomationPeerAttached();
|
||||
void AddNewKeybinding();
|
||||
void AddNewCommand();
|
||||
|
||||
til::typed_event<IInspectable, IInspectable> FocusContainer;
|
||||
til::typed_event<IInspectable, IInspectable> UpdateBackground;
|
||||
void CurrentCommand(const Editor::CommandViewModel& newCommand);
|
||||
Editor::CommandViewModel CurrentCommand();
|
||||
void CmdListItemClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::ItemClickEventArgs& e);
|
||||
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::KeyBindingViewModel>, KeyBindingList);
|
||||
void AttemptDeleteKeyChord(const Control::KeyChord& keys);
|
||||
void AttemptAddOrModifyKeyChord(const Editor::KeyChordViewModel& senderVM, winrt::hstring commandID, const Control::KeyChord& newKeys, const Control::KeyChord& oldKeys);
|
||||
void AttemptAddCopiedCommand(const Model::Command& newCommand);
|
||||
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::CommandViewModel>, CommandList);
|
||||
WINRT_OBSERVABLE_PROPERTY(ActionsSubPage, CurrentPage, _propertyChangedHandlers, ActionsSubPage::Base);
|
||||
|
||||
private:
|
||||
bool _AutomationPeerAttached{ false };
|
||||
Editor::CommandViewModel _CurrentCommand{ nullptr };
|
||||
Model::CascadiaSettings _Settings;
|
||||
Windows::Foundation::Collections::IObservableVector<hstring> _AvailableActionAndArgs;
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionMap;
|
||||
Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring> _AvailableActionsAndNamesMap;
|
||||
|
||||
std::optional<uint32_t> _GetContainerIndexByKeyChord(const Control::KeyChord& keys);
|
||||
void _RegisterEvents(com_ptr<implementation::KeyBindingViewModel>& kbdVM);
|
||||
void _MakeCommandVMsHelper();
|
||||
void _RegisterCmdVMEvents(com_ptr<implementation::CommandViewModel>& cmdVM);
|
||||
|
||||
void _KeyBindingViewModelPropertyChangedHandler(const Windows::Foundation::IInspectable& senderVM, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
void _KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& args);
|
||||
void _KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args);
|
||||
void _KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& args);
|
||||
void _CmdVMPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
void _CmdVMEditRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& args);
|
||||
void _CmdVMDeleteRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& args);
|
||||
void _CmdVMPropagateColorSchemeRequestedHandler(const IInspectable& sender, const Editor::ArgWrapper& wrapper);
|
||||
void _CmdVMPropagateColorSchemeNamesRequestedHandler(const IInspectable& sender, const Editor::ArgWrapper& wrapper);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,60 +1,148 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "EnumEntry.idl";
|
||||
import "ColorSchemeViewModel.idl";
|
||||
import "MainPage.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
runtimeclass ModifyKeyBindingEventArgs
|
||||
runtimeclass NavigateToCommandArgs
|
||||
{
|
||||
CommandViewModel Command { get; };
|
||||
IHostedInWindow WindowRoot { get; };
|
||||
}
|
||||
|
||||
runtimeclass ModifyKeyChordEventArgs
|
||||
{
|
||||
Microsoft.Terminal.Control.KeyChord OldKeys { get; };
|
||||
Microsoft.Terminal.Control.KeyChord NewKeys { get; };
|
||||
String OldActionName { get; };
|
||||
String NewActionName { get; };
|
||||
}
|
||||
|
||||
runtimeclass KeyBindingViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
runtimeclass CommandViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
// Settings Model side
|
||||
String Name;
|
||||
String ID;
|
||||
Boolean IsUserAction { get; };
|
||||
// keybindings
|
||||
IObservableVector<KeyChordViewModel> KeyChordViewModelList { get; };
|
||||
// action args
|
||||
ActionArgsViewModel ActionArgsVM { get; };
|
||||
|
||||
// View-model specific
|
||||
String DisplayName { get; };
|
||||
|
||||
// UI side (command list page)
|
||||
void Edit_Click();
|
||||
|
||||
// UI side (edit command page)
|
||||
IObservableVector<String> AvailableShortcutActions { get; };
|
||||
Object ProposedShortcutAction;
|
||||
void Delete_Click();
|
||||
void AddKeybinding_Click();
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> PropagateColorSchemeRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> PropagateColorSchemeNamesRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> PropagateWindowRootRequested;
|
||||
}
|
||||
|
||||
runtimeclass ArgWrapper : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
String Name { get; };
|
||||
String Type { get; };
|
||||
Microsoft.Terminal.Settings.Model.ArgTag Tag { get; };
|
||||
Boolean Required { get; };
|
||||
IInspectable Value;
|
||||
IInspectable EnumValue;
|
||||
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> EnumList { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.FlagEntry> FlagList { get; };
|
||||
ColorSchemeViewModel DefaultColorScheme;
|
||||
Windows.Foundation.Collections.IVector<String> ColorSchemeNamesList;
|
||||
IHostedInWindow WindowRoot;
|
||||
|
||||
// unboxing functions
|
||||
String UnboxString(Object value);
|
||||
String UnboxGuid(Object value);
|
||||
UInt32 UnboxInt32(Object value);
|
||||
Single UnboxInt32Optional(Object value);
|
||||
UInt32 UnboxUInt32(Object value);
|
||||
Single UnboxUInt32Optional(Object value);
|
||||
Single UnboxUInt64(Object value);
|
||||
Single UnboxFloat(Object value);
|
||||
Boolean UnboxBool(Object value);
|
||||
Windows.Foundation.IReference<Boolean> UnboxBoolOptional(Object value);
|
||||
Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> UnboxTerminalCoreColorOptional(Object value);
|
||||
Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> UnboxWindowsUIColorOptional(Object value);
|
||||
|
||||
// bind back functions
|
||||
void StringBindBack(String newValue);
|
||||
void GuidBindBack(String newValue);
|
||||
void Int32BindBack(Double newValue);
|
||||
void Int32OptionalBindBack(Double newValue);
|
||||
void UInt32BindBack(Double newValue);
|
||||
void UInt32OptionalBindBack(Double newValue);
|
||||
void UInt64BindBack(Double newValue);
|
||||
void FloatBindBack(Double newValue);
|
||||
void BoolOptionalBindBack(Windows.Foundation.IReference<Boolean> newValue);
|
||||
void TerminalCoreColorBindBack(Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> newValue);
|
||||
void WindowsUIColorBindBack(Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> newValue);
|
||||
|
||||
void Browse_Click(IInspectable sender, Windows.UI.Xaml.RoutedEventArgs args);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> ColorSchemeRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> ColorSchemeNamesRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> WindowRootRequested;
|
||||
}
|
||||
|
||||
runtimeclass ActionArgsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
Boolean HasArgs { get; };
|
||||
IObservableVector<ArgWrapper> ArgValues;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WrapperValueChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> PropagateColorSchemeRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> PropagateColorSchemeNamesRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> PropagateWindowRootRequested;
|
||||
}
|
||||
|
||||
runtimeclass KeyChordViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
String KeyChordText { get; };
|
||||
|
||||
// UI side
|
||||
Boolean ShowEditButton { get; };
|
||||
Boolean IsInEditMode { get; };
|
||||
Boolean IsNewlyAdded { get; };
|
||||
Microsoft.Terminal.Control.KeyChord ProposedKeys;
|
||||
Object ProposedAction;
|
||||
Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout;
|
||||
String EditButtonName { get; };
|
||||
String CancelButtonName { get; };
|
||||
String AcceptButtonName { get; };
|
||||
String DeleteButtonName { get; };
|
||||
Windows.UI.Xaml.Media.Brush ContainerBackground { get; };
|
||||
|
||||
void EnterHoverMode();
|
||||
void ExitHoverMode();
|
||||
void ActionGotFocus();
|
||||
void ActionLostFocus();
|
||||
void EditButtonGettingFocus();
|
||||
void EditButtonLosingFocus();
|
||||
IObservableVector<String> AvailableActions { get; };
|
||||
Boolean IsInEditMode { get; };
|
||||
void ToggleEditMode();
|
||||
void AttemptAcceptChanges();
|
||||
void CancelChanges();
|
||||
void DeleteKeyBinding();
|
||||
void DeleteKeyChord();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<KeyBindingViewModel, ModifyKeyBindingEventArgs> ModifyKeyBindingRequested;
|
||||
event Windows.Foundation.TypedEventHandler<KeyBindingViewModel, Microsoft.Terminal.Control.KeyChord> DeleteKeyBindingRequested;
|
||||
event Windows.Foundation.TypedEventHandler<KeyChordViewModel, Microsoft.Terminal.Control.KeyChord> AddKeyChordRequested;
|
||||
event Windows.Foundation.TypedEventHandler<KeyChordViewModel, ModifyKeyChordEventArgs> ModifyKeyChordRequested;
|
||||
event Windows.Foundation.TypedEventHandler<KeyChordViewModel, Microsoft.Terminal.Control.KeyChord> DeleteKeyChordRequested;
|
||||
}
|
||||
|
||||
enum ActionsSubPage
|
||||
{
|
||||
Base = 0,
|
||||
Edit = 1
|
||||
};
|
||||
|
||||
runtimeclass ActionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
ActionsViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
|
||||
void OnAutomationPeerAttached();
|
||||
void AddNewKeybinding();
|
||||
void AddNewCommand();
|
||||
|
||||
IObservableVector<KeyBindingViewModel> KeyBindingList { get; };
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> FocusContainer;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> UpdateBackground;
|
||||
ActionsSubPage CurrentPage;
|
||||
CommandViewModel CurrentCommand;
|
||||
|
||||
void AttemptAddOrModifyKeyChord(KeyChordViewModel senderVM, String commandID, Microsoft.Terminal.Control.KeyChord newKeys, Microsoft.Terminal.Control.KeyChord oldKeys);
|
||||
void AttemptDeleteKeyChord(Microsoft.Terminal.Control.KeyChord keys);
|
||||
void AttemptAddCopiedCommand(Microsoft.Terminal.Settings.Model.Command newCommand);
|
||||
|
||||
IObservableVector<CommandViewModel> CommandList { get; };
|
||||
void CmdListItemClicked(IInspectable sender, Windows.UI.Xaml.Controls.ItemClickEventArgs args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<IconSourceElement Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon), Mode=OneTime}" />
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(EvaluatedIcon), Mode=OneTime}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{x:Bind Name}" />
|
||||
|
||||
117
src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp
Normal file
117
src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ArgsTemplateSelectors.h"
|
||||
#include "ArgsTemplateSelectors.g.cpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
Windows::UI::Xaml::DataTemplate ArgsTemplateSelectors::SelectTemplateCore(const winrt::Windows::Foundation::IInspectable& item, const winrt::Windows::UI::Xaml::DependencyObject& /*container*/)
|
||||
{
|
||||
return SelectTemplateCore(item);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method is called once command palette decides how to render a filtered command.
|
||||
// Currently we support two ways to render command, that depend on its palette item type:
|
||||
// - For TabPalette item we render an icon, a title, and some tab-related indicators like progress bar (as defined by TabItemTemplate)
|
||||
// - All other items are currently rendered with icon, title and optional key-chord (as defined by GeneralItemTemplate)
|
||||
// Arguments:
|
||||
// - item - an instance of filtered command to render
|
||||
// Return Value:
|
||||
// - data template to use for rendering
|
||||
Windows::UI::Xaml::DataTemplate ArgsTemplateSelectors::SelectTemplateCore(const winrt::Windows::Foundation::IInspectable& item)
|
||||
{
|
||||
if (const auto argWrapper{ item.try_as<Microsoft::Terminal::Settings::Editor::ArgWrapper>() })
|
||||
{
|
||||
const auto argType = argWrapper.Type();
|
||||
if (argType == L"winrt::hstring")
|
||||
{
|
||||
// string has some special cases - check the tag
|
||||
const auto argTag = argWrapper.Tag();
|
||||
if (argTag == Model::ArgTag::ColorScheme)
|
||||
{
|
||||
return ColorSchemeTemplate();
|
||||
}
|
||||
else if (argTag == Model::ArgTag::FilePath)
|
||||
{
|
||||
return FilePickerTemplate();
|
||||
}
|
||||
|
||||
// no special handling required, just return the normal string template
|
||||
return StringTemplate();
|
||||
}
|
||||
else if (argType == L"winrt::guid")
|
||||
{
|
||||
return GuidTemplate();
|
||||
}
|
||||
else if (argType == L"int32_t")
|
||||
{
|
||||
return Int32Template();
|
||||
}
|
||||
else if (argType == L"uint32_t")
|
||||
{
|
||||
return UInt32Template();
|
||||
}
|
||||
else if (argType == L"uint64_t")
|
||||
{
|
||||
return UInt64Template();
|
||||
}
|
||||
else if (argType == L"float")
|
||||
{
|
||||
return FloatTemplate();
|
||||
}
|
||||
else if (argType == L"bool")
|
||||
{
|
||||
return BoolTemplate();
|
||||
}
|
||||
else if (argType == L"Windows::Foundation::IReference<bool>")
|
||||
{
|
||||
return BoolOptionalTemplate();
|
||||
}
|
||||
else if (argType == L"Windows::Foundation::IReference<int32_t>")
|
||||
{
|
||||
return Int32OptionalTemplate();
|
||||
}
|
||||
else if (argType == L"Windows::Foundation::IReference<uint32_t>")
|
||||
{
|
||||
return UInt32OptionalTemplate();
|
||||
}
|
||||
else if (argType == L"Model::ResizeDirection" ||
|
||||
argType == L"Model::FocusDirection" ||
|
||||
argType == L"SettingsTarget" ||
|
||||
argType == L"MoveTabDirection" ||
|
||||
argType == L"Microsoft::Terminal::Control::ScrollToMarkDirection" ||
|
||||
argType == L"CommandPaletteLaunchMode" ||
|
||||
argType == L"FindMatchDirection" ||
|
||||
argType == L"Model::DesktopBehavior" ||
|
||||
argType == L"Model::MonitorBehavior" ||
|
||||
argType == L"winrt::Microsoft::Terminal::Control::ClearBufferType" ||
|
||||
argType == L"SelectOutputDirection" ||
|
||||
argType == L"Windows::Foundation::IReference<TabSwitcherMode>" ||
|
||||
argType == L"Model::SplitDirection" ||
|
||||
argType == L"SplitType")
|
||||
{
|
||||
return EnumTemplate();
|
||||
}
|
||||
else if (argType == L"SuggestionsSource")
|
||||
{
|
||||
return FlagTemplate();
|
||||
}
|
||||
else if (argType == L"Windows::Foundation::IReference<Microsoft::Terminal::Core::Color>")
|
||||
{
|
||||
return TerminalCoreColorOptionalTemplate();
|
||||
}
|
||||
else if (argType == L"Windows::Foundation::IReference<Windows::UI::Color>")
|
||||
{
|
||||
return WindowsUIColorOptionalTemplate();
|
||||
}
|
||||
else if (argType == L"Windows::Foundation::IReference<Control::CopyFormat>")
|
||||
{
|
||||
return FlagTemplate();
|
||||
}
|
||||
}
|
||||
return NoArgTemplate();
|
||||
}
|
||||
}
|
||||
40
src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h
Normal file
40
src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ArgsTemplateSelectors.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct ArgsTemplateSelectors : ArgsTemplateSelectorsT<ArgsTemplateSelectors>
|
||||
{
|
||||
ArgsTemplateSelectors() = default;
|
||||
|
||||
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::DependencyObject&);
|
||||
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const winrt::Windows::Foundation::IInspectable&);
|
||||
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, NoArgTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, GuidTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, Int32Template);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, Int32OptionalTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, UInt32Template);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, UInt32OptionalTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, UInt64Template);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FloatTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, StringTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, ColorSchemeTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FilePickerTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, BoolTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, BoolOptionalTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, EnumTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FlagTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, TerminalCoreColorOptionalTemplate);
|
||||
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, WindowsUIColorOptionalTemplate);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ArgsTemplateSelectors);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
[default_interface] runtimeclass ArgsTemplateSelectors : Windows.UI.Xaml.Controls.DataTemplateSelector
|
||||
{
|
||||
ArgsTemplateSelectors();
|
||||
|
||||
Windows.UI.Xaml.DataTemplate NoArgTemplate;
|
||||
Windows.UI.Xaml.DataTemplate GuidTemplate;
|
||||
Windows.UI.Xaml.DataTemplate Int32Template;
|
||||
Windows.UI.Xaml.DataTemplate Int32OptionalTemplate;
|
||||
Windows.UI.Xaml.DataTemplate UInt32Template;
|
||||
Windows.UI.Xaml.DataTemplate UInt32OptionalTemplate;
|
||||
Windows.UI.Xaml.DataTemplate UInt64Template;
|
||||
Windows.UI.Xaml.DataTemplate FloatTemplate;
|
||||
Windows.UI.Xaml.DataTemplate StringTemplate;
|
||||
Windows.UI.Xaml.DataTemplate ColorSchemeTemplate;
|
||||
Windows.UI.Xaml.DataTemplate FilePickerTemplate;
|
||||
Windows.UI.Xaml.DataTemplate BoolTemplate;
|
||||
Windows.UI.Xaml.DataTemplate BoolOptionalTemplate;
|
||||
Windows.UI.Xaml.DataTemplate EnumTemplate;
|
||||
Windows.UI.Xaml.DataTemplate FlagTemplate;
|
||||
Windows.UI.Xaml.DataTemplate TerminalCoreColorOptionalTemplate;
|
||||
Windows.UI.Xaml.DataTemplate WindowsUIColorOptionalTemplate;
|
||||
}
|
||||
}
|
||||
@@ -1204,7 +1204,7 @@
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
@@ -1227,7 +1227,8 @@
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}" />
|
||||
<FontIcon Margin="20,0,8,0"
|
||||
<FontIcon Grid.Column="1"
|
||||
Margin="20,0,8,0"
|
||||
HorizontalAlignment="Right"
|
||||
FontSize="10"
|
||||
FontWeight="Black"
|
||||
@@ -1271,4 +1272,24 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="NewInfoBadge"
|
||||
TargetType="muxc:InfoBadge">
|
||||
<Setter Property="Padding" Value="5,1,5,2" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="muxc:InfoBadge">
|
||||
<Border x:Name="RootGrid"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.InfoBadgeCornerRadius}">
|
||||
<TextBlock x:Uid="NewInfoBadgeTextBlock"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
||||
56
src/cascadia/TerminalSettingsEditor/EditAction.cpp
Normal file
56
src/cascadia/TerminalSettingsEditor/EditAction.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "EditAction.h"
|
||||
#include "EditAction.g.cpp"
|
||||
#include "LibraryResources.h"
|
||||
#include "../TerminalSettingsModel/AllShortcutActions.h"
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Navigation;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
EditAction::EditAction()
|
||||
{
|
||||
InitializeComponent();
|
||||
_itemTemplateSelector = Resources().Lookup(winrt::box_value(L"ArgsTemplateSelector")).try_as<ArgsTemplateSelectors>();
|
||||
_listItemTemplate = Resources().Lookup(winrt::box_value(L"ListItemTemplate")).try_as<DataTemplate>();
|
||||
}
|
||||
|
||||
void EditAction::OnNavigatedTo(const NavigationEventArgs& e)
|
||||
{
|
||||
const auto args = e.Parameter().as<Editor::NavigateToCommandArgs>();
|
||||
_ViewModel = args.Command();
|
||||
_windowRoot = args.WindowRoot();
|
||||
_ViewModel.PropagateWindowRootRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) {
|
||||
if (auto weak = weakThis.get())
|
||||
{
|
||||
if (wrapper)
|
||||
{
|
||||
wrapper.WindowRoot(weak->_windowRoot);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EditAction::_choosingItemContainer(
|
||||
const Windows::UI::Xaml::Controls::ListViewBase& /*sender*/,
|
||||
const Windows::UI::Xaml::Controls::ChoosingItemContainerEventArgs& args)
|
||||
{
|
||||
const auto dataTemplate = _itemTemplateSelector.SelectTemplate(args.Item());
|
||||
const auto itemContainer = args.ItemContainer();
|
||||
|
||||
if (!itemContainer || itemContainer.ContentTemplate() != dataTemplate)
|
||||
{
|
||||
ElementFactoryGetArgs factoryArgs{};
|
||||
const auto listViewItem = _listItemTemplate.GetElement(factoryArgs).try_as<Controls::ListViewItem>();
|
||||
listViewItem.ContentTemplate(dataTemplate);
|
||||
|
||||
args.ItemContainer(listViewItem);
|
||||
}
|
||||
|
||||
args.IsContainerPrepared(true);
|
||||
}
|
||||
}
|
||||
35
src/cascadia/TerminalSettingsEditor/EditAction.h
Normal file
35
src/cascadia/TerminalSettingsEditor/EditAction.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "EditAction.g.h"
|
||||
#include "ActionsViewModel.h"
|
||||
#include "Utils.h"
|
||||
#include "ViewModelHelpers.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct EditAction : public HasScrollViewer<EditAction>, EditActionT<EditAction>
|
||||
{
|
||||
public:
|
||||
EditAction();
|
||||
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
|
||||
WINRT_OBSERVABLE_PROPERTY(Editor::CommandViewModel, ViewModel, PropertyChanged.raise, nullptr);
|
||||
|
||||
private:
|
||||
friend struct EditActionT<EditAction>; // for Xaml to bind events
|
||||
Editor::IHostedInWindow _windowRoot;
|
||||
Windows::UI::Xaml::DataTemplate _listItemTemplate;
|
||||
winrt::Microsoft::Terminal::Settings::Editor::ArgsTemplateSelectors _itemTemplateSelector{ nullptr };
|
||||
void _choosingItemContainer(const Windows::UI::Xaml::Controls::ListViewBase& sender, const Windows::UI::Xaml::Controls::ChoosingItemContainerEventArgs& args);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(EditAction);
|
||||
}
|
||||
13
src/cascadia/TerminalSettingsEditor/EditAction.idl
Normal file
13
src/cascadia/TerminalSettingsEditor/EditAction.idl
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ActionsViewModel.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
[default_interface] runtimeclass EditAction : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
EditAction();
|
||||
CommandViewModel ViewModel { get; };
|
||||
}
|
||||
}
|
||||
703
src/cascadia/TerminalSettingsEditor/EditAction.xaml
Normal file
703
src/cascadia/TerminalSettingsEditor/EditAction.xaml
Normal file
@@ -0,0 +1,703 @@
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<Page x:Class="Microsoft.Terminal.Settings.Editor.EditAction"
|
||||
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:Microsoft.Terminal.Settings.Editor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="CommonResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Theme Dictionary -->
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<!-- KeyChordText styles -->
|
||||
<Style x:Key="KeyChordBorderStyle"
|
||||
TargetType="Button">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="1" />
|
||||
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
<!-- Override visual states -->
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Grid>
|
||||
<!-- Define the appearance of the button -->
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="border"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SystemControlHighlightAccentRevealBackgroundBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed" />
|
||||
<VisualState x:Name="Disabled" />
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="KeyChordTextBlockStyle"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<!-- KeyChordText styles -->
|
||||
<Style x:Key="KeyChordBorderStyle"
|
||||
TargetType="Button">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="1" />
|
||||
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
<!-- Override visual states -->
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Grid>
|
||||
<!-- Define the appearance of the button -->
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="border"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SystemControlHighlightAccentRevealBackgroundBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed" />
|
||||
<VisualState x:Name="Disabled" />
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="KeyChordTextBlockStyle"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
|
||||
<Style x:Key="KeyChordBorderStyle"
|
||||
TargetType="Button" />
|
||||
<Style x:Key="KeyChordTextBlockStyle"
|
||||
TargetType="TextBlock" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<GridLength x:Key="ArgumentNameWidth">148</GridLength>
|
||||
|
||||
<!-- Styles -->
|
||||
<Style x:Key="KeyBindingContainerStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="4" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="XYFocusKeyboardNavigation" Value="Enabled" />
|
||||
</Style>
|
||||
<Style x:Key="KeyChordEditorStyle"
|
||||
TargetType="local:KeyChordListener">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<x:Int32 x:Key="EditButtonSize">32</x:Int32>
|
||||
<x:Double x:Key="EditButtonIconSize">15</x:Double>
|
||||
<Style x:Key="EditButtonStyle"
|
||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Height" Value="{StaticResource EditButtonSize}" />
|
||||
<Setter Property="Width" Value="{StaticResource EditButtonSize}" />
|
||||
</Style>
|
||||
<Style x:Key="AccentEditButtonStyle"
|
||||
BasedOn="{StaticResource AccentButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Padding" Value="3" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Height" Value="{StaticResource EditButtonSize}" />
|
||||
<Setter Property="Width" Value="{StaticResource EditButtonSize}" />
|
||||
</Style>
|
||||
|
||||
<!-- Templates -->
|
||||
<DataTemplate x:Key="KeyChordTemplate"
|
||||
x:DataType="local:KeyChordViewModel">
|
||||
<ListViewItem Style="{StaticResource KeyBindingContainerStyle}">
|
||||
<Grid Padding="2,0,2,0"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0"
|
||||
Click="{x:Bind ToggleEditMode}"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}">
|
||||
<TextBlock FontSize="14"
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</Button>
|
||||
<Grid Grid.Column="0"
|
||||
ColumnSpacing="8"
|
||||
Visibility="{x:Bind IsInEditMode, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Edit Mode: Key Chord Listener -->
|
||||
<local:KeyChordListener Grid.Column="0"
|
||||
Keys="{x:Bind ProposedKeys, Mode=TwoWay}"
|
||||
Style="{StaticResource KeyChordEditorStyle}" />
|
||||
|
||||
<!-- Cancel editing the action -->
|
||||
<Button x:Uid="Actions_CancelButton"
|
||||
Grid.Column="1"
|
||||
Click="{x:Bind CancelChanges}"
|
||||
Style="{StaticResource EditButtonStyle}">
|
||||
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
|
||||
<!-- Accept changes -->
|
||||
<Button x:Uid="Actions_AcceptButton"
|
||||
Grid.Column="2"
|
||||
Click="{x:Bind AttemptAcceptChanges}"
|
||||
Flyout="{x:Bind AcceptChangesFlyout, Mode=OneWay}"
|
||||
Style="{StaticResource AccentEditButtonStyle}">
|
||||
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Button Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
Style="{StaticResource DeleteSmallButtonStyle}">
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
|
||||
Glyph="" />
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="Actions_DeleteConfirmationMessage"
|
||||
Style="{StaticResource CustomFlyoutTextStyle}" />
|
||||
<Button x:Uid="Actions_DeleteConfirmationButton"
|
||||
Click="{x:Bind DeleteKeyChord}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ListItemTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem HorizontalContentAlignment="Stretch" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="NoArgTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="Int32Template"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<muxc:NumberBox Grid.Column="1"
|
||||
LargeChange="1"
|
||||
Maximum="999"
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
Style="{StaticResource NumberBoxSettingStyle}"
|
||||
Value="{x:Bind UnboxInt32(Value), Mode=TwoWay, BindBack=Int32BindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="UInt32Template"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<muxc:NumberBox Grid.Column="1"
|
||||
LargeChange="1"
|
||||
Maximum="999"
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
Style="{StaticResource NumberBoxSettingStyle}"
|
||||
Value="{x:Bind UnboxUInt32(Value), Mode=TwoWay, BindBack=UInt32BindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="UInt32OptionalTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<muxc:NumberBox Grid.Column="1"
|
||||
LargeChange="1"
|
||||
Maximum="999"
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
Style="{StaticResource NumberBoxSettingStyle}"
|
||||
Value="{x:Bind UnboxUInt32Optional(Value), Mode=TwoWay, BindBack=UInt32OptionalBindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="UInt64Template"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<muxc:NumberBox Grid.Column="1"
|
||||
LargeChange="1"
|
||||
Maximum="999"
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
Style="{StaticResource NumberBoxSettingStyle}"
|
||||
Value="{x:Bind UnboxUInt64(Value), Mode=TwoWay, BindBack=UInt64BindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="Int32OptionalTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<muxc:NumberBox Grid.Column="1"
|
||||
LargeChange="1"
|
||||
Maximum="999"
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
Style="{StaticResource NumberBoxSettingStyle}"
|
||||
Value="{x:Bind UnboxInt32Optional(Value), Mode=TwoWay, BindBack=Int32OptionalBindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="FloatTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<muxc:NumberBox Grid.Column="1"
|
||||
LargeChange="1"
|
||||
Maximum="999"
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
Style="{StaticResource NumberBoxSettingStyle}"
|
||||
Value="{x:Bind UnboxFloat(Value), Mode=TwoWay, BindBack=FloatBindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="StringTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{x:Bind UnboxString(Value), Mode=TwoWay, BindBack=StringBindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ColorSchemeTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<ComboBox Grid.Column="1"
|
||||
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
|
||||
ItemsSource="{x:Bind EnumList, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind EnumValue, Mode=TwoWay}"
|
||||
Style="{StaticResource ComboBoxSettingStyle}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="FilePickerTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{x:Bind UnboxString(Value), Mode=TwoWay, BindBack=StringBindBack}" />
|
||||
<Button x:Uid="Actions_Browse"
|
||||
Grid.Column="2"
|
||||
Click="{x:Bind Browse_Click}"
|
||||
Style="{StaticResource BrowseButtonStyle}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="GuidTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{x:Bind UnboxGuid(Value), Mode=TwoWay, BindBack=GuidBindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="BoolTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<ToggleSwitch Grid.Column="1"
|
||||
IsOn="{x:Bind UnboxBool(Value), Mode=TwoWay, BindBack=BoolOptionalBindBack}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="BoolOptionalTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<CheckBox Grid.Column="1"
|
||||
IsChecked="{x:Bind UnboxBoolOptional(Value), Mode=TwoWay, BindBack=BoolOptionalBindBack}"
|
||||
IsThreeState="True" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="EnumComboBoxTemplate"
|
||||
x:DataType="local:EnumEntry">
|
||||
<TextBlock Text="{x:Bind EnumName, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="EnumTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<ComboBox Grid.Column="1"
|
||||
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
|
||||
ItemsSource="{x:Bind EnumList, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind EnumValue, Mode=TwoWay}"
|
||||
Style="{StaticResource ComboBoxSettingStyle}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="FlagItemTemplate"
|
||||
x:DataType="local:FlagEntry">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox Grid.Column="0"
|
||||
IsChecked="{x:Bind IsSet, Mode=TwoWay}" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Padding="0,0,0,4"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind FlagName, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="FlagTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<ListView Grid.Column="1"
|
||||
Margin="0"
|
||||
Padding="-8,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
ItemTemplate="{StaticResource FlagItemTemplate}"
|
||||
ItemsSource="{x:Bind FlagList, Mode=OneWay}"
|
||||
SelectionMode="None" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="TerminalCoreColorOptionalTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<local:NullableColorPicker x:Uid="Actions_NullableColorPicker"
|
||||
Grid.Column="1"
|
||||
ColorSchemeVM="{x:Bind DefaultColorScheme, Mode=OneWay}"
|
||||
CurrentColor="{x:Bind UnboxTerminalCoreColorOptional(Value), Mode=TwoWay, BindBack=TerminalCoreColorBindBack}"
|
||||
NullColorPreview="{x:Bind DefaultColorScheme.ForegroundColor.Color, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="WindowsUIColorOptionalTemplate"
|
||||
x:DataType="local:ArgWrapper">
|
||||
<ListViewItem>
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
<local:NullableColorPicker x:Uid="Actions_NullableColorPicker"
|
||||
Grid.Column="1"
|
||||
ColorSchemeVM="{x:Bind DefaultColorScheme, Mode=OneWay}"
|
||||
CurrentColor="{x:Bind UnboxWindowsUIColorOptional(Value), Mode=TwoWay, BindBack=WindowsUIColorBindBack}"
|
||||
NullColorPreview="{x:Bind DefaultColorScheme.ForegroundColor.Color, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<local:ArgsTemplateSelectors x:Key="ArgsTemplateSelector"
|
||||
BoolOptionalTemplate="{StaticResource BoolOptionalTemplate}"
|
||||
BoolTemplate="{StaticResource BoolTemplate}"
|
||||
ColorSchemeTemplate="{StaticResource ColorSchemeTemplate}"
|
||||
EnumTemplate="{StaticResource EnumTemplate}"
|
||||
FilePickerTemplate="{StaticResource FilePickerTemplate}"
|
||||
FlagTemplate="{StaticResource FlagTemplate}"
|
||||
FloatTemplate="{StaticResource FloatTemplate}"
|
||||
GuidTemplate="{StaticResource GuidTemplate}"
|
||||
Int32OptionalTemplate="{StaticResource Int32OptionalTemplate}"
|
||||
Int32Template="{StaticResource Int32Template}"
|
||||
NoArgTemplate="{StaticResource NoArgTemplate}"
|
||||
StringTemplate="{StaticResource StringTemplate}"
|
||||
TerminalCoreColorOptionalTemplate="{StaticResource TerminalCoreColorOptionalTemplate}"
|
||||
UInt32OptionalTemplate="{StaticResource UInt32OptionalTemplate}"
|
||||
UInt32Template="{StaticResource UInt32Template}"
|
||||
UInt64Template="{StaticResource UInt64Template}"
|
||||
WindowsUIColorOptionalTemplate="{StaticResource WindowsUIColorOptionalTemplate}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
</Page.Resources>
|
||||
|
||||
<Border MaxWidth="{StaticResource StandardControlMaxWidth}"
|
||||
Margin="{StaticResource SettingStackMargin}">
|
||||
<Grid MaxWidth="600"
|
||||
Margin="{StaticResource SettingStackMargin}"
|
||||
HorizontalAlignment="Left"
|
||||
ColumnSpacing="16"
|
||||
RowSpacing="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="Actions_Name"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
HorizontalAlignment="Left"
|
||||
PlaceholderText="{x:Bind ViewModel.DisplayName, Mode=OneWay}"
|
||||
Text="{x:Bind ViewModel.Name, Mode=TwoWay}" />
|
||||
<TextBlock x:Uid="Actions_ShortcutAction"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind ViewModel.AvailableShortcutActions, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.ProposedShortcutAction, Mode=TwoWay}" />
|
||||
<TextBlock x:Uid="Actions_Arguments"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{x:Bind ViewModel.ActionArgsVM.HasArgs, Mode=OneWay}" />
|
||||
<ListView Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="-28,0,0,0"
|
||||
AllowDrop="False"
|
||||
CanDragItems="False"
|
||||
CanReorderItems="False"
|
||||
ChoosingItemContainer="_choosingItemContainer"
|
||||
ItemsSource="{x:Bind ViewModel.ActionArgsVM.ArgValues, Mode=OneWay}"
|
||||
SelectionMode="None" />
|
||||
<TextBlock x:Uid="Actions_Keybindings"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center" />
|
||||
<ListView Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
ItemTemplate="{StaticResource KeyChordTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.KeyChordViewModelList, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.Header>
|
||||
<Button Click="{x:Bind ViewModel.AddKeybinding_Click}">
|
||||
<TextBlock x:Uid="Actions_AddKeyChord" />
|
||||
</Button>
|
||||
</ListView.Header>
|
||||
</ListView>
|
||||
<Button Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
IsEnabled="{x:Bind ViewModel.IsUserAction, Mode=OneWay}"
|
||||
Style="{StaticResource DeleteButtonStyle}">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="Actions_DeleteButton2"
|
||||
Style="{StaticResource IconButtonTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="Actions_CommandDeleteConfirmationMessage"
|
||||
Style="{StaticResource CustomFlyoutTextStyle}" />
|
||||
<Button x:Uid="Actions_CommandDeleteConfirmationButton"
|
||||
Click="{x:Bind ViewModel.Delete_Click}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Page>
|
||||
@@ -17,6 +17,7 @@ Author(s):
|
||||
#pragma once
|
||||
|
||||
#include "EnumEntry.g.h"
|
||||
#include "FlagEntry.g.h"
|
||||
#include "Utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
@@ -55,4 +56,41 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, EnumName, PropertyChanged.raise);
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::IInspectable, EnumValue, PropertyChanged.raise);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct FlagEntryComparator
|
||||
{
|
||||
bool operator()(const Editor::FlagEntry& lhs, const Editor::FlagEntry& rhs) const
|
||||
{
|
||||
return lhs.FlagValue().as<T>() < rhs.FlagValue().as<T>();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct FlagEntryReverseComparator
|
||||
{
|
||||
bool operator()(const Editor::FlagEntry& lhs, const Editor::FlagEntry& rhs) const
|
||||
{
|
||||
return lhs.FlagValue().as<T>() > rhs.FlagValue().as<T>();
|
||||
}
|
||||
};
|
||||
|
||||
struct FlagEntry : FlagEntryT<FlagEntry>
|
||||
{
|
||||
public:
|
||||
FlagEntry(const winrt::hstring flagName, const winrt::Windows::Foundation::IInspectable& flagValue, const bool isSet) :
|
||||
_FlagName{ flagName },
|
||||
_FlagValue{ flagValue },
|
||||
_IsSet{ isSet } {}
|
||||
|
||||
hstring ToString()
|
||||
{
|
||||
return FlagName();
|
||||
}
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, FlagName, PropertyChanged.raise);
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::IInspectable, FlagValue, PropertyChanged.raise);
|
||||
WINRT_OBSERVABLE_PROPERTY(bool, IsSet, PropertyChanged.raise);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,4 +8,11 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
String EnumName { get; };
|
||||
IInspectable EnumValue { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FlagEntry : Windows.UI.Xaml.Data.INotifyPropertyChanged, Windows.Foundation.IStringable
|
||||
{
|
||||
String FlagName { get; };
|
||||
IInspectable FlagValue { get; };
|
||||
Boolean IsSet;
|
||||
}
|
||||
}
|
||||
|
||||
509
src/cascadia/TerminalSettingsEditor/Extensions.cpp
Normal file
509
src/cascadia/TerminalSettingsEditor/Extensions.cpp
Normal file
@@ -0,0 +1,509 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Extensions.h"
|
||||
#include "Extensions.g.cpp"
|
||||
#include "ExtensionPackageViewModel.g.cpp"
|
||||
#include "ExtensionsViewModel.g.cpp"
|
||||
#include "FragmentProfileViewModel.g.cpp"
|
||||
#include "ExtensionPackageTemplateSelector.g.cpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
#include "..\WinRTUtils\inc\Utils.h"
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace winrt::Windows::UI::Xaml::Navigation;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
static constexpr std::wstring_view ExtensionPageId{ L"page.extensions" };
|
||||
|
||||
Extensions::Extensions()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_extensionPackageIdentifierTemplateSelector = Resources().Lookup(box_value(L"ExtensionPackageIdentifierTemplateSelector")).as<Editor::ExtensionPackageTemplateSelector>();
|
||||
|
||||
Automation::AutomationProperties::SetName(ActiveExtensionsList(), RS_(L"Extensions_ActiveExtensionsHeader/Text"));
|
||||
Automation::AutomationProperties::SetName(ModifiedProfilesList(), RS_(L"Extensions_ModifiedProfilesHeader/Text"));
|
||||
Automation::AutomationProperties::SetName(AddedProfilesList(), RS_(L"Extensions_AddedProfilesHeader/Text"));
|
||||
Automation::AutomationProperties::SetName(AddedColorSchemesList(), RS_(L"Extensions_AddedColorSchemesHeader/Text"));
|
||||
}
|
||||
|
||||
void Extensions::OnNavigatedTo(const NavigationEventArgs& e)
|
||||
{
|
||||
_ViewModel = e.Parameter().as<Editor::ExtensionsViewModel>();
|
||||
auto vmImpl = get_self<ExtensionsViewModel>(_ViewModel);
|
||||
vmImpl->ExtensionPackageIdentifierTemplateSelector(_extensionPackageIdentifierTemplateSelector);
|
||||
vmImpl->LazyLoadExtensions();
|
||||
vmImpl->MarkAsVisited();
|
||||
}
|
||||
|
||||
void Extensions::ExtensionNavigator_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
|
||||
{
|
||||
const auto extPkgVM = sender.as<Controls::Button>().Tag().as<Editor::ExtensionPackageViewModel>();
|
||||
_ViewModel.CurrentExtensionPackage(extPkgVM);
|
||||
}
|
||||
|
||||
void Extensions::NavigateToProfile_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
|
||||
{
|
||||
const auto& profileGuid = sender.as<Controls::Button>().Tag().as<guid>();
|
||||
get_self<ExtensionsViewModel>(_ViewModel)->NavigateToProfile(profileGuid);
|
||||
}
|
||||
|
||||
void Extensions::NavigateToColorScheme_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
|
||||
{
|
||||
const auto& schemeVM = sender.as<Controls::Button>().Tag().as<Editor::ColorSchemeViewModel>();
|
||||
get_self<ExtensionsViewModel>(_ViewModel)->NavigateToColorScheme(schemeVM);
|
||||
}
|
||||
|
||||
ExtensionsViewModel::ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM) :
|
||||
_settings{ settings },
|
||||
_colorSchemesPageVM{ colorSchemesPageVM },
|
||||
_extensionsLoaded{ false }
|
||||
{
|
||||
UpdateSettings(settings, colorSchemesPageVM);
|
||||
|
||||
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto viewModelProperty{ args.PropertyName() };
|
||||
|
||||
const bool extensionPackageChanged = viewModelProperty == L"CurrentExtensionPackage";
|
||||
const bool profilesModifiedChanged = viewModelProperty == L"ProfilesModified";
|
||||
const bool profilesAddedChanged = viewModelProperty == L"ProfilesAdded";
|
||||
const bool colorSchemesAddedChanged = viewModelProperty == L"ColorSchemesAdded";
|
||||
if (extensionPackageChanged || (!IsExtensionView() && (profilesModifiedChanged || profilesAddedChanged || colorSchemesAddedChanged)))
|
||||
{
|
||||
// Use these booleans to track which of our observable vectors need to be refreshed.
|
||||
// This prevents a full refresh of the UI when enabling/disabling extensions.
|
||||
// If the CurrentExtensionPackage changed, we want to update all components.
|
||||
// Otherwise, just update the ones that we were notified about.
|
||||
const bool updateProfilesModified = extensionPackageChanged || profilesModifiedChanged;
|
||||
const bool updateProfilesAdded = extensionPackageChanged || profilesAddedChanged;
|
||||
const bool updateColorSchemesAdded = extensionPackageChanged || colorSchemesAddedChanged;
|
||||
_UpdateListViews(updateProfilesModified, updateProfilesAdded, updateColorSchemesAdded);
|
||||
|
||||
if (extensionPackageChanged)
|
||||
{
|
||||
_NotifyChanges(L"IsExtensionView", L"CurrentExtensionPackageIdentifierTemplate");
|
||||
}
|
||||
else if (profilesModifiedChanged)
|
||||
{
|
||||
_NotifyChanges(L"NoProfilesModified");
|
||||
}
|
||||
else if (profilesAddedChanged)
|
||||
{
|
||||
_NotifyChanges(L"NoProfilesAdded");
|
||||
}
|
||||
else if (colorSchemesAddedChanged)
|
||||
{
|
||||
_NotifyChanges(L"NoSchemesAdded");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ExtensionsViewModel::_UpdateListViews(bool updateProfilesModified, bool updateProfilesAdded, bool updateColorSchemesAdded)
|
||||
{
|
||||
// STL vectors to track relevant components for extensions to display in UI
|
||||
std::vector<Editor::FragmentProfileViewModel> profilesModifiedTotal;
|
||||
std::vector<Editor::FragmentProfileViewModel> profilesAddedTotal;
|
||||
std::vector<Editor::FragmentColorSchemeViewModel> colorSchemesAddedTotal;
|
||||
|
||||
// Helper lambda to add the contents of an extension package to the current view.
|
||||
auto addPackageContentsToView = [&](const Editor::ExtensionPackageViewModel& extPkg) {
|
||||
auto extPkgVM = get_self<ExtensionPackageViewModel>(extPkg);
|
||||
for (const auto& ext : extPkgVM->FragmentExtensions())
|
||||
{
|
||||
if (updateProfilesModified)
|
||||
{
|
||||
for (const auto& profile : ext.ProfilesModified())
|
||||
{
|
||||
profilesModifiedTotal.push_back(profile);
|
||||
}
|
||||
}
|
||||
if (updateProfilesAdded)
|
||||
{
|
||||
for (const auto& profile : ext.ProfilesAdded())
|
||||
{
|
||||
profilesAddedTotal.push_back(profile);
|
||||
}
|
||||
}
|
||||
if (updateColorSchemesAdded)
|
||||
{
|
||||
for (const auto& scheme : ext.ColorSchemesAdded())
|
||||
{
|
||||
colorSchemesAddedTotal.push_back(scheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Populate the STL vectors that we want to update
|
||||
if (const auto currentExtensionPackage = CurrentExtensionPackage())
|
||||
{
|
||||
// Update all of the views to reflect the current extension package, if one is selected.
|
||||
addPackageContentsToView(currentExtensionPackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only populate the views with components from enabled extensions
|
||||
for (const auto& extPkg : _extensionPackages)
|
||||
{
|
||||
if (extPkg.Enabled())
|
||||
{
|
||||
addPackageContentsToView(extPkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the lists linguistically for nicer presentation.
|
||||
// Update the WinRT lists bound to UI.
|
||||
if (updateProfilesModified)
|
||||
{
|
||||
std::sort(profilesModifiedTotal.begin(), profilesModifiedTotal.end(), FragmentProfileViewModel::SortAscending);
|
||||
_profilesModifiedView = winrt::single_threaded_observable_vector(std::move(profilesModifiedTotal));
|
||||
}
|
||||
if (updateProfilesAdded)
|
||||
{
|
||||
std::sort(profilesAddedTotal.begin(), profilesAddedTotal.end(), FragmentProfileViewModel::SortAscending);
|
||||
_profilesAddedView = winrt::single_threaded_observable_vector(std::move(profilesAddedTotal));
|
||||
}
|
||||
if (updateColorSchemesAdded)
|
||||
{
|
||||
std::sort(colorSchemesAddedTotal.begin(), colorSchemesAddedTotal.end(), FragmentColorSchemeViewModel::SortAscending);
|
||||
_colorSchemesAddedView = winrt::single_threaded_observable_vector(std::move(colorSchemesAddedTotal));
|
||||
}
|
||||
}
|
||||
|
||||
void ExtensionsViewModel::UpdateSettings(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM)
|
||||
{
|
||||
_settings = settings;
|
||||
_colorSchemesPageVM = colorSchemesPageVM;
|
||||
_CurrentExtensionPackage = nullptr;
|
||||
|
||||
// The extension packages may not be loaded yet because we want to wait until we actually navigate to the page to do so.
|
||||
// In that case, omit "updating" them. They'll get the proper references when we lazy load them.
|
||||
if (_extensionPackages)
|
||||
{
|
||||
for (const auto& extPkg : _extensionPackages)
|
||||
{
|
||||
get_self<ExtensionPackageViewModel>(extPkg)->UpdateSettings(_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExtensionsViewModel::LazyLoadExtensions()
|
||||
{
|
||||
if (_extensionsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::vector<Model::ExtensionPackage> extensions = wil::to_vector(_settings.Extensions());
|
||||
|
||||
// these vectors track components all extensions successfully added
|
||||
std::vector<Editor::ExtensionPackageViewModel> extensionPackages;
|
||||
std::vector<Editor::FragmentProfileViewModel> profilesModifiedTotal;
|
||||
std::vector<Editor::FragmentProfileViewModel> profilesAddedTotal;
|
||||
std::vector<Editor::FragmentColorSchemeViewModel> colorSchemesAddedTotal;
|
||||
for (const auto& extPkg : extensions)
|
||||
{
|
||||
auto extPkgVM = winrt::make_self<ExtensionPackageViewModel>(extPkg, _settings);
|
||||
for (const auto& fragExt : extPkg.FragmentsView())
|
||||
{
|
||||
const auto extensionEnabled = GetExtensionState(fragExt.Source(), _settings);
|
||||
|
||||
// these vectors track everything the current extension attempted to bring in
|
||||
std::vector<Editor::FragmentProfileViewModel> currentProfilesModified;
|
||||
std::vector<Editor::FragmentProfileViewModel> currentProfilesAdded;
|
||||
std::vector<Editor::FragmentColorSchemeViewModel> currentColorSchemesAdded;
|
||||
|
||||
if (fragExt.ModifiedProfilesView())
|
||||
{
|
||||
for (const auto&& entry : fragExt.ModifiedProfilesView())
|
||||
{
|
||||
// Ensure entry successfully modifies a profile before creating and registering the object
|
||||
if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid()))
|
||||
{
|
||||
auto vm = winrt::make<FragmentProfileViewModel>(entry, fragExt, deducedProfile);
|
||||
currentProfilesModified.push_back(vm);
|
||||
if (extensionEnabled)
|
||||
{
|
||||
profilesModifiedTotal.push_back(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fragExt.NewProfilesView())
|
||||
{
|
||||
for (const auto&& entry : fragExt.NewProfilesView())
|
||||
{
|
||||
// Ensure entry successfully points to a profile before creating and registering the object.
|
||||
// The profile may have been removed by the user.
|
||||
if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid()))
|
||||
{
|
||||
auto vm = winrt::make<FragmentProfileViewModel>(entry, fragExt, deducedProfile);
|
||||
currentProfilesAdded.push_back(vm);
|
||||
if (extensionEnabled)
|
||||
{
|
||||
profilesAddedTotal.push_back(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fragExt.ColorSchemesView())
|
||||
{
|
||||
for (const auto&& entry : fragExt.ColorSchemesView())
|
||||
{
|
||||
for (const auto& schemeVM : _colorSchemesPageVM.AllColorSchemes())
|
||||
{
|
||||
if (schemeVM.Name() == entry.ColorSchemeName())
|
||||
{
|
||||
auto vm = winrt::make<FragmentColorSchemeViewModel>(entry, fragExt, schemeVM);
|
||||
currentColorSchemesAdded.push_back(vm);
|
||||
if (extensionEnabled)
|
||||
{
|
||||
colorSchemesAddedTotal.push_back(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort the lists linguistically for nicer presentation
|
||||
std::sort(currentProfilesModified.begin(), currentProfilesModified.end(), FragmentProfileViewModel::SortAscending);
|
||||
std::sort(currentProfilesAdded.begin(), currentProfilesAdded.end(), FragmentProfileViewModel::SortAscending);
|
||||
std::sort(currentColorSchemesAdded.begin(), currentColorSchemesAdded.end(), FragmentColorSchemeViewModel::SortAscending);
|
||||
|
||||
extPkgVM->FragmentExtensions().Append(winrt::make<FragmentExtensionViewModel>(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded));
|
||||
extPkgVM->PropertyChanged([&](const IInspectable& sender, const PropertyChangedEventArgs& args) {
|
||||
const auto viewModelProperty{ args.PropertyName() };
|
||||
if (viewModelProperty == L"Enabled")
|
||||
{
|
||||
// If the extension was enabled/disabled,
|
||||
// check if any of its fragments modified profiles, added profiles, or added color schemes.
|
||||
// Only notify what was affected!
|
||||
bool hasModifiedProfiles = false;
|
||||
bool hasAddedProfiles = false;
|
||||
bool hasAddedColorSchemes = false;
|
||||
for (const auto& fragExtVM : sender.as<ExtensionPackageViewModel>()->FragmentExtensions())
|
||||
{
|
||||
const auto profilesModified = fragExtVM.ProfilesModified();
|
||||
const auto profilesAdded = fragExtVM.ProfilesAdded();
|
||||
const auto colorSchemesAdded = fragExtVM.ColorSchemesAdded();
|
||||
hasModifiedProfiles |= profilesModified && profilesModified.Size() > 0;
|
||||
hasAddedProfiles |= profilesAdded && profilesAdded.Size() > 0;
|
||||
hasAddedColorSchemes |= colorSchemesAdded && colorSchemesAdded.Size() > 0;
|
||||
}
|
||||
if (hasModifiedProfiles)
|
||||
{
|
||||
_NotifyChanges(L"ProfilesModified");
|
||||
}
|
||||
if (hasAddedProfiles)
|
||||
{
|
||||
_NotifyChanges(L"ProfilesAdded");
|
||||
}
|
||||
if (hasAddedColorSchemes)
|
||||
{
|
||||
_NotifyChanges(L"ColorSchemesAdded");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
extensionPackages.push_back(*extPkgVM);
|
||||
}
|
||||
|
||||
// sort the lists linguistically for nicer presentation
|
||||
std::sort(extensionPackages.begin(), extensionPackages.end(), ExtensionPackageViewModel::SortAscending);
|
||||
std::sort(profilesModifiedTotal.begin(), profilesModifiedTotal.end(), FragmentProfileViewModel::SortAscending);
|
||||
std::sort(profilesAddedTotal.begin(), profilesAddedTotal.end(), FragmentProfileViewModel::SortAscending);
|
||||
std::sort(colorSchemesAddedTotal.begin(), colorSchemesAddedTotal.end(), FragmentColorSchemeViewModel::SortAscending);
|
||||
|
||||
_extensionPackages = single_threaded_observable_vector<Editor::ExtensionPackageViewModel>(std::move(extensionPackages));
|
||||
_profilesModifiedView = single_threaded_observable_vector<Editor::FragmentProfileViewModel>(std::move(profilesModifiedTotal));
|
||||
_profilesAddedView = single_threaded_observable_vector<Editor::FragmentProfileViewModel>(std::move(profilesAddedTotal));
|
||||
_colorSchemesAddedView = single_threaded_observable_vector<Editor::FragmentColorSchemeViewModel>(std::move(colorSchemesAddedTotal));
|
||||
_extensionsLoaded = true;
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::DataTemplate ExtensionsViewModel::CurrentExtensionPackageIdentifierTemplate() const
|
||||
{
|
||||
return _ExtensionPackageIdentifierTemplateSelector.SelectTemplate(CurrentExtensionPackage());
|
||||
}
|
||||
|
||||
bool ExtensionsViewModel::DisplayBadge() const noexcept
|
||||
{
|
||||
return !Model::ApplicationState::SharedInstance().BadgeDismissed(ExtensionPageId);
|
||||
}
|
||||
|
||||
// Returns true if the extension is enabled, false otherwise
|
||||
bool ExtensionsViewModel::GetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings)
|
||||
{
|
||||
if (const auto& disabledExtensions = settings.GlobalSettings().DisabledProfileSources())
|
||||
{
|
||||
uint32_t ignored;
|
||||
return !disabledExtensions.IndexOf(extensionSource, ignored);
|
||||
}
|
||||
// "disabledProfileSources" not defined --> all extensions are enabled
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enable/Disable an extension
|
||||
void ExtensionsViewModel::SetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings, bool enableExt)
|
||||
{
|
||||
// get the current status of the extension
|
||||
uint32_t idx;
|
||||
bool currentlyEnabled = true;
|
||||
const auto& disabledExtensions = settings.GlobalSettings().DisabledProfileSources();
|
||||
if (disabledExtensions)
|
||||
{
|
||||
currentlyEnabled = !disabledExtensions.IndexOf(extensionSource, idx);
|
||||
}
|
||||
|
||||
// current status mismatches the desired status,
|
||||
// update the list of disabled extensions
|
||||
if (currentlyEnabled != enableExt)
|
||||
{
|
||||
// If we're disabling an extension and we don't have "disabledProfileSources" defined,
|
||||
// create it in the model directly
|
||||
if (!disabledExtensions && !enableExt)
|
||||
{
|
||||
std::vector<hstring> disabledProfileSources{ extensionSource };
|
||||
settings.GlobalSettings().DisabledProfileSources(single_threaded_vector<hstring>(std::move(disabledProfileSources)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the list of disabled extensions
|
||||
if (enableExt)
|
||||
{
|
||||
disabledExtensions.RemoveAt(idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
disabledExtensions.Append(extensionSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thickness Extensions::CalculateMargin(bool hidden)
|
||||
{
|
||||
return ThicknessHelper::FromLengths(/*left*/ 0,
|
||||
/*top*/ hidden ? 0 : 20,
|
||||
/*right*/ 0,
|
||||
/*bottom*/ 0);
|
||||
}
|
||||
|
||||
void ExtensionsViewModel::NavigateToProfile(const guid profileGuid)
|
||||
{
|
||||
NavigateToProfileRequested.raise(*this, profileGuid);
|
||||
}
|
||||
|
||||
void ExtensionsViewModel::NavigateToColorScheme(const Editor::ColorSchemeViewModel& schemeVM)
|
||||
{
|
||||
_colorSchemesPageVM.CurrentScheme(schemeVM);
|
||||
NavigateToColorSchemeRequested.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
void ExtensionsViewModel::MarkAsVisited()
|
||||
{
|
||||
Model::ApplicationState::SharedInstance().DismissBadge(ExtensionPageId);
|
||||
_NotifyChanges(L"DisplayBadge");
|
||||
}
|
||||
|
||||
bool ExtensionPackageViewModel::SortAscending(const Editor::ExtensionPackageViewModel& lhs, const Editor::ExtensionPackageViewModel& rhs)
|
||||
{
|
||||
auto getKey = [&](const Editor::ExtensionPackageViewModel& pkgVM) {
|
||||
const auto pkg = pkgVM.Package();
|
||||
const auto displayName = pkg.DisplayName();
|
||||
return displayName.empty() ? pkg.Source() : displayName;
|
||||
};
|
||||
|
||||
return til::compare_linguistic_insensitive(getKey(lhs), getKey(rhs)) < 0;
|
||||
}
|
||||
|
||||
void ExtensionPackageViewModel::UpdateSettings(const Model::CascadiaSettings& settings)
|
||||
{
|
||||
const auto oldEnabled = Enabled();
|
||||
_settings = settings;
|
||||
if (oldEnabled != Enabled())
|
||||
{
|
||||
// The enabled state of the extension has changed, notify the UI
|
||||
_NotifyChanges(L"Enabled");
|
||||
}
|
||||
}
|
||||
|
||||
hstring ExtensionPackageViewModel::Scope() const noexcept
|
||||
{
|
||||
return _package.Scope() == Model::FragmentScope::User ? RS_(L"Extensions_ScopeUser") : RS_(L"Extensions_ScopeSystem");
|
||||
}
|
||||
|
||||
bool ExtensionPackageViewModel::Enabled() const
|
||||
{
|
||||
return ExtensionsViewModel::GetExtensionState(_package.Source(), _settings);
|
||||
}
|
||||
|
||||
void ExtensionPackageViewModel::Enabled(bool val)
|
||||
{
|
||||
if (Enabled() != val)
|
||||
{
|
||||
ExtensionsViewModel::SetExtensionState(_package.Source(), _settings, val);
|
||||
_NotifyChanges(L"Enabled");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the accessible name for the extension package in the following format:
|
||||
// "<DisplayName?>, <Source>"
|
||||
hstring ExtensionPackageViewModel::AccessibleName() const noexcept
|
||||
{
|
||||
hstring name;
|
||||
const auto source = _package.Source();
|
||||
if (const auto displayName = _package.DisplayName(); !displayName.empty())
|
||||
{
|
||||
return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), displayName, source) };
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
bool FragmentProfileViewModel::SortAscending(const Editor::FragmentProfileViewModel& lhs, const Editor::FragmentProfileViewModel& rhs)
|
||||
{
|
||||
return til::compare_linguistic_insensitive(lhs.Profile().Name(), rhs.Profile().Name()) < 0;
|
||||
}
|
||||
|
||||
hstring FragmentProfileViewModel::AccessibleName() const noexcept
|
||||
{
|
||||
return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), Profile().Name(), SourceName()) };
|
||||
}
|
||||
|
||||
bool FragmentColorSchemeViewModel::SortAscending(const Editor::FragmentColorSchemeViewModel& lhs, const Editor::FragmentColorSchemeViewModel& rhs)
|
||||
{
|
||||
return til::compare_linguistic_insensitive(lhs.ColorSchemeVM().Name(), rhs.ColorSchemeVM().Name()) < 0;
|
||||
}
|
||||
|
||||
hstring FragmentColorSchemeViewModel::AccessibleName() const noexcept
|
||||
{
|
||||
return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), ColorSchemeVM().Name(), SourceName()) };
|
||||
}
|
||||
|
||||
DataTemplate ExtensionPackageTemplateSelector::SelectTemplateCore(const IInspectable& item, const DependencyObject& /*container*/)
|
||||
{
|
||||
return SelectTemplateCore(item);
|
||||
}
|
||||
|
||||
DataTemplate ExtensionPackageTemplateSelector::SelectTemplateCore(const IInspectable& item)
|
||||
{
|
||||
if (const auto extPkgVM = item.try_as<Editor::ExtensionPackageViewModel>())
|
||||
{
|
||||
if (!extPkgVM.Package().DisplayName().empty())
|
||||
{
|
||||
return ComplexTemplate();
|
||||
}
|
||||
return DefaultTemplate();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
193
src/cascadia/TerminalSettingsEditor/Extensions.h
Normal file
193
src/cascadia/TerminalSettingsEditor/Extensions.h
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Extensions.g.h"
|
||||
#include "ExtensionsViewModel.g.h"
|
||||
#include "ExtensionPackageViewModel.g.h"
|
||||
#include "FragmentExtensionViewModel.g.h"
|
||||
#include "FragmentProfileViewModel.g.h"
|
||||
#include "FragmentColorSchemeViewModel.g.h"
|
||||
#include "ExtensionPackageTemplateSelector.g.h"
|
||||
#include "ViewModelHelpers.h"
|
||||
#include "Utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct Extensions : public HasScrollViewer<Extensions>, ExtensionsT<Extensions>
|
||||
{
|
||||
public:
|
||||
Windows::UI::Xaml::Thickness CalculateMargin(bool hidden);
|
||||
|
||||
Extensions();
|
||||
|
||||
void OnNavigatedTo(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
|
||||
|
||||
void ExtensionNavigator_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
|
||||
void NavigateToProfile_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
|
||||
void NavigateToColorScheme_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
|
||||
|
||||
WINRT_PROPERTY(Editor::ExtensionsViewModel, ViewModel, nullptr);
|
||||
|
||||
private:
|
||||
Editor::ExtensionPackageTemplateSelector _extensionPackageIdentifierTemplateSelector;
|
||||
};
|
||||
|
||||
struct ExtensionsViewModel : ExtensionsViewModelT<ExtensionsViewModel>, ViewModelHelper<ExtensionsViewModel>
|
||||
{
|
||||
public:
|
||||
ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM);
|
||||
|
||||
// Properties
|
||||
Windows::UI::Xaml::DataTemplate CurrentExtensionPackageIdentifierTemplate() const;
|
||||
bool IsExtensionView() const noexcept { return _CurrentExtensionPackage != nullptr; }
|
||||
bool NoExtensionPackages() const noexcept { return _extensionPackages.Size() == 0; }
|
||||
bool NoProfilesModified() const noexcept { return _profilesModifiedView.Size() == 0; }
|
||||
bool NoProfilesAdded() const noexcept { return _profilesAddedView.Size() == 0; }
|
||||
bool NoSchemesAdded() const noexcept { return _colorSchemesAddedView.Size() == 0; }
|
||||
bool DisplayBadge() const noexcept;
|
||||
|
||||
// Views
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::ExtensionPackageViewModel> ExtensionPackages() const noexcept { return _extensionPackages; }
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FragmentProfileViewModel> ProfilesModified() const noexcept { return _profilesModifiedView; }
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FragmentProfileViewModel> ProfilesAdded() const noexcept { return _profilesAddedView; }
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FragmentColorSchemeViewModel> ColorSchemesAdded() const noexcept { return _colorSchemesAddedView; }
|
||||
|
||||
// Methods
|
||||
void LazyLoadExtensions();
|
||||
void UpdateSettings(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM);
|
||||
void NavigateToProfile(const guid profileGuid);
|
||||
void NavigateToColorScheme(const Editor::ColorSchemeViewModel& schemeVM);
|
||||
void MarkAsVisited();
|
||||
|
||||
static bool GetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings);
|
||||
static void SetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings, bool enableExt);
|
||||
|
||||
til::typed_event<IInspectable, guid> NavigateToProfileRequested;
|
||||
til::typed_event<IInspectable, Editor::ColorSchemeViewModel> NavigateToColorSchemeRequested;
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ExtensionPackageViewModel, CurrentExtensionPackage, nullptr);
|
||||
WINRT_PROPERTY(Editor::ExtensionPackageTemplateSelector, ExtensionPackageIdentifierTemplateSelector, nullptr);
|
||||
|
||||
private:
|
||||
Model::CascadiaSettings _settings;
|
||||
Editor::ColorSchemesPageViewModel _colorSchemesPageVM;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::ExtensionPackageViewModel> _extensionPackages;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FragmentProfileViewModel> _profilesModifiedView;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FragmentProfileViewModel> _profilesAddedView;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FragmentColorSchemeViewModel> _colorSchemesAddedView;
|
||||
bool _extensionsLoaded;
|
||||
|
||||
void _UpdateListViews(bool updateProfilesModified, bool updateProfilesAdded, bool updateColorSchemesAdded);
|
||||
};
|
||||
|
||||
struct ExtensionPackageViewModel : ExtensionPackageViewModelT<ExtensionPackageViewModel>, ViewModelHelper<ExtensionPackageViewModel>
|
||||
{
|
||||
public:
|
||||
ExtensionPackageViewModel(const Model::ExtensionPackage& pkg, const Model::CascadiaSettings& settings) :
|
||||
_package{ pkg },
|
||||
_settings{ settings },
|
||||
_fragmentExtensions{ single_threaded_observable_vector<Editor::FragmentExtensionViewModel>() } {}
|
||||
|
||||
static bool SortAscending(const Editor::ExtensionPackageViewModel& lhs, const Editor::ExtensionPackageViewModel& rhs);
|
||||
|
||||
void UpdateSettings(const Model::CascadiaSettings& settings);
|
||||
|
||||
Model::ExtensionPackage Package() const noexcept { return _package; }
|
||||
hstring Scope() const noexcept;
|
||||
bool Enabled() const;
|
||||
void Enabled(bool val);
|
||||
hstring AccessibleName() const noexcept;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FragmentExtensionViewModel> FragmentExtensions() { return _fragmentExtensions; }
|
||||
|
||||
private:
|
||||
Model::ExtensionPackage _package;
|
||||
Model::CascadiaSettings _settings;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FragmentExtensionViewModel> _fragmentExtensions;
|
||||
};
|
||||
|
||||
struct FragmentExtensionViewModel : FragmentExtensionViewModelT<FragmentExtensionViewModel>, ViewModelHelper<FragmentExtensionViewModel>
|
||||
{
|
||||
public:
|
||||
FragmentExtensionViewModel(const Model::FragmentSettings& fragment,
|
||||
std::vector<FragmentProfileViewModel>& profilesModified,
|
||||
std::vector<FragmentProfileViewModel>& profilesAdded,
|
||||
std::vector<FragmentColorSchemeViewModel>& colorSchemesAdded) :
|
||||
_fragment{ fragment },
|
||||
_profilesModified{ single_threaded_vector(std::move(profilesModified)) },
|
||||
_profilesAdded{ single_threaded_vector(std::move(profilesAdded)) },
|
||||
_colorSchemesAdded{ single_threaded_vector(std::move(colorSchemesAdded)) } {}
|
||||
|
||||
Model::FragmentSettings Fragment() const noexcept { return _fragment; }
|
||||
Windows::Foundation::Collections::IVectorView<FragmentProfileViewModel> ProfilesModified() const noexcept { return _profilesModified.GetView(); }
|
||||
Windows::Foundation::Collections::IVectorView<FragmentProfileViewModel> ProfilesAdded() const noexcept { return _profilesAdded.GetView(); }
|
||||
Windows::Foundation::Collections::IVectorView<FragmentColorSchemeViewModel> ColorSchemesAdded() const noexcept { return _colorSchemesAdded.GetView(); }
|
||||
|
||||
private:
|
||||
Model::FragmentSettings _fragment;
|
||||
Windows::Foundation::Collections::IVector<FragmentProfileViewModel> _profilesModified;
|
||||
Windows::Foundation::Collections::IVector<FragmentProfileViewModel> _profilesAdded;
|
||||
Windows::Foundation::Collections::IVector<FragmentColorSchemeViewModel> _colorSchemesAdded;
|
||||
};
|
||||
|
||||
struct FragmentProfileViewModel : FragmentProfileViewModelT<FragmentProfileViewModel>, ViewModelHelper<FragmentProfileViewModel>
|
||||
{
|
||||
public:
|
||||
FragmentProfileViewModel(const Model::FragmentProfileEntry& entry, const Model::FragmentSettings& fragment, const Model::Profile& deducedProfile) :
|
||||
_entry{ entry },
|
||||
_fragment{ fragment },
|
||||
_deducedProfile{ deducedProfile } {}
|
||||
|
||||
static bool SortAscending(const Editor::FragmentProfileViewModel& lhs, const Editor::FragmentProfileViewModel& rhs);
|
||||
|
||||
Model::Profile Profile() const { return _deducedProfile; };
|
||||
hstring SourceName() const { return _fragment.Source(); }
|
||||
hstring Json() const { return _entry.Json(); }
|
||||
hstring AccessibleName() const noexcept;
|
||||
|
||||
private:
|
||||
Model::FragmentProfileEntry _entry;
|
||||
Model::FragmentSettings _fragment;
|
||||
Model::Profile _deducedProfile;
|
||||
};
|
||||
|
||||
struct FragmentColorSchemeViewModel : FragmentColorSchemeViewModelT<FragmentColorSchemeViewModel>, ViewModelHelper<FragmentColorSchemeViewModel>
|
||||
{
|
||||
public:
|
||||
FragmentColorSchemeViewModel(const Model::FragmentColorSchemeEntry& entry, const Model::FragmentSettings& fragment, const Editor::ColorSchemeViewModel& deducedSchemeVM) :
|
||||
_entry{ entry },
|
||||
_fragment{ fragment },
|
||||
_deducedSchemeVM{ deducedSchemeVM } {}
|
||||
|
||||
static bool SortAscending(const Editor::FragmentColorSchemeViewModel& lhs, const Editor::FragmentColorSchemeViewModel& rhs);
|
||||
|
||||
Editor::ColorSchemeViewModel ColorSchemeVM() const { return _deducedSchemeVM; };
|
||||
hstring SourceName() const { return _fragment.Source(); }
|
||||
hstring Json() const { return _entry.Json(); }
|
||||
hstring AccessibleName() const noexcept;
|
||||
|
||||
private:
|
||||
Model::FragmentColorSchemeEntry _entry;
|
||||
Model::FragmentSettings _fragment;
|
||||
Editor::ColorSchemeViewModel _deducedSchemeVM;
|
||||
};
|
||||
|
||||
struct ExtensionPackageTemplateSelector : public ExtensionPackageTemplateSelectorT<ExtensionPackageTemplateSelector>
|
||||
{
|
||||
public:
|
||||
ExtensionPackageTemplateSelector() = default;
|
||||
|
||||
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item, const Windows::UI::Xaml::DependencyObject& container);
|
||||
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item);
|
||||
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, DefaultTemplate, nullptr);
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ComplexTemplate, nullptr);
|
||||
};
|
||||
};
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(Extensions);
|
||||
BASIC_FACTORY(ExtensionPackageTemplateSelector);
|
||||
}
|
||||
81
src/cascadia/TerminalSettingsEditor/Extensions.idl
Normal file
81
src/cascadia/TerminalSettingsEditor/Extensions.idl
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ColorSchemesPageViewModel.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
[default_interface] runtimeclass Extensions : Windows.UI.Xaml.Controls.Page
|
||||
{
|
||||
Extensions();
|
||||
ExtensionsViewModel ViewModel { get; };
|
||||
|
||||
Windows.UI.Xaml.Thickness CalculateMargin(Boolean hidden);
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ExtensionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
// Properties
|
||||
ExtensionPackageViewModel CurrentExtensionPackage;
|
||||
Windows.UI.Xaml.DataTemplate CurrentExtensionPackageIdentifierTemplate { get; };
|
||||
Boolean IsExtensionView { get; };
|
||||
Boolean NoExtensionPackages { get; };
|
||||
Boolean NoProfilesModified { get; };
|
||||
Boolean NoProfilesAdded { get; };
|
||||
Boolean NoSchemesAdded { get; };
|
||||
Boolean DisplayBadge { get; };
|
||||
|
||||
// Views
|
||||
IVector<ExtensionPackageViewModel> ExtensionPackages { get; };
|
||||
IObservableVector<FragmentProfileViewModel> ProfilesModified { get; };
|
||||
IObservableVector<FragmentProfileViewModel> ProfilesAdded { get; };
|
||||
IObservableVector<FragmentColorSchemeViewModel> ColorSchemesAdded { get; };
|
||||
|
||||
// Methods
|
||||
void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, ColorSchemesPageViewModel colorSchemesPageVM);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Guid> NavigateToProfileRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ColorSchemeViewModel> NavigateToColorSchemeRequested;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ExtensionPackageViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
Microsoft.Terminal.Settings.Model.ExtensionPackage Package { get; };
|
||||
Boolean Enabled;
|
||||
String Scope { get; };
|
||||
String AccessibleName { get; };
|
||||
IVector<FragmentExtensionViewModel> FragmentExtensions { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FragmentExtensionViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
Microsoft.Terminal.Settings.Model.FragmentSettings Fragment { get; };
|
||||
IVectorView<FragmentProfileViewModel> ProfilesModified { get; };
|
||||
IVectorView<FragmentProfileViewModel> ProfilesAdded { get; };
|
||||
IVectorView<FragmentColorSchemeViewModel> ColorSchemesAdded { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FragmentProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
Microsoft.Terminal.Settings.Model.Profile Profile { get; };
|
||||
String SourceName { get; };
|
||||
String Json { get; };
|
||||
String AccessibleName { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FragmentColorSchemeViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
ColorSchemeViewModel ColorSchemeVM { get; };
|
||||
String SourceName { get; };
|
||||
String Json { get; };
|
||||
String AccessibleName { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ExtensionPackageTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector
|
||||
{
|
||||
ExtensionPackageTemplateSelector();
|
||||
|
||||
Windows.UI.Xaml.DataTemplate DefaultTemplate;
|
||||
Windows.UI.Xaml.DataTemplate ComplexTemplate;
|
||||
}
|
||||
}
|
||||
497
src/cascadia/TerminalSettingsEditor/Extensions.xaml
Normal file
497
src/cascadia/TerminalSettingsEditor/Extensions.xaml
Normal file
@@ -0,0 +1,497 @@
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<Page x:Class="Microsoft.Terminal.Settings.Editor.Extensions"
|
||||
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:Microsoft.Terminal.Settings.Editor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="CommonResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style x:Key="ItalicDisclaimerStyle"
|
||||
BasedOn="{StaticResource DisclaimerStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="FontStyle" Value="Italic" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CodeBlockStyle"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="Cascadia Mono, Consolas" />
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CodeBlockScrollViewerStyle"
|
||||
TargetType="ScrollViewer">
|
||||
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
|
||||
<Setter Property="HorizontalScrollMode" Value="Auto" />
|
||||
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="VerticalScrollMode" Value="Disabled" />
|
||||
<Setter Property="VerticalScrollBarVisibility" Value="Disabled" />
|
||||
</Style>
|
||||
|
||||
<local:ExtensionPackageTemplateSelector x:Key="ExtensionPackageIdentifierTemplateSelector"
|
||||
ComplexTemplate="{StaticResource ComplexExtensionIdentifierTemplate}"
|
||||
DefaultTemplate="{StaticResource DefaultExtensionIdentifierTemplate}" />
|
||||
|
||||
<DataTemplate x:Key="DefaultExtensionIdentifierTemplate"
|
||||
x:DataType="local:ExtensionPackageViewModel">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<FontIcon Grid.Column="0"
|
||||
Margin="0,0,8,0"
|
||||
FontSize="32"
|
||||
Glyph="" />
|
||||
<TextBlock Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Package.Source}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ComplexExtensionIdentifierTemplate"
|
||||
x:DataType="local:ExtensionPackageViewModel">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<IconSourceElement Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="0"
|
||||
Width="32"
|
||||
Height="32"
|
||||
Margin="0,0,8,0"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Package.Icon)}" />
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Text="{x:Bind Package.DisplayName}" />
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource SettingsPageItemDescriptionStyle}"
|
||||
Text="{x:Bind Package.Source}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<local:ExtensionPackageTemplateSelector x:Key="ExtensionPackageNavigatorTemplateSelector"
|
||||
ComplexTemplate="{StaticResource ComplexExtensionNavigatorTemplate}"
|
||||
DefaultTemplate="{StaticResource DefaultExtensionNavigatorTemplate}" />
|
||||
|
||||
<DataTemplate x:Key="DefaultExtensionNavigatorTemplate"
|
||||
x:DataType="local:ExtensionPackageViewModel">
|
||||
<Button AutomationProperties.Name="{x:Bind AccessibleName}"
|
||||
Click="ExtensionNavigator_Click"
|
||||
Style="{StaticResource NavigatorButtonStyle}"
|
||||
Tag="{x:Bind}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter Content="{x:Bind}"
|
||||
ContentTemplate="{StaticResource DefaultExtensionIdentifierTemplate}" />
|
||||
|
||||
<ToggleSwitch Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind AccessibleName}"
|
||||
IsOn="{x:Bind Enabled, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ComplexExtensionNavigatorTemplate"
|
||||
x:DataType="local:ExtensionPackageViewModel">
|
||||
<Button AutomationProperties.Name="{x:Bind AccessibleName}"
|
||||
Click="ExtensionNavigator_Click"
|
||||
Style="{StaticResource NavigatorButtonStyle}"
|
||||
Tag="{x:Bind}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter Content="{x:Bind}"
|
||||
ContentTemplate="{StaticResource ComplexExtensionIdentifierTemplate}" />
|
||||
|
||||
<ToggleSwitch Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind AccessibleName}"
|
||||
IsOn="{x:Bind Enabled, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="FragmentProfileViewModelTemplate"
|
||||
x:DataType="local:FragmentProfileViewModel">
|
||||
<muxc:Expander AutomationProperties.Name="{x:Bind AccessibleName}"
|
||||
Style="{StaticResource ExpanderStyle}">
|
||||
<muxc:Expander.Header>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal">
|
||||
<IconSourceElement Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,8,0"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Profile.EvaluatedIcon), Mode=OneWay}" />
|
||||
|
||||
<TextBlock Text="{x:Bind Profile.Name, Mode=OneWay}" />
|
||||
|
||||
<Button x:Name="NavigateToProfileButton"
|
||||
x:Uid="Extensions_NavigateToProfileButton"
|
||||
Click="NavigateToProfile_Click"
|
||||
Style="{StaticResource SettingContainerResetButtonStyle}"
|
||||
Tag="{x:Bind Profile.Guid}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(Profile.Deleted)}">
|
||||
<FontIcon Glyph=""
|
||||
Style="{StaticResource SettingContainerFontIconStyle}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Style="{StaticResource SettingContainerCurrentValueTextBlockStyle}"
|
||||
Text="{x:Bind SourceName}" />
|
||||
</Grid>
|
||||
</muxc:Expander.Header>
|
||||
<muxc:Expander.Content>
|
||||
<ScrollViewer Style="{StaticResource CodeBlockScrollViewerStyle}">
|
||||
<TextBlock Style="{StaticResource CodeBlockStyle}"
|
||||
Text="{x:Bind Json, Mode=OneWay}" />
|
||||
</ScrollViewer>
|
||||
</muxc:Expander.Content>
|
||||
</muxc:Expander>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- This styling matches that of ExpanderSettingContainerStyle for consistency -->
|
||||
<Style x:Key="ExpanderStyle"
|
||||
TargetType="muxc:Expander">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
<Setter Property="MinHeight" Value="64" />
|
||||
<Setter Property="Margin" Value="0,4,0,0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="JsonTemplate"
|
||||
x:DataType="local:FragmentExtensionViewModel">
|
||||
<muxc:Expander Header="{x:Bind Fragment.Filename}"
|
||||
Style="{StaticResource ExpanderStyle}">
|
||||
<ScrollViewer Style="{StaticResource CodeBlockScrollViewerStyle}">
|
||||
<TextBlock Style="{StaticResource CodeBlockStyle}"
|
||||
Text="{x:Bind Fragment.Json}" />
|
||||
</ScrollViewer>
|
||||
</muxc:Expander>
|
||||
</DataTemplate>
|
||||
|
||||
<!--
|
||||
Copied over from Appearances.xaml. We're unable to add the DataTemplate to CommonResources.xaml
|
||||
because it needs a code-behind class to use {x:Bind}
|
||||
-->
|
||||
<DataTemplate x:Key="ColorChipTemplate"
|
||||
x:DataType="local:ColorTableEntry">
|
||||
<Border Width="8"
|
||||
Height="8"
|
||||
Background="{x:Bind mtu:Converters.ColorToBrush(Color)}"
|
||||
CornerRadius="1" />
|
||||
</DataTemplate>
|
||||
|
||||
<!--
|
||||
Copied over from Appearances.xaml. We're unable to add the DataTemplate to CommonResources.xaml
|
||||
because it needs a code-behind class to use {x:Bind}
|
||||
-->
|
||||
<DataTemplate x:Key="ColorSchemeVMTemplate"
|
||||
x:DataType="local:ColorSchemeViewModel">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Grid Grid.Column="0"
|
||||
Padding="8"
|
||||
VerticalAlignment="Center"
|
||||
Background="{x:Bind mtu:Converters.ColorToBrush(BackgroundColor.Color), Mode=OneWay}"
|
||||
ColumnSpacing="1"
|
||||
CornerRadius="2"
|
||||
RowSpacing="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Content="{x:Bind ColorEntryAt(0), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Content="{x:Bind ColorEntryAt(1), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Content="{x:Bind ColorEntryAt(2), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.Column="3"
|
||||
Content="{x:Bind ColorEntryAt(3), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.Column="4"
|
||||
Content="{x:Bind ColorEntryAt(4), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.Column="5"
|
||||
Content="{x:Bind ColorEntryAt(5), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.Column="6"
|
||||
Content="{x:Bind ColorEntryAt(6), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.Column="7"
|
||||
Content="{x:Bind ColorEntryAt(7), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Content="{x:Bind ColorEntryAt(8), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Content="{x:Bind ColorEntryAt(9), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Content="{x:Bind ColorEntryAt(10), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="3"
|
||||
Content="{x:Bind ColorEntryAt(11), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="4"
|
||||
Content="{x:Bind ColorEntryAt(12), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="5"
|
||||
Content="{x:Bind ColorEntryAt(13), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="6"
|
||||
Content="{x:Bind ColorEntryAt(14), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Grid.Column="7"
|
||||
Content="{x:Bind ColorEntryAt(15), Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorChipTemplate}"
|
||||
IsTabStop="False" />
|
||||
<TextBlock Grid.RowSpan="2"
|
||||
Grid.Column="8"
|
||||
MaxWidth="192"
|
||||
Margin="4,0,4,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontFamily="Cascadia Code"
|
||||
Foreground="{x:Bind mtu:Converters.ColorToBrush(ForegroundColor.Color), Mode=OneWay}"
|
||||
Text="{x:Bind Name, Mode=OneWay}"
|
||||
TextTrimming="WordEllipsis" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="FragmentColorSchemeViewModelTemplate"
|
||||
x:DataType="local:FragmentColorSchemeViewModel">
|
||||
<muxc:Expander AutomationProperties.Name="{x:Bind AccessibleName}"
|
||||
Style="{StaticResource ExpanderStyle}">
|
||||
<muxc:Expander.Header>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal">
|
||||
<ContentPresenter Content="{x:Bind ColorSchemeVM, Mode=OneWay}"
|
||||
ContentTemplate="{StaticResource ColorSchemeVMTemplate}" />
|
||||
<Button x:Name="NavigateToColorSchemeButton"
|
||||
x:Uid="Extensions_NavigateToColorSchemeButton"
|
||||
Click="NavigateToColorScheme_Click"
|
||||
Style="{StaticResource SettingContainerResetButtonStyle}"
|
||||
Tag="{x:Bind ColorSchemeVM}">
|
||||
<FontIcon Glyph=""
|
||||
Style="{StaticResource SettingContainerFontIconStyle}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Style="{StaticResource SettingContainerCurrentValueTextBlockStyle}"
|
||||
Text="{x:Bind SourceName}" />
|
||||
</Grid>
|
||||
</muxc:Expander.Header>
|
||||
<muxc:Expander.Content>
|
||||
<ScrollViewer Style="{StaticResource CodeBlockScrollViewerStyle}">
|
||||
<TextBlock Style="{StaticResource CodeBlockStyle}"
|
||||
Text="{x:Bind Json, Mode=OneWay}" />
|
||||
</ScrollViewer>
|
||||
</muxc:Expander.Content>
|
||||
</muxc:Expander>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</Page.Resources>
|
||||
|
||||
<StackPanel Style="{StaticResource SettingsStackStyle}">
|
||||
|
||||
<!-- [Root View Only] -->
|
||||
<StackPanel MaxWidth="{StaticResource StandardControlMaxWidth}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.IsExtensionView), Mode=OneWay}">
|
||||
|
||||
<!-- Learn more about fragment extensions -->
|
||||
<HyperlinkButton x:Uid="Extensions_DisclaimerHyperlink"
|
||||
NavigateUri="https://learn.microsoft.com/en-us/windows/terminal/json-fragment-extensions" />
|
||||
|
||||
<!-- Grouping: Active Extensions -->
|
||||
<TextBlock x:Uid="Extensions_ActiveExtensionsHeader"
|
||||
Style="{StaticResource TextBlockSubHeaderStyle}" />
|
||||
<ItemsControl x:Name="ActiveExtensionsList"
|
||||
IsTabStop="False"
|
||||
ItemTemplateSelector="{StaticResource ExtensionPackageNavigatorTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.ExtensionPackages}"
|
||||
XYFocusKeyboardNavigation="Enabled" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- [Extension View Only] -->
|
||||
<StackPanel MaxWidth="{StaticResource StandardControlMaxWidth}"
|
||||
Visibility="{x:Bind ViewModel.IsExtensionView, Mode=OneWay}">
|
||||
<!-- Extension Status -->
|
||||
<muxc:Expander AutomationProperties.Name="{x:Bind ViewModel.CurrentExtensionPackage.AccessibleName, Mode=OneWay}"
|
||||
IsExpanded="True"
|
||||
Style="{StaticResource ExpanderStyle}">
|
||||
<muxc:Expander.Header>
|
||||
<Grid MinHeight="64">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!--
|
||||
BODGY
|
||||
Theoretically, you could use a ContentTemplateSelector directly. However, that doesn't work.
|
||||
For some reason, we just get the object type's ToString called and the selector gets nullptr as a parameter.
|
||||
Adding the template as a view model property is a workaround.
|
||||
-->
|
||||
<ContentPresenter Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Content="{x:Bind ViewModel.CurrentExtensionPackage, Mode=OneWay}"
|
||||
ContentTemplate="{x:Bind ViewModel.CurrentExtensionPackageIdentifierTemplate, Mode=OneWay}" />
|
||||
|
||||
<ToggleSwitch Grid.Column="1"
|
||||
Margin="0"
|
||||
AutomationProperties.Name="{x:Bind ViewModel.CurrentExtensionPackage.AccessibleName, Mode=OneWay}"
|
||||
IsOn="{x:Bind ViewModel.CurrentExtensionPackage.Enabled, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}"
|
||||
Tag="{x:Bind ViewModel.CurrentExtensionPackage.Package.Source, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</muxc:Expander.Header>
|
||||
<muxc:Expander.Content>
|
||||
<StackPanel>
|
||||
<!-- Scope -->
|
||||
<local:SettingContainer x:Uid="Extensions_Scope"
|
||||
Content="{x:Bind ViewModel.CurrentExtensionPackage.Scope, Mode=OneWay}"
|
||||
IsTabStop="False"
|
||||
Style="{StaticResource SettingContainerWithTextContent}" />
|
||||
<!-- JSON -->
|
||||
<ItemsControl IsTabStop="False"
|
||||
ItemTemplate="{StaticResource JsonTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.CurrentExtensionPackage.FragmentExtensions, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</muxc:Expander.Content>
|
||||
</muxc:Expander>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Grouping: Modified Profiles -->
|
||||
<StackPanel Margin="{x:Bind CalculateMargin(ViewModel.NoProfilesModified), Mode=OneWay}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.NoProfilesModified), Mode=OneWay}">
|
||||
<TextBlock x:Uid="Extensions_ModifiedProfilesHeader"
|
||||
Style="{StaticResource TextBlockSubHeaderStyle}" />
|
||||
<ItemsControl x:Name="ModifiedProfilesList"
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource FragmentProfileViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.ProfilesModified, Mode=OneWay}"
|
||||
XYFocusKeyboardNavigation="Enabled" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Grouping: Added Profiles -->
|
||||
<StackPanel Margin="{x:Bind CalculateMargin(ViewModel.NoProfilesAdded), Mode=OneWay}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.NoProfilesAdded), Mode=OneWay}">
|
||||
<TextBlock x:Uid="Extensions_AddedProfilesHeader"
|
||||
Style="{StaticResource TextBlockSubHeaderStyle}" />
|
||||
<ItemsControl x:Name="AddedProfilesList"
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource FragmentProfileViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.ProfilesAdded, Mode=OneWay}"
|
||||
XYFocusKeyboardNavigation="Enabled" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Grouping: Added Color Schemes -->
|
||||
<StackPanel Margin="{x:Bind CalculateMargin(ViewModel.NoProfilesAdded), Mode=OneWay}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.NoSchemesAdded), Mode=OneWay}">
|
||||
<TextBlock x:Uid="Extensions_AddedColorSchemesHeader"
|
||||
Style="{StaticResource TextBlockSubHeaderStyle}" />
|
||||
<ItemsControl x:Name="AddedColorSchemesList"
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource FragmentColorSchemeViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.ColorSchemesAdded, Mode=OneWay}"
|
||||
XYFocusKeyboardNavigation="Enabled" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Page>
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Compatibility.h"
|
||||
#include "Rendering.h"
|
||||
#include "RenderingViewModel.h"
|
||||
#include "Extensions.h"
|
||||
#include "Actions.h"
|
||||
#include "ProfileViewModel.h"
|
||||
#include "GlobalAppearance.h"
|
||||
@@ -45,6 +46,7 @@ static const std::wstring_view renderingTag{ L"Rendering_Nav" };
|
||||
static const std::wstring_view compatibilityTag{ L"Compatibility_Nav" };
|
||||
static const std::wstring_view actionsTag{ L"Actions_Nav" };
|
||||
static const std::wstring_view newTabMenuTag{ L"NewTabMenu_Nav" };
|
||||
static const std::wstring_view extensionsTag{ L"Extensions_Nav" };
|
||||
static const std::wstring_view globalProfileTag{ L"GlobalProfile_Nav" };
|
||||
static const std::wstring_view addProfileTag{ L"AddProfile" };
|
||||
static const std::wstring_view colorSchemesTag{ L"ColorSchemes_Nav" };
|
||||
@@ -112,6 +114,50 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
});
|
||||
|
||||
auto extensionsVMImpl = winrt::make_self<ExtensionsViewModel>(_settingsClone, _colorSchemesPageVM);
|
||||
extensionsVMImpl->NavigateToProfileRequested({ this, &MainPage::_NavigateToProfileHandler });
|
||||
extensionsVMImpl->NavigateToColorSchemeRequested({ this, &MainPage::_NavigateToColorSchemeHandler });
|
||||
_extensionsVM = *extensionsVMImpl;
|
||||
_extensionsViewModelChangedRevoker = _extensionsVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto settingName{ args.PropertyName() };
|
||||
if (settingName == L"CurrentExtensionPackage")
|
||||
{
|
||||
if (const auto& currentExtensionPackage = _extensionsVM.CurrentExtensionPackage())
|
||||
{
|
||||
const auto& pkg = currentExtensionPackage.Package();
|
||||
const auto label = pkg.DisplayName().empty() ? pkg.Source() : pkg.DisplayName();
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(currentExtensionPackage), label, BreadcrumbSubPage::Extensions_Extension);
|
||||
_breadcrumbs.Append(crumb);
|
||||
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we don't have a current extension package, we're at the root of the Extensions page
|
||||
_breadcrumbs.Clear();
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), _extensionsVM);
|
||||
}
|
||||
});
|
||||
_actionsVM = winrt::make<ActionsViewModel>(_settingsClone);
|
||||
_actionsViewModelChangedRevoker = _actionsVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto settingName{ args.PropertyName() };
|
||||
if (settingName == L"CurrentPage")
|
||||
{
|
||||
if (_actionsVM.CurrentPage() == ActionsSubPage::Edit)
|
||||
{
|
||||
contentFrame().Navigate(xaml_typename<Editor::EditAction>(), winrt::make<implementation::NavigateToCommandArgs>(_actionsVM.CurrentCommand(), *this));
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(actionsTag), L"Edit Action...", BreadcrumbSubPage::Actions_Edit);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
else if (_actionsVM.CurrentPage() == ActionsSubPage::Base)
|
||||
{
|
||||
_Navigate(winrt::hstring{ actionsTag }, BreadcrumbSubPage::None);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure to initialize the profiles _after_ we have initialized the color schemes page VM, because we pass
|
||||
// that VM into the appearance VMs within the profiles
|
||||
_InitializeProfilesList();
|
||||
@@ -161,7 +207,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_InitializeProfilesList();
|
||||
// Update the Nav State with the new version of the settings
|
||||
_colorSchemesPageVM.UpdateSettings(_settingsClone);
|
||||
_actionsVM.UpdateSettings(_settingsClone);
|
||||
_newTabMenuPageVM.UpdateSettings(_settingsClone);
|
||||
_extensionsVM.UpdateSettings(_settingsClone, _colorSchemesPageVM);
|
||||
|
||||
// We'll update the profile in the _profilesNavState whenever we actually navigate to one
|
||||
|
||||
@@ -183,7 +231,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
// found the one that was selected before the refresh
|
||||
SettingsNav().SelectedItem(item);
|
||||
_Navigate(*stringTag, crumb->SubPage());
|
||||
_Navigate(*breadcrumbStringTag, crumb->SubPage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -198,6 +246,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (const auto& breadcrumbExtensionPackage{ crumb->Tag().try_as<Editor::ExtensionPackageViewModel>() })
|
||||
{
|
||||
if (stringTag == extensionsTag)
|
||||
{
|
||||
// navigate to the NewTabMenu page,
|
||||
// _Navigate() will handle trying to find the right subpage
|
||||
SettingsNav().SelectedItem(item);
|
||||
_Navigate(breadcrumbExtensionPackage, BreadcrumbSubPage::NewTabMenu_Folder);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (const auto& profileTag{ tag.try_as<ProfileViewModel>() })
|
||||
{
|
||||
@@ -437,9 +496,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
else if (clickedItemTag == actionsTag)
|
||||
{
|
||||
contentFrame().Navigate(xaml_typename<Editor::Actions>(), winrt::make<ActionsViewModel>(_settingsClone));
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
contentFrame().Navigate(xaml_typename<Editor::Actions>(), _actionsVM);
|
||||
|
||||
if (subPage == BreadcrumbSubPage::Actions_Edit && _actionsVM.CurrentCommand() != nullptr)
|
||||
{
|
||||
_actionsVM.CurrentPage(ActionsSubPage::Edit);
|
||||
}
|
||||
}
|
||||
else if (clickedItemTag == newTabMenuTag)
|
||||
{
|
||||
@@ -457,6 +521,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
}
|
||||
else if (clickedItemTag == extensionsTag)
|
||||
{
|
||||
if (_extensionsVM.CurrentExtensionPackage())
|
||||
{
|
||||
// Setting CurrentExtensionPackage triggers the PropertyChanged event,
|
||||
// which will navigate to the correct page and update the breadcrumbs appropriately
|
||||
_extensionsVM.CurrentExtensionPackage(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), _extensionsVM);
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
}
|
||||
else if (clickedItemTag == globalProfileTag)
|
||||
{
|
||||
auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone, Dispatcher()) };
|
||||
@@ -587,6 +666,40 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void MainPage::_Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage)
|
||||
{
|
||||
_PreNavigateHelper();
|
||||
|
||||
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), _extensionsVM);
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
|
||||
if (subPage == BreadcrumbSubPage::None)
|
||||
{
|
||||
_extensionsVM.CurrentExtensionPackage(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
for (const auto& pkgVM : _extensionsVM.ExtensionPackages())
|
||||
{
|
||||
if (pkgVM.Package().Source() == extPkgVM.Package().Source())
|
||||
{
|
||||
// Take advantage of the PropertyChanged event to navigate
|
||||
// to the correct extension package and build the breadcrumbs as we go
|
||||
_extensionsVM.CurrentExtensionPackage(pkgVM);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
// If we couldn't find a reasonable match, just go back to the root
|
||||
_extensionsVM.CurrentExtensionPackage(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainPage::SaveButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/)
|
||||
{
|
||||
_settingsClone.LogSettingChanges(false);
|
||||
@@ -612,6 +725,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
_Navigate(*ntmEntryViewModel, subPage);
|
||||
}
|
||||
else if (const auto extPkgViewModel = tag.try_as<ExtensionPackageViewModel>())
|
||||
{
|
||||
_Navigate(*extPkgViewModel, subPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
_Navigate(tag.as<hstring>(), subPage);
|
||||
@@ -809,6 +926,35 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
return _breadcrumbs;
|
||||
}
|
||||
|
||||
void MainPage::_NavigateToProfileHandler(const IInspectable& /*sender*/, winrt::guid profileGuid)
|
||||
{
|
||||
for (auto&& menuItem : _menuItemSource)
|
||||
{
|
||||
if (const auto& navViewItem{ menuItem.try_as<MUX::Controls::NavigationViewItem>() })
|
||||
{
|
||||
if (const auto& tag{ navViewItem.Tag() })
|
||||
{
|
||||
if (const auto& profileTag{ tag.try_as<ProfileViewModel>() })
|
||||
{
|
||||
if (profileTag->OriginalProfileGuid() == profileGuid)
|
||||
{
|
||||
SettingsNav().SelectedItem(menuItem);
|
||||
_Navigate(*profileTag, BreadcrumbSubPage::None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Silently fail if the profile wasn't found
|
||||
}
|
||||
|
||||
void MainPage::_NavigateToColorSchemeHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/)
|
||||
{
|
||||
SettingsNav().SelectedItem(ColorSchemesNavItem());
|
||||
_Navigate(hstring{ colorSchemesTag }, BreadcrumbSubPage::ColorSchemes_Edit);
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::Media::Brush MainPage::BackgroundBrush()
|
||||
{
|
||||
return SettingsNav().Background();
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<IInspectable> Breadcrumbs() noexcept;
|
||||
Editor::ExtensionsViewModel ExtensionsVM() const noexcept { return _extensionsVM; }
|
||||
|
||||
til::typed_event<Windows::Foundation::IInspectable, Model::SettingsTarget> OpenJson;
|
||||
|
||||
@@ -68,16 +69,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
void _Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage);
|
||||
void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage);
|
||||
void _Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage);
|
||||
void _Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage);
|
||||
void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid);
|
||||
void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args);
|
||||
|
||||
void _UpdateBackgroundForMica();
|
||||
void _MoveXamlParsedNavItemsIntoItemSource();
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr };
|
||||
winrt::Microsoft::Terminal::Settings::Editor::ActionsViewModel _actionsVM{ nullptr };
|
||||
winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr };
|
||||
winrt::Microsoft::Terminal::Settings::Editor::ExtensionsViewModel _extensionsVM{ nullptr };
|
||||
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker;
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker;
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _actionsViewModelChangedRevoker;
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ntmViewModelChangedRevoker;
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _extensionsViewModelChangedRevoker;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "Extensions.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
// Due to a XAML Compiler bug, it is hard for us to propagate an HWND into a XAML-using runtimeclass.
|
||||
@@ -20,7 +22,9 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
Profile_Terminal,
|
||||
Profile_Advanced,
|
||||
ColorSchemes_Edit,
|
||||
NewTabMenu_Folder
|
||||
NewTabMenu_Folder,
|
||||
Extensions_Extension,
|
||||
Actions_Edit
|
||||
};
|
||||
|
||||
runtimeclass Breadcrumb : Windows.Foundation.IStringable
|
||||
@@ -42,6 +46,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
void SetHostingWindow(UInt64 window);
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<IInspectable> Breadcrumbs { get; };
|
||||
ExtensionsViewModel ExtensionsVM { get; };
|
||||
|
||||
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
|
||||
}
|
||||
|
||||
@@ -120,7 +120,8 @@
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItem x:Uid="Nav_ColorSchemes"
|
||||
<muxc:NavigationViewItem x:Name="ColorSchemesNavItem"
|
||||
x:Uid="Nav_ColorSchemes"
|
||||
Tag="ColorSchemes_Nav">
|
||||
<muxc:NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
@@ -155,6 +156,17 @@
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItem x:Uid="Nav_Extensions"
|
||||
Tag="Extensions_Nav">
|
||||
<muxc:NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
<muxc:NavigationViewItem.InfoBadge>
|
||||
<muxc:InfoBadge Style="{StaticResource NewInfoBadge}"
|
||||
Visibility="{x:Bind ExtensionsVM.DisplayBadge, Mode=OneWay}" />
|
||||
</muxc:NavigationViewItem.InfoBadge>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItemHeader x:Uid="Nav_Profiles" />
|
||||
|
||||
<muxc:NavigationViewItem x:Name="BaseLayerMenuItem"
|
||||
|
||||
@@ -44,6 +44,13 @@
|
||||
<ClInclude Include="Actions.h">
|
||||
<DependentUpon>Actions.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ArgsTemplateSelectors.h">
|
||||
<DependentUpon>ArgsTemplateSelectors.idl</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="EditAction.h">
|
||||
<DependentUpon>EditAction.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AddProfile.h">
|
||||
<DependentUpon>AddProfile.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -125,6 +132,10 @@
|
||||
<DependentUpon>NewTabMenuViewModel.idl</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Extensions.h">
|
||||
<DependentUpon>Extensions.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Profiles_Base.h">
|
||||
<DependentUpon>Profiles_Base.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@@ -163,6 +174,9 @@
|
||||
<Page Include="Actions.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="EditAction.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="AddProfile.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
@@ -199,6 +213,9 @@
|
||||
<Page Include="MainPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Extensions.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Profiles_Base.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
@@ -229,6 +246,13 @@
|
||||
<ClCompile Include="Actions.cpp">
|
||||
<DependentUpon>Actions.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ArgsTemplateSelectors.cpp">
|
||||
<DependentUpon>ArgsTemplateSelectors.idl</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EditAction.cpp">
|
||||
<DependentUpon>EditAction.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AddProfile.cpp">
|
||||
<DependentUpon>AddProfile.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -309,6 +333,10 @@
|
||||
<DependentUpon>NewTabMenuViewModel.idl</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Extensions.cpp">
|
||||
<DependentUpon>Extensions.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Profiles_Base.cpp">
|
||||
<DependentUpon>Profiles_Base.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@@ -350,6 +378,13 @@
|
||||
<DependentUpon>Actions.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="ArgsTemplateSelectors.idl">
|
||||
<SubType>Designer</SubType>
|
||||
</Midl>
|
||||
<Midl Include="EditAction.idl">
|
||||
<DependentUpon>EditAction.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="AddProfile.idl">
|
||||
<DependentUpon>AddProfile.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@@ -408,6 +443,10 @@
|
||||
<Midl Include="GlobalAppearanceViewModel.idl" />
|
||||
<Midl Include="LaunchViewModel.idl" />
|
||||
<Midl Include="NewTabMenuViewModel.idl" />
|
||||
<Midl Include="Extensions.idl">
|
||||
<DependentUpon>Extensions.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="Profiles_Base.idl">
|
||||
<DependentUpon>Profiles_Base.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<Midl Include="LaunchViewModel.idl" />
|
||||
<Midl Include="EnumEntry.idl" />
|
||||
<Midl Include="SettingContainer.idl" />
|
||||
<Midl Include="ArgsTemplateSelectors.idl" />
|
||||
<Midl Include="TerminalColorConverters.idl" />
|
||||
<Midl Include="NewTabMenuViewModel.idl" />
|
||||
</ItemGroup>
|
||||
@@ -49,6 +50,7 @@
|
||||
<Page Include="Appearances.xaml" />
|
||||
<Page Include="Rendering.xaml" />
|
||||
<Page Include="Actions.xaml" />
|
||||
<Page Include="EditAction.xaml" />
|
||||
<Page Include="SettingContainerStyle.xaml" />
|
||||
<Page Include="AddProfile.xaml" />
|
||||
<Page Include="KeyChordListener.xaml" />
|
||||
|
||||
@@ -22,8 +22,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_entryTemplateSelector = Resources().Lookup(box_value(L"NewTabMenuEntryTemplateSelector")).as<Editor::NewTabMenuEntryTemplateSelector>();
|
||||
|
||||
// Ideally, we'd bind IsEnabled to something like mtu:Converters.isEmpty(NewTabMenuListView.SelectedItems.Size) in the XAML,
|
||||
// but the XAML compiler can't find NewTabMenuListView when we try that. Rather than copying the list of selected items over
|
||||
// to the view model, we'll just do this instead (much simpler).
|
||||
|
||||
@@ -42,7 +42,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
WINRT_OBSERVABLE_PROPERTY(Editor::NewTabMenuViewModel, ViewModel, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
private:
|
||||
Editor::NewTabMenuEntryTemplateSelector _entryTemplateSelector{ nullptr };
|
||||
Editor::NewTabMenuEntryViewModel _draggedEntry{ nullptr };
|
||||
|
||||
void _ScrollToEntry(const Editor::NewTabMenuEntryViewModel& entry);
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
<TextBlock x:Uid="NewTabMenu_CurrentFolderTextBlock"
|
||||
Style="{StaticResource TextBlockSubHeaderStyle}" />
|
||||
|
||||
<!-- TODO CARLOS: Icon -->
|
||||
<!-- TODO GH #18281: Icon -->
|
||||
<!-- Once PR #17965 merges, we can add that kind of control to set an icon -->
|
||||
|
||||
<!-- Name -->
|
||||
|
||||
@@ -684,6 +684,10 @@
|
||||
<value>Actions</value>
|
||||
<comment>Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app.</comment>
|
||||
</data>
|
||||
<data name="Nav_Extensions.Content" xml:space="preserve">
|
||||
<value>Extensions</value>
|
||||
<comment>Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app.</comment>
|
||||
</data>
|
||||
<data name="Profile_OpacitySlider.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Background opacity</value>
|
||||
<comment>Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque.</comment>
|
||||
@@ -942,6 +946,10 @@
|
||||
<value>Always</value>
|
||||
<comment>An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility.</comment>
|
||||
</data>
|
||||
<data name="Profile_AdjustIndistinguishableColorsAutomatic.Content" xml:space="preserve">
|
||||
<value>Automatic</value>
|
||||
<comment>An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled.</comment>
|
||||
</data>
|
||||
<data name="Profile_CursorShapeBar.Content" xml:space="preserve">
|
||||
<value>Bar ( ┃ )</value>
|
||||
<comment>{Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like.</comment>
|
||||
@@ -1784,6 +1792,38 @@
|
||||
<value>Delete the unfocused appearance for this profile.</value>
|
||||
<comment>A description for what the delete unfocused appearance button does.</comment>
|
||||
</data>
|
||||
<data name="Actions_Disclaimer.Text" xml:space="preserve">
|
||||
<value>Learn more about actions</value>
|
||||
<comment>Disclaimer presented at the top of the actions page to redirect the user to documentation regarding actions.</comment>
|
||||
</data>
|
||||
<data name="Actions_DeleteButton2.Text" xml:space="preserve">
|
||||
<value>Delete action</value>
|
||||
<comment>Button label that deletes the selected action.</comment>
|
||||
</data>
|
||||
<data name="Actions_Name.Text" xml:space="preserve">
|
||||
<value>Action name</value>
|
||||
<comment>Label for the text box that edits the action name.</comment>
|
||||
</data>
|
||||
<data name="Actions_NameEntryBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Action name</value>
|
||||
<comment>Placeholder text for the text box where the user can edit the action name.</comment>
|
||||
</data>
|
||||
<data name="Actions_ShortcutAction.Text" xml:space="preserve">
|
||||
<value>Action type</value>
|
||||
<comment>Label for the combo box that edits the action type.</comment>
|
||||
</data>
|
||||
<data name="Actions_Arguments.Text" xml:space="preserve">
|
||||
<value>Additional arguments</value>
|
||||
<comment>Label for the list of editable arguments for the currently selected action.</comment>
|
||||
</data>
|
||||
<data name="Actions_Keybindings.Text" xml:space="preserve">
|
||||
<value>Keybindings</value>
|
||||
<comment>Label for the list of editable keybindings for the current command.</comment>
|
||||
</data>
|
||||
<data name="Actions_AddKeyChord.Text" xml:space="preserve">
|
||||
<value>Add keybinding</value>
|
||||
<comment>Button label that adds a keybinding to the current action.</comment>
|
||||
</data>
|
||||
<data name="Actions_DeleteConfirmationButton.Content" xml:space="preserve">
|
||||
<value>Yes, delete key binding</value>
|
||||
<comment>Button label that confirms deletion of a key binding entry.</comment>
|
||||
@@ -1792,6 +1832,14 @@
|
||||
<value>Are you sure you want to delete this key binding?</value>
|
||||
<comment>Confirmation message displayed when the user attempts to delete a key binding entry.</comment>
|
||||
</data>
|
||||
<data name="Actions_CommandDeleteConfirmationButton.Content" xml:space="preserve">
|
||||
<value>Yes, delete action</value>
|
||||
<comment>Button label that confirms deletion of an action.</comment>
|
||||
</data>
|
||||
<data name="Actions_CommandDeleteConfirmationMessage.Text" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this action?</value>
|
||||
<comment>Confirmation message displayed when the user attempts to delete an action.</comment>
|
||||
</data>
|
||||
<data name="Actions_InvalidKeyChordMessage" xml:space="preserve">
|
||||
<value>Invalid key chord. Please enter a valid key chord.</value>
|
||||
<comment>Error message displayed when an invalid key chord is input by the user.</comment>
|
||||
@@ -1836,6 +1884,278 @@
|
||||
<value>Action</value>
|
||||
<comment>Label for a control that sets the action of a key binding.</comment>
|
||||
</data>
|
||||
<data name="Actions_NullEnumValue" xml:space="preserve">
|
||||
<value>Null (use global setting)</value>
|
||||
<comment>An option to choose from for nullable enums. Clears the enum value.</comment>
|
||||
</data>
|
||||
<data name="Actions_CopyFormatHtml.Content" xml:space="preserve">
|
||||
<value>HTML</value>
|
||||
<comment>An option to choose from for the "copy format". Copies content in HTML format.</comment>
|
||||
</data>
|
||||
<data name="Actions_CopyFormatRtf.Content" xml:space="preserve">
|
||||
<value>RTF</value>
|
||||
<comment>An option to choose from for the "copy format". Copies content in Rich Text Format (RTF).</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitDirectionAuto.Content" xml:space="preserve">
|
||||
<value>Automatic</value>
|
||||
<comment>An option to choose from for the "split direction". Automatically determines the split direction.</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitDirectionUp.Content" xml:space="preserve">
|
||||
<value>Up</value>
|
||||
<comment>An option to choose from for the "split direction". Splits upward.</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitDirectionRight.Content" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
<comment>An option to choose from for the "split direction". Splits to the right.</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitDirectionDown.Content" xml:space="preserve">
|
||||
<value>Down</value>
|
||||
<comment>An option to choose from for the "split direction". Splits downward.</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitDirectionLeft.Content" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
<comment>An option to choose from for the "split direction". Splits to the left.</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitDirectionVertical.Content" xml:space="preserve">
|
||||
<value>Vertical</value>
|
||||
<comment>An option to choose from for the "split direction". Splits to the left.</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitDirectionHorizontal.Content" xml:space="preserve">
|
||||
<value>Horizontal</value>
|
||||
<comment>An option to choose from for the "split direction". Splits to the left.</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitTypeManual.Content" xml:space="preserve">
|
||||
<value>Manual</value>
|
||||
<comment>An option to choose from for the "split type". Creates a manual split.</comment>
|
||||
</data>
|
||||
<data name="Actions_SplitTypeDuplicate.Content" xml:space="preserve">
|
||||
<value>Duplicate</value>
|
||||
<comment>An option to choose from for the "split type". Creates a split by duplicating the current session.</comment>
|
||||
</data>
|
||||
<data name="Actions_ResizeDirectionNone.Content" xml:space="preserve">
|
||||
<value>None</value>
|
||||
<comment>An option to choose from for the "resize direction". None option.</comment>
|
||||
</data>
|
||||
<data name="Actions_ResizeDirectionLeft.Content" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
<comment>An option to choose from for the "resize direction". Left option.</comment>
|
||||
</data>
|
||||
<data name="Actions_ResizeDirectionRight.Content" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
<comment>An option to choose from for the "resize direction". Right option.</comment>
|
||||
</data>
|
||||
<data name="Actions_ResizeDirectionUp.Content" xml:space="preserve">
|
||||
<value>Up</value>
|
||||
<comment>An option to choose from for the "resize direction". Up option.</comment>
|
||||
</data>
|
||||
<data name="Actions_ResizeDirectionDown.Content" xml:space="preserve">
|
||||
<value>Down</value>
|
||||
<comment>An option to choose from for the "resize direction". Down option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionNone.Content" xml:space="preserve">
|
||||
<value>None</value>
|
||||
<comment>An option to choose from for the "focus direction". None option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionLeft.Content" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
<comment>An option to choose from for the "focus direction". Left option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionRight.Content" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
<comment>An option to choose from for the "focus direction". Right option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionUp.Content" xml:space="preserve">
|
||||
<value>Up</value>
|
||||
<comment>An option to choose from for the "focus direction". Up option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionDown.Content" xml:space="preserve">
|
||||
<value>Down</value>
|
||||
<comment>An option to choose from for the "focus direction". Down option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionPrevious.Content" xml:space="preserve">
|
||||
<value>Previous</value>
|
||||
<comment>An option to choose from for the "focus direction". Previous option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionPreviousInOrder.Content" xml:space="preserve">
|
||||
<value>Previous In Order</value>
|
||||
<comment>An option to choose from for the "focus direction". Previous in order option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionNextInOrder.Content" xml:space="preserve">
|
||||
<value>Next In Order</value>
|
||||
<comment>An option to choose from for the "focus direction". Next in order option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionFirst.Content" xml:space="preserve">
|
||||
<value>First</value>
|
||||
<comment>An option to choose from for the "focus direction". First option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionParent.Content" xml:space="preserve">
|
||||
<value>Parent</value>
|
||||
<comment>An option to choose from for the "focus direction". Parent option.</comment>
|
||||
</data>
|
||||
<data name="Actions_FocusDirectionChild.Content" xml:space="preserve">
|
||||
<value>Child</value>
|
||||
<comment>An option to choose from for the "focus direction". Child option.</comment>
|
||||
</data>
|
||||
<data name="Actions_SettingsTargetSettingsFile.Content" xml:space="preserve">
|
||||
<value>Settings File</value>
|
||||
<comment>An option to choose from for the "settings target". Targets the settings file.</comment>
|
||||
</data>
|
||||
<data name="Actions_SettingsTargetDefaultsFile.Content" xml:space="preserve">
|
||||
<value>Defaults File</value>
|
||||
<comment>An option to choose from for the "settings target". Targets the defaults file.</comment>
|
||||
</data>
|
||||
<data name="Actions_SettingsTargetAllFiles.Content" xml:space="preserve">
|
||||
<value>All Files</value>
|
||||
<comment>An option to choose from for the "settings target". Targets all files.</comment>
|
||||
</data>
|
||||
<data name="Actions_SettingsTargetSettingsUI.Content" xml:space="preserve">
|
||||
<value>Settings UI</value>
|
||||
<comment>An option to choose from for the "settings target". Targets the settings UI.</comment>
|
||||
</data>
|
||||
<data name="Actions_SettingsTargetDirectory.Content" xml:space="preserve">
|
||||
<value>Directory</value>
|
||||
<comment>An option to choose from for the "settings target". Targets the directory.</comment>
|
||||
</data>
|
||||
<data name="Actions_MoveTabDirectionNone.Content" xml:space="preserve">
|
||||
<value>None</value>
|
||||
<comment>An option to choose from for the "move tab direction". No movement.</comment>
|
||||
</data>
|
||||
<data name="Actions_MoveTabDirectionForward.Content" xml:space="preserve">
|
||||
<value>Forward</value>
|
||||
<comment>An option to choose from for the "move tab direction". Moves the tab forward.</comment>
|
||||
</data>
|
||||
<data name="Actions_MoveTabDirectionBackward.Content" xml:space="preserve">
|
||||
<value>Backward</value>
|
||||
<comment>An option to choose from for the "move tab direction". Moves the tab backward.</comment>
|
||||
</data>
|
||||
<data name="Actions_ScrollToMarkDirectionPrevious.Content" xml:space="preserve">
|
||||
<value>Previous</value>
|
||||
<comment>An option to choose from for the "scroll to mark direction". Scrolls to the previous mark.</comment>
|
||||
</data>
|
||||
<data name="Actions_ScrollToMarkDirectionNext.Content" xml:space="preserve">
|
||||
<value>Next</value>
|
||||
<comment>An option to choose from for the "scroll to mark direction". Scrolls to the next mark.</comment>
|
||||
</data>
|
||||
<data name="Actions_ScrollToMarkDirectionFirst.Content" xml:space="preserve">
|
||||
<value>First</value>
|
||||
<comment>An option to choose from for the "scroll to mark direction". Scrolls to the first mark.</comment>
|
||||
</data>
|
||||
<data name="Actions_ScrollToMarkDirectionLast.Content" xml:space="preserve">
|
||||
<value>Last</value>
|
||||
<comment>An option to choose from for the "scroll to mark direction". Scrolls to the last mark.</comment>
|
||||
</data>
|
||||
<data name="Actions_CommandPaletteLaunchModeAction.Content" xml:space="preserve">
|
||||
<value>Action</value>
|
||||
<comment>An option to choose from for the "command palette launch mode". Launches in action mode.</comment>
|
||||
</data>
|
||||
<data name="Actions_CommandPaletteLaunchModeCommandLine.Content" xml:space="preserve">
|
||||
<value>Command Line</value>
|
||||
<comment>An option to choose from for the "command palette launch mode". Launches in command line mode.</comment>
|
||||
</data>
|
||||
<data name="Actions_SuggestionsSourceNone.Content" xml:space="preserve">
|
||||
<value>None</value>
|
||||
<comment>An option to choose from for the "suggestions source". No suggestions source.</comment>
|
||||
</data>
|
||||
<data name="Actions_SuggestionsSourceTasks.Content" xml:space="preserve">
|
||||
<value>Tasks</value>
|
||||
<comment>An option to choose from for the "suggestions source". Suggestions come from tasks.</comment>
|
||||
</data>
|
||||
<data name="Actions_SuggestionsSourceSnippets.Content" xml:space="preserve">
|
||||
<value>Snippets</value>
|
||||
<comment>An option to choose from for the "suggestions source". Suggestions come from snippets.</comment>
|
||||
</data>
|
||||
<data name="Actions_SuggestionsSourceCommandHistory.Content" xml:space="preserve">
|
||||
<value>Command History</value>
|
||||
<comment>An option to choose from for the "suggestions source". Suggestions come from command history.</comment>
|
||||
</data>
|
||||
<data name="Actions_SuggestionsSourceDirectoryHistory.Content" xml:space="preserve">
|
||||
<value>Directory History</value>
|
||||
<comment>An option to choose from for the "suggestions source". Suggestions come from directory history.</comment>
|
||||
</data>
|
||||
<data name="Actions_SuggestionsSourceQuickFix.Content" xml:space="preserve">
|
||||
<value>Quick Fixes</value>
|
||||
<comment>An option to choose from for the "suggestions source". Suggestions come from quick fixes.</comment>
|
||||
</data>
|
||||
<data name="Actions_SuggestionsSourceAll.Content" xml:space="preserve">
|
||||
<value>All</value>
|
||||
<comment>An option to choose from for the "suggestions source". Includes all suggestion sources.</comment>
|
||||
</data>
|
||||
<data name="Actions_FindMatchDirectionNone.Content" xml:space="preserve">
|
||||
<value>None</value>
|
||||
<comment>An option to choose from for the "find match direction". No direction selected.</comment>
|
||||
</data>
|
||||
<data name="Actions_FindMatchDirectionNext.Content" xml:space="preserve">
|
||||
<value>Next</value>
|
||||
<comment>An option to choose from for the "find match direction". Finds the next match.</comment>
|
||||
</data>
|
||||
<data name="Actions_FindMatchDirectionPrev.Content" xml:space="preserve">
|
||||
<value>Previous</value>
|
||||
<comment>An option to choose from for the "find match direction". Finds the previous match.</comment>
|
||||
</data>
|
||||
<data name="Actions_DesktopBehaviorAny.Content" xml:space="preserve">
|
||||
<value>Any</value>
|
||||
<comment>An option to choose from for the "desktop behavior". Applies to any desktop.</comment>
|
||||
</data>
|
||||
<data name="Actions_DesktopBehaviorToCurrent.Content" xml:space="preserve">
|
||||
<value>To Current</value>
|
||||
<comment>An option to choose from for the "desktop behavior". Moves to the current desktop.</comment>
|
||||
</data>
|
||||
<data name="Actions_DesktopBehaviorOnCurrent.Content" xml:space="preserve">
|
||||
<value>On Current</value>
|
||||
<comment>An option to choose from for the "desktop behavior". Stays on the current desktop.</comment>
|
||||
</data>
|
||||
<data name="Actions_MonitorBehaviorAny.Content" xml:space="preserve">
|
||||
<value>Any</value>
|
||||
<comment>An option to choose from for the "monitor behavior". Applies to any monitor.</comment>
|
||||
</data>
|
||||
<data name="Actions_MonitorBehaviorToCurrent.Content" xml:space="preserve">
|
||||
<value>To Current</value>
|
||||
<comment>An option to choose from for the "monitor behavior". Moves to the current monitor.</comment>
|
||||
</data>
|
||||
<data name="Actions_MonitorBehaviorToMouse.Content" xml:space="preserve">
|
||||
<value>To Mouse</value>
|
||||
<comment>An option to choose from for the "monitor behavior". Moves to the monitor where the mouse is located.</comment>
|
||||
</data>
|
||||
<data name="Actions_ClearBufferTypeScreen.Content" xml:space="preserve">
|
||||
<value>Screen</value>
|
||||
<comment>An option to choose from for the "clear buffer type". Clears only the screen.</comment>
|
||||
</data>
|
||||
<data name="Actions_ClearBufferTypeScrollback.Content" xml:space="preserve">
|
||||
<value>Scrollback</value>
|
||||
<comment>An option to choose from for the "clear buffer type". Clears only the scrollback buffer.</comment>
|
||||
</data>
|
||||
<data name="Actions_ClearBufferTypeAll.Content" xml:space="preserve">
|
||||
<value>All</value>
|
||||
<comment>An option to choose from for the "clear buffer type". Clears both the screen and the scrollback buffer.</comment>
|
||||
</data>
|
||||
<data name="Actions_SelectOutputDirectionPrev.Content" xml:space="preserve">
|
||||
<value>Previous</value>
|
||||
<comment>An option to choose from for the "select output direction". Selects the previous output.</comment>
|
||||
</data>
|
||||
<data name="Actions_SelectOutputDirectionNext.Content" xml:space="preserve">
|
||||
<value>Next</value>
|
||||
<comment>An option to choose from for the "select output direction". Selects the next output.</comment>
|
||||
</data>
|
||||
<data name="Actions_TabSwitcherModeMru.Content" xml:space="preserve">
|
||||
<value>Most Recently Used</value>
|
||||
<comment>An option to choose from for the "tab switcher mode". Switches tabs based on most recently used order.</comment>
|
||||
</data>
|
||||
<data name="Actions_TabSwitcherModeInOrder.Content" xml:space="preserve">
|
||||
<value>In Order</value>
|
||||
<comment>An option to choose from for the "tab switcher mode". Switches tabs in sequential order.</comment>
|
||||
</data>
|
||||
<data name="Actions_TabSwitcherModeDisabled.Content" xml:space="preserve">
|
||||
<value>Disabled</value>
|
||||
<comment>An option to choose from for the "tab switcher mode". Disables tab switching.</comment>
|
||||
</data>
|
||||
<data name="Actions_NullableColorPicker.NullColorButtonLabel" xml:space="preserve">
|
||||
<value>No color</value>
|
||||
<comment>Label for a button directing the user to opt out of choosing a color.</comment>
|
||||
</data>
|
||||
<data name="Actions_Browse.Content" xml:space="preserve">
|
||||
<value>Browse...</value>
|
||||
<comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
|
||||
</data>
|
||||
<data name="KeyChordListener.[using:Windows.UI.Xaml.Automation]AutomationProperties.HelpText" xml:space="preserve">
|
||||
<value>Input your desired keyboard shortcut.</value>
|
||||
<comment>Help text directing users how to use the "KeyChordListener" control. Pressing a keyboard shortcut will be recorded by this control.</comment>
|
||||
@@ -2388,4 +2708,47 @@
|
||||
<data name="Settings_ResetApplicationStateConfirmationButton.Content" xml:space="preserve">
|
||||
<value>Yes, clear the cache</value>
|
||||
</data>
|
||||
<data name="Extensions_ActiveExtensionsHeader.Text" xml:space="preserve">
|
||||
<value>Active Extensions</value>
|
||||
</data>
|
||||
<data name="Extensions_ModifiedProfilesHeader.Text" xml:space="preserve">
|
||||
<value>Modified Profiles</value>
|
||||
</data>
|
||||
<data name="Extensions_AddedProfilesHeader.Text" xml:space="preserve">
|
||||
<value>Added Profiles</value>
|
||||
</data>
|
||||
<data name="Extensions_AddedColorSchemesHeader.Text" xml:space="preserve">
|
||||
<value>Added Color Schemes</value>
|
||||
</data>
|
||||
<data name="Extensions_DisclaimerHyperlink.Content" xml:space="preserve">
|
||||
<value>Learn more about extensions</value>
|
||||
</data>
|
||||
<data name="Extensions_NavigateToProfileButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Navigate to profile</value>
|
||||
</data>
|
||||
<data name="Extensions_NavigateToProfileButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Navigate to profile</value>
|
||||
</data>
|
||||
<data name="Extensions_NavigateToColorSchemeButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Navigate to color scheme</value>
|
||||
</data>
|
||||
<data name="Extensions_NavigateToColorSchemeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Navigate to color scheme</value>
|
||||
</data>
|
||||
<data name="Extensions_ScopeUser" xml:space="preserve">
|
||||
<value>Current User</value>
|
||||
<comment>Label for the installation scope of an extension.</comment>
|
||||
</data>
|
||||
<data name="Extensions_ScopeSystem" xml:space="preserve">
|
||||
<value>All Users</value>
|
||||
<comment>Label for the installation scope of an extension</comment>
|
||||
</data>
|
||||
<data name="Extensions_Scope.Header" xml:space="preserve">
|
||||
<value>Scope</value>
|
||||
<comment>Header for the installation scope of the extension</comment>
|
||||
</data>
|
||||
<data name="NewInfoBadgeTextBlock.Text" xml:space="preserve">
|
||||
<value>NEW</value>
|
||||
<comment>Text is used on an info badge for new navigation items. Must be all caps.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SettingContainerResetButtonStyle"
|
||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Margin" Value="5,0,0,0" />
|
||||
<Setter Property="Height" Value="19" />
|
||||
@@ -179,13 +180,18 @@
|
||||
<Setter Property="FontFamily" Value="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SettingContainerCurrentValueTextBlockStyle"
|
||||
BasedOn="{StaticResource SettingsPageItemDescriptionStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="MaxWidth" Value="250" />
|
||||
<Setter Property="Margin" Value="0,0,-16,0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="ExpanderSettingContainerStringPreviewTemplate">
|
||||
<TextBlock MaxWidth="250"
|
||||
Margin="0,0,-16,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
|
||||
Style="{StaticResource SettingsPageItemDescriptionStyle}"
|
||||
<TextBlock Style="{StaticResource SettingContainerCurrentValueTextBlockStyle}"
|
||||
Text="{Binding}" />
|
||||
</DataTemplate>
|
||||
|
||||
@@ -228,6 +234,45 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- A basic setting container displaying immutable text as content -->
|
||||
<Style x:Key="SettingContainerWithTextContent"
|
||||
TargetType="local:SettingContainer">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:SettingContainer">
|
||||
<Grid AutomationProperties.Name="{TemplateBinding Header}"
|
||||
Style="{StaticResource NonExpanderGrid}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0"
|
||||
Style="{StaticResource StackPanelInExpanderStyle}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
|
||||
Text="{TemplateBinding Header}" />
|
||||
<Button x:Name="ResetButton"
|
||||
Style="{StaticResource SettingContainerResetButtonStyle}">
|
||||
<FontIcon Glyph=""
|
||||
Style="{StaticResource SettingContainerFontIconStyle}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="HelpTextBlock"
|
||||
Style="{StaticResource SettingsPageItemDescriptionStyle}"
|
||||
Text="{TemplateBinding HelpText}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1"
|
||||
Margin="0,0,8,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Style="{ThemeResource SecondaryTextBlockStyle}"
|
||||
Text="{TemplateBinding Content}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!--
|
||||
A setting container for a setting that has no additional options.
|
||||
Includes space for an icon on the left side of the header.
|
||||
@@ -302,8 +347,7 @@
|
||||
<StackPanel Grid.Column="0"
|
||||
Style="{StaticResource StackPanelInExpanderStyle}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
|
||||
Text="{TemplateBinding Header}" />
|
||||
<ContentPresenter Content="{TemplateBinding Header}" />
|
||||
<Button x:Name="ResetButton"
|
||||
Style="{StaticResource SettingContainerResetButtonStyle}">
|
||||
<FontIcon Glyph=""
|
||||
|
||||
@@ -102,189 +102,208 @@ protected: \
|
||||
// false, if we don't really care if the parameter is required or not.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define COPY_TEXT_ARGS(X) \
|
||||
X(bool, DismissSelection, "dismissSelection", false, true) \
|
||||
X(bool, SingleLine, "singleLine", false, false) \
|
||||
X(bool, WithControlSequences, "withControlSequences", false, false) \
|
||||
X(Windows::Foundation::IReference<Control::CopyFormat>, CopyFormatting, "copyFormatting", false, nullptr)
|
||||
#define COPY_TEXT_ARGS(X) \
|
||||
X(bool, DismissSelection, "dismissSelection", false, ArgTag::None, true) \
|
||||
X(bool, SingleLine, "singleLine", false, ArgTag::None, false) \
|
||||
X(bool, WithControlSequences, "withControlSequences", false, ArgTag::None, false) \
|
||||
X(Windows::Foundation::IReference<Control::CopyFormat>, CopyFormatting, "copyFormatting", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define MOVE_PANE_ARGS(X) \
|
||||
X(uint32_t, TabIndex, "index", false, 0) \
|
||||
X(winrt::hstring, Window, "window", false, L"")
|
||||
#define MOVE_PANE_ARGS(X) \
|
||||
X(uint32_t, TabIndex, "index", false, ArgTag::None, 0) \
|
||||
X(winrt::hstring, Window, "window", false, ArgTag::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SWITCH_TO_TAB_ARGS(X) \
|
||||
X(uint32_t, TabIndex, "index", false, 0)
|
||||
X(uint32_t, TabIndex, "index", false, ArgTag::None, 0)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define RESIZE_PANE_ARGS(X) \
|
||||
X(Model::ResizeDirection, ResizeDirection, "direction", args->ResizeDirection() == ResizeDirection::None, Model::ResizeDirection::None)
|
||||
X(Model::ResizeDirection, ResizeDirection, "direction", args->ResizeDirection() == ResizeDirection::None, ArgTag::None, Model::ResizeDirection::None)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define MOVE_FOCUS_ARGS(X) \
|
||||
X(Model::FocusDirection, FocusDirection, "direction", args->FocusDirection() == Model::FocusDirection::None, Model::FocusDirection::None)
|
||||
X(Model::FocusDirection, FocusDirection, "direction", args->FocusDirection() == Model::FocusDirection::None, ArgTag::None, Model::FocusDirection::None)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SWAP_PANE_ARGS(X) \
|
||||
X(Model::FocusDirection, Direction, "direction", args->Direction() == Model::FocusDirection::None, Model::FocusDirection::None)
|
||||
X(Model::FocusDirection, Direction, "direction", args->Direction() == Model::FocusDirection::None, ArgTag::None, Model::FocusDirection::None)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define ADJUST_FONT_SIZE_ARGS(X) \
|
||||
X(float, Delta, "delta", false, 0)
|
||||
X(float, Delta, "delta", false, ArgTag::None, 0)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SEND_INPUT_ARGS(X) \
|
||||
X(winrt::hstring, Input, "input", args->Input().empty(), L"")
|
||||
X(winrt::hstring, Input, "input", args->Input().empty(), ArgTag::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define OPEN_SETTINGS_ARGS(X) \
|
||||
X(SettingsTarget, Target, "target", false, SettingsTarget::SettingsFile)
|
||||
X(SettingsTarget, Target, "target", false, ArgTag::None, SettingsTarget::SettingsFile)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SET_FOCUS_MODE_ARGS(X) \
|
||||
X(bool, IsFocusMode, "isFocusMode", false, false)
|
||||
X(bool, IsFocusMode, "isFocusMode", false, ArgTag::None, false)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SET_MAXIMIZED_ARGS(X) \
|
||||
X(bool, IsMaximized, "isMaximized", false, false)
|
||||
X(bool, IsMaximized, "isMaximized", false, ArgTag::None, false)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SET_FULL_SCREEN_ARGS(X) \
|
||||
X(bool, IsFullScreen, "isFullScreen", false, false)
|
||||
X(bool, IsFullScreen, "isFullScreen", false, ArgTag::None, false)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SET_MAXIMIZED_ARGS(X) \
|
||||
X(bool, IsMaximized, "isMaximized", false, false)
|
||||
X(bool, IsMaximized, "isMaximized", false, ArgTag::None, false)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SET_COLOR_SCHEME_ARGS(X) \
|
||||
X(winrt::hstring, SchemeName, "colorScheme", args->SchemeName().empty(), L"")
|
||||
X(winrt::hstring, SchemeName, "colorScheme", args->SchemeName().empty(), ArgTag::ColorScheme, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SET_TAB_COLOR_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<Windows::UI::Color>, TabColor, "color", false, nullptr)
|
||||
X(Windows::Foundation::IReference<Windows::UI::Color>, TabColor, "color", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define RENAME_TAB_ARGS(X) \
|
||||
X(winrt::hstring, Title, "title", false, L"")
|
||||
X(winrt::hstring, Title, "title", false, ArgTag::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define EXECUTE_COMMANDLINE_ARGS(X) \
|
||||
X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), L"")
|
||||
X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), ArgTag::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define CLOSE_OTHER_TABS_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<uint32_t>, Index, "index", false, nullptr)
|
||||
X(Windows::Foundation::IReference<uint32_t>, Index, "index", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define CLOSE_TABS_AFTER_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<uint32_t>, Index, "index", false, nullptr)
|
||||
X(Windows::Foundation::IReference<uint32_t>, Index, "index", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define CLOSE_TAB_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<uint32_t>, Index, "index", false, nullptr)
|
||||
X(Windows::Foundation::IReference<uint32_t>, Index, "index", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Interestingly, the order MATTERS here. Window has to be BEFORE Direction,
|
||||
// because otherwise we won't have parsed the Window yet when we validate the
|
||||
// Direction.
|
||||
#define MOVE_TAB_ARGS(X) \
|
||||
X(winrt::hstring, Window, "window", false, L"") \
|
||||
X(MoveTabDirection, Direction, "direction", (args->Direction() == MoveTabDirection::None) && (args->Window().empty()), MoveTabDirection::None)
|
||||
#define MOVE_TAB_ARGS(X) \
|
||||
X(winrt::hstring, Window, "window", false, ArgTag::None, L"") \
|
||||
X(MoveTabDirection, Direction, "direction", (args->Direction() == MoveTabDirection::None) && (args->Window().empty()), ArgTag::None, MoveTabDirection::None)
|
||||
|
||||
// Other ideas:
|
||||
// X(uint32_t, TabIndex, "index", false, 0) \ // target? source?
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SCROLL_UP_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<uint32_t>, RowsToScroll, "rowsToScroll", false, nullptr)
|
||||
X(Windows::Foundation::IReference<uint32_t>, RowsToScroll, "rowsToScroll", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SCROLL_DOWN_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<uint32_t>, RowsToScroll, "rowsToScroll", false, nullptr)
|
||||
X(Windows::Foundation::IReference<uint32_t>, RowsToScroll, "rowsToScroll", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SCROLL_TO_MARK_ARGS(X) \
|
||||
X(Microsoft::Terminal::Control::ScrollToMarkDirection, Direction, "direction", false, Microsoft::Terminal::Control::ScrollToMarkDirection::Previous)
|
||||
X(Microsoft::Terminal::Control::ScrollToMarkDirection, Direction, "direction", false, ArgTag::None, Microsoft::Terminal::Control::ScrollToMarkDirection::Previous)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define ADD_MARK_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<Microsoft::Terminal::Core::Color>, Color, "color", false, nullptr)
|
||||
X(Windows::Foundation::IReference<Microsoft::Terminal::Core::Color>, Color, "color", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define TOGGLE_COMMAND_PALETTE_ARGS(X) \
|
||||
X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, CommandPaletteLaunchMode::Action)
|
||||
X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, ArgTag::None, CommandPaletteLaunchMode::Action)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SAVE_TASK_ARGS(X) \
|
||||
X(winrt::hstring, Name, "name", false, L"") \
|
||||
X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), L"") \
|
||||
X(winrt::hstring, KeyChord, "keyChord", false, L"")
|
||||
#define SAVE_TASK_ARGS(X) \
|
||||
X(winrt::hstring, Name, "name", false, ArgTag::None, L"") \
|
||||
X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), ArgTag::None, L"") \
|
||||
X(winrt::hstring, KeyChord, "keyChord", false, ArgTag::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SUGGESTIONS_ARGS(X) \
|
||||
X(SuggestionsSource, Source, "source", false, SuggestionsSource::Tasks) \
|
||||
X(bool, UseCommandline, "useCommandline", false, false)
|
||||
#define SUGGESTIONS_ARGS(X) \
|
||||
X(SuggestionsSource, Source, "source", false, ArgTag::None, SuggestionsSource::Tasks) \
|
||||
X(bool, UseCommandline, "useCommandline", false, ArgTag::None, false)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define FIND_MATCH_ARGS(X) \
|
||||
X(FindMatchDirection, Direction, "direction", args->Direction() == FindMatchDirection::None, FindMatchDirection::None)
|
||||
X(FindMatchDirection, Direction, "direction", args->Direction() == FindMatchDirection::None, ArgTag::None, FindMatchDirection::None)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define PREV_TAB_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, "tabSwitcherMode", false, nullptr)
|
||||
X(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, "tabSwitcherMode", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define NEXT_TAB_ARGS(X) \
|
||||
X(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, "tabSwitcherMode", false, nullptr)
|
||||
X(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, "tabSwitcherMode", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define RENAME_WINDOW_ARGS(X) \
|
||||
X(winrt::hstring, Name, "name", false, L"")
|
||||
X(winrt::hstring, Name, "name", false, ArgTag::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SEARCH_FOR_TEXT_ARGS(X) \
|
||||
X(winrt::hstring, QueryUrl, "queryUrl", false, L"")
|
||||
X(winrt::hstring, QueryUrl, "queryUrl", false, ArgTag::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define GLOBAL_SUMMON_ARGS(X) \
|
||||
X(winrt::hstring, Name, "name", false, L"") \
|
||||
X(Model::DesktopBehavior, Desktop, "desktop", false, Model::DesktopBehavior::ToCurrent) \
|
||||
X(Model::MonitorBehavior, Monitor, "monitor", false, Model::MonitorBehavior::ToMouse) \
|
||||
X(bool, ToggleVisibility, "toggleVisibility", false, true) \
|
||||
X(uint32_t, DropdownDuration, "dropdownDuration", false, 0)
|
||||
#define GLOBAL_SUMMON_ARGS(X) \
|
||||
X(winrt::hstring, Name, "name", false, ArgTag::None, L"") \
|
||||
X(Model::DesktopBehavior, Desktop, "desktop", false, ArgTag::None, Model::DesktopBehavior::ToCurrent) \
|
||||
X(Model::MonitorBehavior, Monitor, "monitor", false, ArgTag::None, Model::MonitorBehavior::ToMouse) \
|
||||
X(bool, ToggleVisibility, "toggleVisibility", false, ArgTag::None, true) \
|
||||
X(uint32_t, DropdownDuration, "dropdownDuration", false, ArgTag::None, 0)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define FOCUS_PANE_ARGS(X) \
|
||||
X(uint32_t, Id, "id", false, 0u)
|
||||
X(uint32_t, Id, "id", false, ArgTag::None, 0u)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define EXPORT_BUFFER_ARGS(X) \
|
||||
X(winrt::hstring, Path, "path", false, L"")
|
||||
X(winrt::hstring, Path, "path", false, ArgTag::FilePath, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define CLEAR_BUFFER_ARGS(X) \
|
||||
X(winrt::Microsoft::Terminal::Control::ClearBufferType, Clear, "clear", false, winrt::Microsoft::Terminal::Control::ClearBufferType::All)
|
||||
X(winrt::Microsoft::Terminal::Control::ClearBufferType, Clear, "clear", false, ArgTag::None, winrt::Microsoft::Terminal::Control::ClearBufferType::All)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define ADJUST_OPACITY_ARGS(X) \
|
||||
X(int32_t, Opacity, "opacity", false, 0) \
|
||||
X(bool, Relative, "relative", false, true)
|
||||
#define ADJUST_OPACITY_ARGS(X) \
|
||||
X(int32_t, Opacity, "opacity", false, ArgTag::None, 0) \
|
||||
X(bool, Relative, "relative", false, ArgTag::None, true)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SELECT_COMMAND_ARGS(X) \
|
||||
X(SelectOutputDirection, Direction, "direction", false, SelectOutputDirection::Previous)
|
||||
X(SelectOutputDirection, Direction, "direction", false, ArgTag::None, SelectOutputDirection::Previous)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SELECT_OUTPUT_ARGS(X) \
|
||||
X(SelectOutputDirection, Direction, "direction", false, SelectOutputDirection::Previous)
|
||||
X(SelectOutputDirection, Direction, "direction", false, ArgTag::None, SelectOutputDirection::Previous)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define COLOR_SELECTION_ARGS(X) \
|
||||
X(winrt::Microsoft::Terminal::Control::SelectionColor, Foreground, "foreground", false, nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Control::SelectionColor, Background, "background", false, nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Core::MatchMode, MatchMode, "matchMode", false, winrt::Microsoft::Terminal::Core::MatchMode::None)
|
||||
#define COLOR_SELECTION_ARGS(X) \
|
||||
X(winrt::Microsoft::Terminal::Control::SelectionColor, Foreground, "foreground", false, ArgTag::None, nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Control::SelectionColor, Background, "background", false, ArgTag::None, nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Core::MatchMode, MatchMode, "matchMode", false, ArgTag::None, winrt::Microsoft::Terminal::Core::MatchMode::None)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define NEW_TERMINAL_ARGS(X) \
|
||||
X(winrt::hstring, Commandline, "commandline", false, ArgTag::None, L"") \
|
||||
X(winrt::hstring, StartingDirectory, "startingDirectory", false, ArgTag::None, L"") \
|
||||
X(winrt::hstring, TabTitle, "tabTitle", false, ArgTag::None, L"") \
|
||||
X(Windows::Foundation::IReference<Windows::UI::Color>, TabColor, "tabColor", false, ArgTag::None, nullptr) \
|
||||
X(Windows::Foundation::IReference<int32_t>, ProfileIndex, "index", false, ArgTag::None, nullptr) \
|
||||
X(winrt::hstring, Profile, "profile", false, ArgTag::None, L"") \
|
||||
X(Windows::Foundation::IReference<bool>, SuppressApplicationTitle, "suppressApplicationTitle", false, ArgTag::None, nullptr) \
|
||||
X(winrt::hstring, ColorScheme, "colorScheme", args->SchemeName().empty(), ArgTag::ColorScheme, L"") \
|
||||
X(Windows::Foundation::IReference<bool>, Elevate, "elevate", false, ArgTag::None, nullptr) \
|
||||
X(Windows::Foundation::IReference<bool>, ReloadEnvironmentVariables, "reloadEnvironmentVariables", false, ArgTag::None, nullptr)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SPLIT_PANE_ARGS(X) \
|
||||
X(Model::SplitDirection, SplitDirection, "split", false, ArgTag::None, SplitDirection::Automatic) \
|
||||
X(SplitType, SplitMode, "splitMode", false, ArgTag::None, SplitType::Manual) \
|
||||
X(float, SplitSize, "size", false, ArgTag::None, 0.5f)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -358,41 +377,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// assumptions made in the macro.
|
||||
struct NewTerminalArgs : public NewTerminalArgsT<NewTerminalArgs>
|
||||
{
|
||||
NewTerminalArgs() = default;
|
||||
NewTerminalArgs(int32_t& profileIndex) :
|
||||
_ProfileIndex{ profileIndex } {};
|
||||
|
||||
PARTIAL_ACTION_ARG_BODY(NewTerminalArgs, NEW_TERMINAL_ARGS);
|
||||
ACTION_ARG(winrt::hstring, Type, L"");
|
||||
|
||||
ACTION_ARG(winrt::hstring, Commandline, L"");
|
||||
ACTION_ARG(winrt::hstring, StartingDirectory, L"");
|
||||
ACTION_ARG(winrt::hstring, TabTitle, L"");
|
||||
ACTION_ARG(Windows::Foundation::IReference<Windows::UI::Color>, TabColor, nullptr);
|
||||
ACTION_ARG(Windows::Foundation::IReference<int32_t>, ProfileIndex, nullptr);
|
||||
ACTION_ARG(winrt::hstring, Profile, L"");
|
||||
ACTION_ARG(winrt::guid, SessionId, winrt::guid{});
|
||||
ACTION_ARG(bool, AppendCommandLine, false);
|
||||
ACTION_ARG(Windows::Foundation::IReference<bool>, SuppressApplicationTitle, nullptr);
|
||||
ACTION_ARG(winrt::hstring, ColorScheme);
|
||||
ACTION_ARG(Windows::Foundation::IReference<bool>, Elevate, nullptr);
|
||||
ACTION_ARG(Windows::Foundation::IReference<bool>, ReloadEnvironmentVariables, nullptr);
|
||||
ACTION_ARG(uint64_t, ContentId);
|
||||
|
||||
static constexpr std::string_view CommandlineKey{ "commandline" };
|
||||
static constexpr std::string_view StartingDirectoryKey{ "startingDirectory" };
|
||||
static constexpr std::string_view TabTitleKey{ "tabTitle" };
|
||||
static constexpr std::string_view TabColorKey{ "tabColor" };
|
||||
static constexpr std::string_view ProfileIndexKey{ "index" };
|
||||
static constexpr std::string_view ProfileKey{ "profile" };
|
||||
static constexpr std::string_view SessionIdKey{ "sessionId" };
|
||||
static constexpr std::string_view AppendCommandLineKey{ "appendCommandLine" };
|
||||
static constexpr std::string_view SuppressApplicationTitleKey{ "suppressApplicationTitle" };
|
||||
static constexpr std::string_view ColorSchemeKey{ "colorScheme" };
|
||||
static constexpr std::string_view ElevateKey{ "elevate" };
|
||||
static constexpr std::string_view ReloadEnvironmentVariablesKey{ "reloadEnvironmentVariables" };
|
||||
static constexpr std::string_view ContentKey{ "__content" };
|
||||
|
||||
public:
|
||||
NewTerminalArgs(int32_t& profileIndex) :
|
||||
_ProfileIndex{ profileIndex }
|
||||
{
|
||||
NEW_TERMINAL_ARGS(APPEND_ARG_DESCRIPTION);
|
||||
};
|
||||
hstring GenerateName() const;
|
||||
hstring ToCommandline() const;
|
||||
|
||||
@@ -471,6 +471,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
copy->_Elevate = _Elevate;
|
||||
copy->_ReloadEnvironmentVariables = _ReloadEnvironmentVariables;
|
||||
copy->_ContentId = _ContentId;
|
||||
copy->_argDescriptions = _argDescriptions;
|
||||
return *copy;
|
||||
}
|
||||
size_t Hash() const
|
||||
@@ -589,7 +590,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
NewTabArgs() = default;
|
||||
NewTabArgs(const Model::INewContentArgs& terminalArgs) :
|
||||
_ContentArgs{ terminalArgs } {};
|
||||
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr);
|
||||
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, Model::NewTerminalArgs{});
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
@@ -632,34 +633,54 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
h.write(ContentArgs());
|
||||
return h.finalize();
|
||||
}
|
||||
uint32_t GetArgCount() const
|
||||
{
|
||||
return _ContentArgs.as<NewTerminalArgs>()->GetArgCount();
|
||||
}
|
||||
Model::ArgDescription GetArgDescriptionAt(uint32_t index) const
|
||||
{
|
||||
return _ContentArgs.as<NewTerminalArgs>()->GetArgDescriptionAt(index);
|
||||
}
|
||||
IInspectable GetArgAt(uint32_t index) const
|
||||
{
|
||||
return _ContentArgs.as<NewTerminalArgs>()->GetArgAt(index);
|
||||
}
|
||||
void SetArgAt(uint32_t index, IInspectable value)
|
||||
{
|
||||
_ContentArgs.as<NewTerminalArgs>()->SetArgAt(index, value);
|
||||
}
|
||||
};
|
||||
|
||||
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
|
||||
{
|
||||
SplitPaneArgs() = default;
|
||||
SplitPaneArgs(){
|
||||
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
|
||||
};
|
||||
SplitPaneArgs(SplitType splitMode, SplitDirection direction, float size, const Model::INewContentArgs& terminalArgs) :
|
||||
_SplitMode{ splitMode },
|
||||
_SplitDirection{ direction },
|
||||
_SplitSize{ size },
|
||||
_ContentArgs{ terminalArgs } {};
|
||||
_ContentArgs{ terminalArgs } {
|
||||
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
|
||||
};
|
||||
SplitPaneArgs(SplitDirection direction, float size, const Model::INewContentArgs& terminalArgs) :
|
||||
_SplitDirection{ direction },
|
||||
_SplitSize{ size },
|
||||
_ContentArgs{ terminalArgs } {};
|
||||
_ContentArgs{ terminalArgs } {
|
||||
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
|
||||
};
|
||||
SplitPaneArgs(SplitDirection direction, const Model::INewContentArgs& terminalArgs) :
|
||||
_SplitDirection{ direction },
|
||||
_ContentArgs{ terminalArgs } {};
|
||||
_ContentArgs{ terminalArgs } {
|
||||
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
|
||||
};
|
||||
SplitPaneArgs(SplitType splitMode) :
|
||||
_SplitMode{ splitMode } {};
|
||||
_SplitMode{ splitMode } {
|
||||
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
|
||||
};
|
||||
|
||||
ACTION_ARG(Model::SplitDirection, SplitDirection, SplitDirection::Automatic);
|
||||
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr);
|
||||
ACTION_ARG(SplitType, SplitMode, SplitType::Manual);
|
||||
ACTION_ARG(float, SplitSize, 0.5f);
|
||||
|
||||
static constexpr std::string_view SplitKey{ "split" };
|
||||
static constexpr std::string_view SplitModeKey{ "splitMode" };
|
||||
static constexpr std::string_view SplitSizeKey{ "size" };
|
||||
SPLIT_PANE_ARGS(DECLARE_ARGS);
|
||||
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, Model::NewTerminalArgs{});
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
@@ -681,7 +702,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<SplitPaneArgs>();
|
||||
JsonUtils::GetValueForKey(json, SplitKey, args->_SplitDirection);
|
||||
JsonUtils::GetValueForKey(json, SplitDirectionKey, args->_SplitDirection);
|
||||
JsonUtils::GetValueForKey(json, SplitModeKey, args->_SplitMode);
|
||||
JsonUtils::GetValueForKey(json, SplitSizeKey, args->_SplitSize);
|
||||
if (args->SplitSize() >= 1 || args->SplitSize() <= 0)
|
||||
@@ -701,7 +722,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
const auto args{ get_self<SplitPaneArgs>(val) };
|
||||
auto json{ ContentArgsToJson(args->_ContentArgs) };
|
||||
JsonUtils::SetValueForKey(json, SplitKey, args->_SplitDirection);
|
||||
JsonUtils::SetValueForKey(json, SplitDirectionKey, args->_SplitDirection);
|
||||
JsonUtils::SetValueForKey(json, SplitModeKey, args->_SplitMode);
|
||||
JsonUtils::SetValueForKey(json, SplitSizeKey, args->_SplitSize);
|
||||
return json;
|
||||
@@ -713,6 +734,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
copy->_ContentArgs = _ContentArgs.Copy();
|
||||
copy->_SplitMode = _SplitMode;
|
||||
copy->_SplitSize = _SplitSize;
|
||||
copy->_argDescriptions = _argDescriptions;
|
||||
return *copy;
|
||||
}
|
||||
size_t Hash() const
|
||||
@@ -724,6 +746,59 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
h.write(SplitSize());
|
||||
return h.finalize();
|
||||
}
|
||||
uint32_t GetArgCount() const
|
||||
{
|
||||
if (const auto newTermArgs = _ContentArgs.try_as<NewTerminalArgs>())
|
||||
{
|
||||
return newTermArgs->GetArgCount() + gsl::narrow<uint32_t>(_argDescriptions.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
return gsl::narrow<uint32_t>(_argDescriptions.size());
|
||||
}
|
||||
}
|
||||
Model::ArgDescription GetArgDescriptionAt(uint32_t index) const
|
||||
{
|
||||
const auto additionalArgCount = gsl::narrow<uint32_t>(_argDescriptions.size());
|
||||
if (index < additionalArgCount)
|
||||
{
|
||||
return _argDescriptions.at(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _ContentArgs.as<NewTerminalArgs>()->GetArgDescriptionAt(index - additionalArgCount);
|
||||
}
|
||||
}
|
||||
IInspectable GetArgAt(uint32_t index) const
|
||||
{
|
||||
const auto additionalArgCount = gsl::narrow<uint32_t>(_argDescriptions.size());
|
||||
if (index < additionalArgCount)
|
||||
{
|
||||
uint32_t curIndex{ 0 };
|
||||
SPLIT_PANE_ARGS(GET_ARG_BY_INDEX);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _ContentArgs.as<NewTerminalArgs>()->GetArgAt(index - additionalArgCount);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void SetArgAt(uint32_t index, IInspectable value)
|
||||
{
|
||||
const auto additionalArgCount = gsl::narrow<uint32_t>(_argDescriptions.size());
|
||||
if (index < additionalArgCount)
|
||||
{
|
||||
uint32_t curIndex{ 0 };
|
||||
SPLIT_PANE_ARGS(SET_ARG_BY_INDEX);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ContentArgs.as<NewTerminalArgs>()->SetArgAt(index - additionalArgCount, value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ArgDescription> _argDescriptions;
|
||||
};
|
||||
|
||||
struct NewWindowArgs : public NewWindowArgsT<NewWindowArgs>
|
||||
@@ -731,7 +806,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
NewWindowArgs() = default;
|
||||
NewWindowArgs(const Model::INewContentArgs& terminalArgs) :
|
||||
_ContentArgs{ terminalArgs } {};
|
||||
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr);
|
||||
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, Model::NewTerminalArgs{});
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
@@ -774,6 +849,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
h.write(ContentArgs());
|
||||
return h.finalize();
|
||||
}
|
||||
uint32_t GetArgCount() const
|
||||
{
|
||||
return _ContentArgs.as<NewTerminalArgs>()->GetArgCount();
|
||||
}
|
||||
Model::ArgDescription GetArgDescriptionAt(uint32_t index) const
|
||||
{
|
||||
return _ContentArgs.as<NewTerminalArgs>()->GetArgDescriptionAt(index);
|
||||
}
|
||||
IInspectable GetArgAt(uint32_t index) const
|
||||
{
|
||||
return _ContentArgs.as<NewTerminalArgs>()->GetArgAt(index);
|
||||
}
|
||||
void SetArgAt(uint32_t index, IInspectable value)
|
||||
{
|
||||
_ContentArgs.as<NewTerminalArgs>()->SetArgAt(index, value);
|
||||
}
|
||||
};
|
||||
|
||||
ACTION_ARGS_STRUCT(CopyTextArgs, COPY_TEXT_ARGS);
|
||||
@@ -913,6 +1004,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
h.write(winrt::get_abi(_Actions));
|
||||
return h.finalize();
|
||||
}
|
||||
uint32_t GetArgCount() const
|
||||
{
|
||||
return _Actions.Size();
|
||||
}
|
||||
Model::ArgDescription GetArgDescriptionAt(uint32_t /*index*/) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
IInspectable GetArgAt(uint32_t /*index*/) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
void SetArgAt(uint32_t /*index*/, IInspectable /*value*/)
|
||||
{
|
||||
throw winrt::hresult_not_implemented();
|
||||
}
|
||||
};
|
||||
|
||||
ACTION_ARGS_STRUCT(AdjustOpacityArgs, ADJUST_OPACITY_ARGS);
|
||||
|
||||
@@ -5,12 +5,32 @@ import "Command.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
enum ArgTag
|
||||
{
|
||||
None = 0,
|
||||
FilePath,
|
||||
ColorScheme
|
||||
};
|
||||
|
||||
struct ArgDescription
|
||||
{
|
||||
String Name;
|
||||
String Type;
|
||||
Boolean Required;
|
||||
ArgTag Tag;
|
||||
};
|
||||
|
||||
interface IActionArgs
|
||||
{
|
||||
Boolean Equals(IActionArgs other);
|
||||
String GenerateName();
|
||||
IActionArgs Copy();
|
||||
UInt64 Hash();
|
||||
|
||||
UInt32 GetArgCount();
|
||||
ArgDescription GetArgDescriptionAt(UInt32 index);
|
||||
IInspectable GetArgAt(UInt32 index);
|
||||
void SetArgAt(UInt32 index, Object value);
|
||||
};
|
||||
|
||||
interface IActionEventArgs
|
||||
@@ -168,6 +188,11 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
UInt64 ContentId{ get; set; };
|
||||
|
||||
String ToCommandline();
|
||||
|
||||
UInt32 GetArgCount();
|
||||
ArgDescription GetArgDescriptionAt(UInt32 index);
|
||||
IInspectable GetArgAt(UInt32 index);
|
||||
void SetArgAt(UInt32 index, Object value);
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs
|
||||
@@ -230,7 +255,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
SendInputArgs(String input);
|
||||
|
||||
String Input { get; };
|
||||
String Input;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass SplitPaneArgs : IActionArgs
|
||||
@@ -309,7 +334,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
[default_interface] runtimeclass CloseTabArgs : IActionArgs
|
||||
{
|
||||
CloseTabArgs(UInt32 tabIndex);
|
||||
Windows.Foundation.IReference<UInt32> Index { get; };
|
||||
Windows.Foundation.IReference<UInt32> Index;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass MoveTabArgs : IActionArgs
|
||||
|
||||
@@ -54,24 +54,55 @@ struct InitListPlaceholder
|
||||
// expanded. Pretty critical for tracking down extraneous commas, etc.
|
||||
|
||||
// Property definitions, and JSON keys
|
||||
#define DECLARE_ARGS(type, name, jsonKey, required, ...) \
|
||||
static constexpr std::string_view name##Key{ jsonKey }; \
|
||||
#define DECLARE_ARGS(type, name, jsonKey, required, tag, ...) \
|
||||
static constexpr std::string_view name##Key{ jsonKey }; \
|
||||
ACTION_ARG(type, name, ##__VA_ARGS__);
|
||||
|
||||
// Parameters to the non-default ctor
|
||||
#define CTOR_PARAMS(type, name, jsonKey, required, ...) \
|
||||
#define CTOR_PARAMS(type, name, jsonKey, required, tag, ...) \
|
||||
const type &name##Param,
|
||||
|
||||
// initializers in the ctor
|
||||
#define CTOR_INIT(type, name, jsonKey, required, ...) \
|
||||
#define CTOR_INIT(type, name, jsonKey, required, tag, ...) \
|
||||
_##name{ name##Param },
|
||||
|
||||
// append this argument's description to the internal vector
|
||||
#define APPEND_ARG_DESCRIPTION(type, name, jsonKey, required, tag, ...) \
|
||||
_argDescriptions.push_back({ L## #name, L## #type, std::wstring_view(L## #required) != L"false", tag });
|
||||
|
||||
// check each property in the Equals() method. You'll note there's a stray
|
||||
// `true` in the definition of Equals() below, that's to deal with trailing
|
||||
// commas
|
||||
#define EQUALS_ARGS(type, name, jsonKey, required, ...) \
|
||||
#define EQUALS_ARGS(type, name, jsonKey, required, tag, ...) \
|
||||
&&(otherAsUs->_##name == _##name)
|
||||
|
||||
// getter and setter for each property by index
|
||||
#define GET_ARG_BY_INDEX(type, name, jsonKey, required, tag, ...) \
|
||||
if (index == curIndex++) \
|
||||
{ \
|
||||
if (_##name.has_value()) \
|
||||
{ \
|
||||
return winrt::box_value(_##name.value()); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
return winrt::box_value(static_cast<type>(__VA_ARGS__)); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SET_ARG_BY_INDEX(type, name, jsonKey, required, tag, ...) \
|
||||
if (index == curIndex++) \
|
||||
{ \
|
||||
if (value) \
|
||||
{ \
|
||||
_##name = winrt::unbox_value<type>(value); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
_##name = std::nullopt; \
|
||||
} \
|
||||
}
|
||||
|
||||
// JSON deserialization. If the parameter is required to pass any validation,
|
||||
// add that as the `required` parameter here, as the body of a conditional
|
||||
// EX: For the RESIZE_PANE_ARGS
|
||||
@@ -79,7 +110,7 @@ struct InitListPlaceholder
|
||||
// the bit
|
||||
// args->ResizeDirection() == ResizeDirection::None
|
||||
// is used as the conditional for the validation here.
|
||||
#define FROM_JSON_ARGS(type, name, jsonKey, required, ...) \
|
||||
#define FROM_JSON_ARGS(type, name, jsonKey, required, tag, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, args->_##name); \
|
||||
if (required) \
|
||||
{ \
|
||||
@@ -87,17 +118,17 @@ struct InitListPlaceholder
|
||||
}
|
||||
|
||||
// JSON serialization
|
||||
#define TO_JSON_ARGS(type, name, jsonKey, required, ...) \
|
||||
#define TO_JSON_ARGS(type, name, jsonKey, required, tag, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, args->_##name);
|
||||
|
||||
// Copy each property in the Copy() method
|
||||
#define COPY_ARGS(type, name, jsonKey, required, ...) \
|
||||
#define COPY_ARGS(type, name, jsonKey, required, tag, ...) \
|
||||
copy->_##name = _##name;
|
||||
|
||||
// hash each property in Hash(). You'll note there's a stray `0` in the
|
||||
// definition of Hash() below, that's to deal with trailing commas (or in this
|
||||
// case, leading.)
|
||||
#define HASH_ARGS(type, name, jsonKey, required, ...) \
|
||||
#define HASH_ARGS(type, name, jsonKey, required, tag, ...) \
|
||||
h.write(name());
|
||||
|
||||
// Use ACTION_ARGS_STRUCT when you've got no other customizing to do.
|
||||
@@ -111,53 +142,108 @@ struct InitListPlaceholder
|
||||
// * NewTerminalArgs has a ToCommandline method it needs to additionally declare.
|
||||
// * GlobalSummonArgs has the QuakeModeFromJson helper
|
||||
|
||||
#define ACTION_ARG_BODY(className, argsMacro) \
|
||||
className() = default; \
|
||||
className( \
|
||||
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
|
||||
argsMacro(CTOR_INIT) _placeholder{} {}; \
|
||||
argsMacro(DECLARE_ARGS); \
|
||||
\
|
||||
private: \
|
||||
InitListPlaceholder _placeholder; \
|
||||
\
|
||||
public: \
|
||||
hstring GenerateName() const; \
|
||||
bool Equals(const IActionArgs& other) \
|
||||
{ \
|
||||
auto otherAsUs = other.try_as<className>(); \
|
||||
if (otherAsUs) \
|
||||
{ \
|
||||
return true argsMacro(EQUALS_ARGS); \
|
||||
} \
|
||||
return false; \
|
||||
}; \
|
||||
static FromJsonResult FromJson(const Json::Value& json) \
|
||||
{ \
|
||||
auto args = winrt::make_self<className>(); \
|
||||
argsMacro(FROM_JSON_ARGS); \
|
||||
return { *args, {} }; \
|
||||
} \
|
||||
static Json::Value ToJson(const IActionArgs& val) \
|
||||
{ \
|
||||
if (!val) \
|
||||
{ \
|
||||
return {}; \
|
||||
} \
|
||||
Json::Value json{ Json::ValueType::objectValue }; \
|
||||
const auto args{ get_self<className>(val) }; \
|
||||
argsMacro(TO_JSON_ARGS); \
|
||||
return json; \
|
||||
} \
|
||||
IActionArgs Copy() const \
|
||||
{ \
|
||||
auto copy{ winrt::make_self<className>() }; \
|
||||
argsMacro(COPY_ARGS); \
|
||||
return *copy; \
|
||||
} \
|
||||
size_t Hash() const \
|
||||
{ \
|
||||
til::hasher h; \
|
||||
argsMacro(HASH_ARGS); \
|
||||
return h.finalize(); \
|
||||
#define ACTION_ARG_BODY(className, argsMacro) \
|
||||
className(){ argsMacro(APPEND_ARG_DESCRIPTION) }; \
|
||||
className( \
|
||||
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
|
||||
argsMacro(CTOR_INIT) _placeholder{} { \
|
||||
argsMacro(APPEND_ARG_DESCRIPTION) \
|
||||
}; \
|
||||
argsMacro(DECLARE_ARGS); \
|
||||
\
|
||||
private: \
|
||||
InitListPlaceholder _placeholder; \
|
||||
std::vector<ArgDescription> _argDescriptions; \
|
||||
\
|
||||
public: \
|
||||
hstring GenerateName() const; \
|
||||
bool Equals(const IActionArgs& other) \
|
||||
{ \
|
||||
auto otherAsUs = other.try_as<className>(); \
|
||||
if (otherAsUs) \
|
||||
{ \
|
||||
return true argsMacro(EQUALS_ARGS); \
|
||||
} \
|
||||
return false; \
|
||||
}; \
|
||||
static FromJsonResult FromJson(const Json::Value& json) \
|
||||
{ \
|
||||
auto args = winrt::make_self<className>(); \
|
||||
argsMacro(FROM_JSON_ARGS); \
|
||||
return { *args, {} }; \
|
||||
} \
|
||||
static Json::Value ToJson(const IActionArgs& val) \
|
||||
{ \
|
||||
if (!val) \
|
||||
{ \
|
||||
return {}; \
|
||||
} \
|
||||
Json::Value json{ Json::ValueType::objectValue }; \
|
||||
const auto args{ get_self<className>(val) }; \
|
||||
argsMacro(TO_JSON_ARGS); \
|
||||
return json; \
|
||||
} \
|
||||
IActionArgs Copy() const \
|
||||
{ \
|
||||
auto copy{ winrt::make_self<className>() }; \
|
||||
argsMacro(COPY_ARGS); \
|
||||
copy->_argDescriptions = _argDescriptions; \
|
||||
return *copy; \
|
||||
} \
|
||||
size_t Hash() const \
|
||||
{ \
|
||||
til::hasher h; \
|
||||
argsMacro(HASH_ARGS); \
|
||||
return h.finalize(); \
|
||||
} \
|
||||
uint32_t GetArgCount() const \
|
||||
{ \
|
||||
return gsl::narrow<uint32_t>(_argDescriptions.size()); \
|
||||
} \
|
||||
ArgDescription GetArgDescriptionAt(uint32_t index) const \
|
||||
{ \
|
||||
return _argDescriptions.at(index); \
|
||||
} \
|
||||
IInspectable GetArgAt(uint32_t index) const \
|
||||
{ \
|
||||
uint32_t curIndex{ 0 }; \
|
||||
argsMacro(GET_ARG_BY_INDEX) return nullptr; \
|
||||
} \
|
||||
void SetArgAt(uint32_t index, IInspectable value) \
|
||||
{ \
|
||||
uint32_t curIndex{ 0 }; \
|
||||
argsMacro(SET_ARG_BY_INDEX) \
|
||||
}
|
||||
|
||||
#define PARTIAL_ACTION_ARG_BODY(className, argsMacro) \
|
||||
className(){ argsMacro(APPEND_ARG_DESCRIPTION) }; \
|
||||
className( \
|
||||
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
|
||||
argsMacro(CTOR_INIT) _placeholder{} { \
|
||||
argsMacro(APPEND_ARG_DESCRIPTION) \
|
||||
}; \
|
||||
argsMacro(DECLARE_ARGS); \
|
||||
\
|
||||
private: \
|
||||
InitListPlaceholder _placeholder; \
|
||||
std::vector<ArgDescription> _argDescriptions; \
|
||||
\
|
||||
public: \
|
||||
uint32_t GetArgCount() const \
|
||||
{ \
|
||||
return gsl::narrow<uint32_t>(_argDescriptions.size()); \
|
||||
} \
|
||||
ArgDescription GetArgDescriptionAt(uint32_t index) const \
|
||||
{ \
|
||||
return _argDescriptions.at(index); \
|
||||
} \
|
||||
IInspectable GetArgAt(uint32_t index) const \
|
||||
{ \
|
||||
uint32_t curIndex{ 0 }; \
|
||||
argsMacro(GET_ARG_BY_INDEX) return nullptr; \
|
||||
} \
|
||||
void SetArgAt(uint32_t index, IInspectable value) \
|
||||
{ \
|
||||
uint32_t curIndex{ 0 }; \
|
||||
argsMacro(SET_ARG_BY_INDEX) \
|
||||
}
|
||||
|
||||
@@ -380,6 +380,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return _ResolvedKeyToActionMapCache.GetView();
|
||||
}
|
||||
|
||||
IVectorView<Model::Command> ActionMap::AllCommands()
|
||||
{
|
||||
if (!_ResolvedKeyToActionMapCache)
|
||||
{
|
||||
_RefreshKeyBindingCaches();
|
||||
}
|
||||
return _AllCommandsCache.GetView();
|
||||
}
|
||||
|
||||
void ActionMap::_RefreshKeyBindingCaches()
|
||||
{
|
||||
_CumulativeKeyToActionMapCache.clear();
|
||||
@@ -387,6 +396,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
_CumulativeActionToKeyMapCache.clear();
|
||||
std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality> globalHotkeys;
|
||||
std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality> resolvedKeyToActionMap;
|
||||
std::vector<Model::Command> allCommandsVector;
|
||||
|
||||
_PopulateCumulativeKeyMaps(_CumulativeKeyToActionMapCache, _CumulativeActionToKeyMapCache);
|
||||
_PopulateCumulativeActionMap(_CumulativeIDToActionMapCache);
|
||||
@@ -406,8 +416,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [_, cmd] : _CumulativeIDToActionMapCache)
|
||||
{
|
||||
allCommandsVector.emplace_back(cmd);
|
||||
}
|
||||
|
||||
_ResolvedKeyToActionMapCache = single_threaded_map(std::move(resolvedKeyToActionMap));
|
||||
_GlobalHotkeysCache = single_threaded_map(std::move(globalHotkeys));
|
||||
_AllCommandsCache = single_threaded_vector(std::move(allCommandsVector));
|
||||
}
|
||||
|
||||
com_ptr<ActionMap> ActionMap::Copy() const
|
||||
@@ -421,7 +437,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
actionMap->_ActionMap.reserve(_ActionMap.size());
|
||||
for (const auto& [actionID, cmd] : _ActionMap)
|
||||
{
|
||||
actionMap->_ActionMap.emplace(actionID, *winrt::get_self<Command>(cmd)->Copy());
|
||||
const auto copiedCmd = winrt::get_self<Command>(cmd)->Copy();
|
||||
actionMap->_ActionMap.emplace(actionID, *copiedCmd);
|
||||
copiedCmd->IDChanged({ actionMap.get(), &ActionMap::_CommandIDChangedHandler });
|
||||
}
|
||||
|
||||
// Name --> Command
|
||||
@@ -541,6 +559,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
}
|
||||
}
|
||||
cmd.IDChanged({ this, &ActionMap::_CommandIDChangedHandler });
|
||||
_ActionMap.insert_or_assign(cmdID, cmd);
|
||||
}
|
||||
}
|
||||
@@ -573,6 +592,44 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
_changeLog.emplace(KeysKey);
|
||||
}
|
||||
|
||||
void ActionMap::_CommandIDChangedHandler(const Model::Command& senderCmd, const winrt::hstring& oldID)
|
||||
{
|
||||
const auto newID = senderCmd.ID();
|
||||
if (newID != oldID)
|
||||
{
|
||||
if (const auto foundCmd{ _GetActionByID(newID) })
|
||||
{
|
||||
if (foundCmd.ActionAndArgs() != senderCmd.ActionAndArgs())
|
||||
{
|
||||
// we found a command that has the same ID as this one, but that command has different ActionAndArgs
|
||||
// this means that foundCommand's action and/or args have been changed since its ID was generated,
|
||||
// generate a new one for it
|
||||
// Note: this is recursive! Found command's ID being changed lands us back in here to resolve any cascading collisions
|
||||
foundCmd.GenerateID();
|
||||
}
|
||||
}
|
||||
// update _ActionMap with the ID change
|
||||
_ActionMap.erase(oldID);
|
||||
_ActionMap.emplace(newID, senderCmd);
|
||||
|
||||
// update _KeyMap so that all keys that pointed to the old ID now point to the new ID
|
||||
std::unordered_set<KeyChord, KeyChordHash, KeyChordEquality> keysToRemap{};
|
||||
for (const auto& [keys, cmdID] : _KeyMap)
|
||||
{
|
||||
if (cmdID == oldID)
|
||||
{
|
||||
keysToRemap.insert(keys);
|
||||
}
|
||||
}
|
||||
for (const auto& keys : keysToRemap)
|
||||
{
|
||||
_KeyMap.erase(keys);
|
||||
_KeyMap.emplace(keys, newID);
|
||||
}
|
||||
}
|
||||
_RefreshKeyBindingCaches();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the given key chord is explicitly unbound
|
||||
// Arguments:
|
||||
@@ -686,6 +743,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IVector<Control::KeyChord> ActionMap::AllKeyBindingsForAction(const winrt::hstring& cmdID)
|
||||
{
|
||||
if (!_ResolvedKeyToActionMapCache)
|
||||
{
|
||||
_RefreshKeyBindingCaches();
|
||||
}
|
||||
|
||||
std::vector<Control::KeyChord> keybindingsList;
|
||||
for (const auto& [key, ID] : _CumulativeKeyToActionMapCache)
|
||||
{
|
||||
if (ID == cmdID)
|
||||
{
|
||||
keybindingsList.emplace_back(key);
|
||||
}
|
||||
}
|
||||
return single_threaded_vector(std::move(keybindingsList));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Rebinds a key binding to a new key chord
|
||||
// Arguments:
|
||||
@@ -741,6 +816,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void ActionMap::AddKeyBinding(Control::KeyChord keys, const winrt::hstring& cmdID)
|
||||
{
|
||||
_KeyMap.insert_or_assign(keys, cmdID);
|
||||
_RefreshKeyBindingCaches();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Add a new key binding
|
||||
// - If the key chord is already in use, the conflicting command is overwritten.
|
||||
@@ -757,6 +838,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
AddAction(*cmd, keys);
|
||||
}
|
||||
|
||||
void ActionMap::DeleteUserCommand(const winrt::hstring& cmdID)
|
||||
{
|
||||
_ActionMap.erase(cmdID);
|
||||
_RefreshKeyBindingCaches();
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
static bool _compareSchemeNames(const ColorScheme& lhs, const ColorScheme& rhs)
|
||||
{
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Windows::Foundation::Collections::IMapView<hstring, Model::Command> NameMap();
|
||||
Windows::Foundation::Collections::IMapView<Control::KeyChord, Model::Command> GlobalHotkeys();
|
||||
Windows::Foundation::Collections::IMapView<Control::KeyChord, Model::Command> KeyBindings();
|
||||
Windows::Foundation::Collections::IVectorView<Model::Command> AllCommands();
|
||||
com_ptr<ActionMap> Copy() const;
|
||||
|
||||
// queries
|
||||
@@ -63,6 +64,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Model::Command GetActionByID(const winrt::hstring& cmdID) const;
|
||||
bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const;
|
||||
Control::KeyChord GetKeyBindingForAction(const winrt::hstring& cmdID);
|
||||
Windows::Foundation::Collections::IVector<Control::KeyChord> AllKeyBindingsForAction(const winrt::hstring& cmdID);
|
||||
|
||||
// population
|
||||
void AddAction(const Model::Command& cmd, const Control::KeyChord& keys);
|
||||
@@ -78,7 +80,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// modification
|
||||
bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys);
|
||||
void DeleteKeyBinding(const Control::KeyChord& keys);
|
||||
void AddKeyBinding(Control::KeyChord keys, const winrt::hstring& cmdID);
|
||||
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
|
||||
void DeleteUserCommand(const winrt::hstring& cmdID);
|
||||
void AddSendInputAction(winrt::hstring name, winrt::hstring input, const Control::KeyChord keys);
|
||||
|
||||
Windows::Foundation::Collections::IVector<Model::Command> ExpandedCommands();
|
||||
@@ -105,6 +109,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
static std::unordered_map<hstring, Model::Command> _loadLocalSnippets(const std::filesystem::path& currentWorkingDirectory);
|
||||
|
||||
void _CommandIDChangedHandler(const Model::Command& senderCmd, const winrt::hstring& oldID);
|
||||
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionsCache{ nullptr };
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::Command> _NameMapCache{ nullptr };
|
||||
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _GlobalHotkeysCache{ nullptr };
|
||||
@@ -136,6 +142,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// This is effectively a combination of _CumulativeKeyMapCache and _CumulativeActionMapCache and its purpose is so that
|
||||
// we can give the SUI a view of the key chords and the commands they map to
|
||||
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _ResolvedKeyToActionMapCache{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<Model::Command> _AllCommandsCache{ nullptr };
|
||||
|
||||
til::shared_mutex<std::unordered_map<std::filesystem::path, std::unordered_map<hstring, Model::Command>>> _cwdLocalSnippetsCache{};
|
||||
|
||||
|
||||
@@ -13,12 +13,14 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys);
|
||||
Command GetActionByID(String cmdID);
|
||||
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID);
|
||||
IVector<Microsoft.Terminal.Control.KeyChord> AllKeyBindingsForAction(String cmdID);
|
||||
|
||||
Windows.Foundation.Collections.IMapView<String, ActionAndArgs> AvailableActions { get; };
|
||||
|
||||
Windows.Foundation.Collections.IMapView<String, Command> NameMap { get; };
|
||||
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> KeyBindings { get; };
|
||||
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> GlobalHotkeys { get; };
|
||||
Windows.Foundation.Collections.IVectorView<Command> AllCommands { get; };
|
||||
|
||||
IVector<Command> ExpandedCommands { get; };
|
||||
|
||||
@@ -27,9 +29,11 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
|
||||
[default_interface] runtimeclass ActionMap : IActionMapView
|
||||
{
|
||||
void AddAction(Command cmd, Microsoft.Terminal.Control.KeyChord keys);
|
||||
void RebindKeys(Microsoft.Terminal.Control.KeyChord oldKeys, Microsoft.Terminal.Control.KeyChord newKeys);
|
||||
void DeleteKeyBinding(Microsoft.Terminal.Control.KeyChord keys);
|
||||
|
||||
void DeleteUserCommand(String cmdID);
|
||||
void AddKeyBinding(Microsoft.Terminal.Control.KeyChord keys, String cmdID);
|
||||
void RegisterKeyBinding(Microsoft.Terminal.Control.KeyChord keys, ActionAndArgs action);
|
||||
void AddSendInputAction(String name, String input, Microsoft.Terminal.Control.KeyChord keys);
|
||||
}
|
||||
|
||||
@@ -309,6 +309,31 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
_throttler();
|
||||
}
|
||||
|
||||
bool ApplicationState::DismissBadge(const hstring& badgeId)
|
||||
{
|
||||
bool inserted{ false };
|
||||
{
|
||||
const auto state = _state.lock();
|
||||
if (!state->DismissedBadges)
|
||||
{
|
||||
state->DismissedBadges = std::unordered_set<hstring>{};
|
||||
}
|
||||
inserted = state->DismissedBadges->insert(badgeId).second;
|
||||
}
|
||||
_throttler();
|
||||
return inserted;
|
||||
}
|
||||
|
||||
bool ApplicationState::BadgeDismissed(const hstring& badgeId) const
|
||||
{
|
||||
const auto state = _state.lock_shared();
|
||||
if (state->DismissedBadges)
|
||||
{
|
||||
return state->DismissedBadges->contains(badgeId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type ApplicationState::name() const noexcept \
|
||||
|
||||
@@ -40,7 +40,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<Model::WindowLayout>, PersistedWindowLayouts, "persistedWindowLayouts") \
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines")
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines") \
|
||||
X(FileSource::Local, std::unordered_set<hstring>, DismissedBadges, "dismissedBadges")
|
||||
|
||||
struct WindowLayout : WindowLayoutT<WindowLayout>
|
||||
{
|
||||
@@ -70,6 +71,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Json::Value ToJson(FileSource parseSource) const noexcept;
|
||||
|
||||
void AppendPersistedWindowLayout(Model::WindowLayout layout);
|
||||
bool DismissBadge(const hstring& badgeId);
|
||||
bool BadgeDismissed(const hstring& badgeId) const;
|
||||
|
||||
// State getters/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
void Reset();
|
||||
|
||||
void AppendPersistedWindowLayout(WindowLayout layout);
|
||||
Boolean DismissBadge(String badgeId);
|
||||
Boolean BadgeDismissed(String badgeId);
|
||||
|
||||
String SettingsHash;
|
||||
Windows.Foundation.Collections.IVector<WindowLayout> PersistedWindowLayouts;
|
||||
|
||||
@@ -8,16 +8,29 @@
|
||||
|
||||
#include "../../inc/DefaultSettings.h"
|
||||
#include "DynamicProfileUtils.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace ::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
||||
|
||||
std::wstring_view GENERATOR_ICON_PATH{ L"ms-appx:///ProfileGeneratorIcons/AzureCloudShell.png" };
|
||||
|
||||
std::wstring_view AzureCloudShellGenerator::GetNamespace() const noexcept
|
||||
{
|
||||
return AzureGeneratorNamespace;
|
||||
}
|
||||
|
||||
std::wstring_view AzureCloudShellGenerator::GetDisplayName() const noexcept
|
||||
{
|
||||
return RS_(L"AzureCloudShellGeneratorDisplayName");
|
||||
}
|
||||
|
||||
std::wstring_view AzureCloudShellGenerator::GetIcon() const noexcept
|
||||
{
|
||||
return GENERATOR_ICON_PATH;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Checks if the Azure Cloud shell is available on this platform, and if it
|
||||
// is, creates a profile to be able to launch it.
|
||||
@@ -25,7 +38,7 @@ std::wstring_view AzureCloudShellGenerator::GetNamespace() const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a vector with the Azure Cloud Shell connection profile, if available.
|
||||
void AzureCloudShellGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
void AzureCloudShellGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
if (AzureConnection::IsAzureConnectionAvailable())
|
||||
{
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
public:
|
||||
std::wstring_view GetNamespace() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
std::wstring_view GetDisplayName() const noexcept override;
|
||||
std::wstring_view GetIcon() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "DefaultTerminal.h"
|
||||
#include "FileUtils.h"
|
||||
|
||||
#include "AllShortcutActions.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
#include <VersionHelpers.h>
|
||||
#include <WtExeUtils.h>
|
||||
@@ -114,6 +116,10 @@ Model::CascadiaSettings CascadiaSettings::Copy() const
|
||||
settings->_globals = _globals->Copy();
|
||||
settings->_allProfiles = winrt::single_threaded_observable_vector(std::move(allProfiles));
|
||||
settings->_activeProfiles = winrt::single_threaded_observable_vector(std::move(activeProfiles));
|
||||
|
||||
// extension packages don't need a deep clone
|
||||
// because they're fully immutable. We can just copy the reference over instead.
|
||||
settings->_extensionPackages = _extensionPackages;
|
||||
}
|
||||
|
||||
// load errors
|
||||
@@ -174,6 +180,16 @@ IObservableVector<Model::Profile> CascadiaSettings::ActiveProfiles() const noexc
|
||||
return _activeProfiles;
|
||||
}
|
||||
|
||||
IVectorView<Model::ExtensionPackage> CascadiaSettings::Extensions()
|
||||
{
|
||||
if (!_extensionPackages)
|
||||
{
|
||||
// Lazy load the ExtensionPackage objects
|
||||
_extensionPackages = winrt::single_threaded_vector<Model::ExtensionPackage>(std::move(SettingsLoader::LoadExtensionPackages()));
|
||||
}
|
||||
return _extensionPackages.GetView();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the globally configured keybindings
|
||||
// Arguments:
|
||||
@@ -1017,6 +1033,35 @@ winrt::hstring CascadiaSettings::ApplicationVersion()
|
||||
return RS_(L"ApplicationVersionUnknown");
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring> CascadiaSettings::AvailableShortcutActionsAndNames()
|
||||
{
|
||||
std::map<ShortcutAction, winrt::hstring> availableShortcutActionsAndNames;
|
||||
|
||||
#define ON_ALL_ACTIONS(action) availableShortcutActionsAndNames.emplace(ShortcutAction::action, RS_(L## #action));
|
||||
ALL_SHORTCUT_ACTIONS
|
||||
// Don't include internal actions here
|
||||
#undef ON_ALL_ACTIONS
|
||||
|
||||
return single_threaded_map(std::move(availableShortcutActionsAndNames));
|
||||
}
|
||||
|
||||
Model::IActionArgs CascadiaSettings::GetEmptyArgsForAction(Model::ShortcutAction shortcutAction)
|
||||
{
|
||||
switch (shortcutAction)
|
||||
{
|
||||
#define ON_ALL_ACTIONS_WITH_ARGS(name) \
|
||||
case Model::ShortcutAction::name: \
|
||||
return winrt::make<name##Args>();
|
||||
|
||||
ALL_SHORTCUT_ACTIONS_WITH_ARGS
|
||||
|
||||
#undef ON_ALL_ACTIONS_WITH_ARGS
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines if we're on an OS platform that supports
|
||||
// the default terminal handoff functionality.
|
||||
|
||||
@@ -18,6 +18,10 @@ Author(s):
|
||||
#pragma once
|
||||
|
||||
#include "CascadiaSettings.g.h"
|
||||
#include "FragmentSettings.g.h"
|
||||
#include "FragmentProfileEntry.g.h"
|
||||
#include "FragmentColorSchemeEntry.g.h"
|
||||
#include "ExtensionPackage.g.h"
|
||||
|
||||
#include "GlobalAppSettings.h"
|
||||
#include "Profile.h"
|
||||
@@ -39,6 +43,28 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
struct ExtensionPackage : ExtensionPackageT<ExtensionPackage>
|
||||
{
|
||||
public:
|
||||
ExtensionPackage(hstring source, FragmentScope scope) :
|
||||
_source{ source },
|
||||
_scope{ scope },
|
||||
_fragments{ winrt::single_threaded_vector<Model::FragmentSettings>() } {}
|
||||
|
||||
hstring Source() const noexcept { return _source; }
|
||||
FragmentScope Scope() const noexcept { return _scope; }
|
||||
Windows::Foundation::Collections::IVectorView<Model::FragmentSettings> FragmentsView() const noexcept { return _fragments.GetView(); }
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentSettings> Fragments() const noexcept { return _fragments; }
|
||||
|
||||
WINRT_PROPERTY(hstring, Icon);
|
||||
WINRT_PROPERTY(hstring, DisplayName);
|
||||
|
||||
private:
|
||||
hstring _source;
|
||||
FragmentScope _scope;
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentSettings> _fragments;
|
||||
};
|
||||
|
||||
struct ParsedSettings
|
||||
{
|
||||
winrt::com_ptr<implementation::GlobalAppSettings> globals;
|
||||
@@ -56,12 +82,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
struct SettingsLoader
|
||||
{
|
||||
static SettingsLoader Default(const std::string_view& userJSON, const std::string_view& inboxJSON);
|
||||
static std::vector<Model::ExtensionPackage> LoadExtensionPackages();
|
||||
SettingsLoader(const std::string_view& userJSON, const std::string_view& inboxJSON);
|
||||
|
||||
void GenerateProfiles();
|
||||
void GenerateExtensionPackagesFromProfileGenerators();
|
||||
void ApplyRuntimeInitialSettings();
|
||||
void MergeInboxIntoUserSettings();
|
||||
void FindFragmentsAndMergeIntoUserSettings();
|
||||
void FindFragmentsAndMergeIntoUserSettings(bool generateExtensionPackages);
|
||||
void MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content);
|
||||
void FinalizeLayering();
|
||||
bool DisableDeletedProfiles();
|
||||
@@ -70,6 +98,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
ParsedSettings inboxSettings;
|
||||
ParsedSettings userSettings;
|
||||
std::unordered_map<hstring, winrt::com_ptr<implementation::ExtensionPackage>> extensionPackageMap;
|
||||
bool duplicateProfile = false;
|
||||
|
||||
private:
|
||||
@@ -81,6 +110,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
const Json::Value& profilesList;
|
||||
const Json::Value& themes;
|
||||
};
|
||||
struct ParseFragmentMetadata
|
||||
{
|
||||
std::wstring_view jsonFilename;
|
||||
FragmentScope scope;
|
||||
};
|
||||
SettingsLoader() = default;
|
||||
|
||||
static std::pair<size_t, size_t> _lineAndColumnFromPosition(const std::string_view& string, const size_t position);
|
||||
static void _rethrowSerializationExceptionWithLocationInfo(const JsonUtils::DeserializationError& e, const std::string_view& settingsString);
|
||||
@@ -88,13 +123,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static const Json::Value& _getJSONValue(const Json::Value& json, const std::string_view& key) noexcept;
|
||||
std::span<const winrt::com_ptr<implementation::Profile>> _getNonUserOriginProfiles() const;
|
||||
void _parse(const OriginTag origin, const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings);
|
||||
void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings);
|
||||
void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, const std::optional<ParseFragmentMetadata>& fragmentMeta);
|
||||
static JsonSettings _parseJson(const std::string_view& content);
|
||||
static winrt::com_ptr<implementation::Profile> _parseProfile(const OriginTag origin, const winrt::hstring& source, const Json::Value& profileJson);
|
||||
void _appendProfile(winrt::com_ptr<Profile>&& profile, const winrt::guid& guid, ParsedSettings& settings);
|
||||
void _addUserProfileParent(const winrt::com_ptr<implementation::Profile>& profile);
|
||||
void _addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& colorScheme);
|
||||
void _executeGenerator(const IDynamicProfileGenerator& generator);
|
||||
bool _addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& colorScheme);
|
||||
static void _executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList);
|
||||
void _patchInstallPowerShellProfile(bool isPowerShellInstalled);
|
||||
winrt::com_ptr<implementation::ExtensionPackage> _registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope);
|
||||
Json::StreamWriterBuilder _getJsonStyledWriter();
|
||||
|
||||
std::unordered_set<winrt::hstring, til::transparent_hstring_hash, til::transparent_hstring_equal_to> _ignoredNamespaces;
|
||||
std::set<std::string> themesChangeLog;
|
||||
@@ -115,6 +153,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static winrt::hstring ApplicationVersion();
|
||||
static bool IsPortableMode();
|
||||
|
||||
static Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring> AvailableShortcutActionsAndNames();
|
||||
static Model::IActionArgs GetEmptyArgsForAction(Model::ShortcutAction shortcutAction);
|
||||
|
||||
CascadiaSettings() noexcept = default;
|
||||
CascadiaSettings(const winrt::hstring& userJSON, const winrt::hstring& inboxJSON);
|
||||
CascadiaSettings(const std::string_view& userJSON, const std::string_view& inboxJSON = {});
|
||||
@@ -129,6 +170,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Model::ActionMap ActionMap() const noexcept;
|
||||
void ResetApplicationState() const;
|
||||
void ResetToDefaultSettings();
|
||||
winrt::Windows::Foundation::Collections::IVectorView<Model::ExtensionPackage> Extensions();
|
||||
void WriteSettingsToDisk();
|
||||
Json::Value ToJson() const;
|
||||
Model::Profile ProfileDefaults() const;
|
||||
@@ -188,6 +230,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
winrt::com_ptr<implementation::Profile> _baseLayerProfile = winrt::make_self<implementation::Profile>();
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<Model::Profile> _allProfiles = winrt::single_threaded_observable_vector<Model::Profile>();
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<Model::Profile> _activeProfiles = winrt::single_threaded_observable_vector<Model::Profile>();
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::ExtensionPackage> _extensionPackages = nullptr;
|
||||
std::set<std::string> _themesChangeLog{};
|
||||
|
||||
// load errors
|
||||
@@ -203,6 +246,67 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
mutable std::once_flag _commandLinesCacheOnce;
|
||||
mutable std::vector<std::pair<std::wstring, Model::Profile>> _commandLinesCache;
|
||||
};
|
||||
|
||||
struct FragmentProfileEntry : FragmentProfileEntryT<FragmentProfileEntry>
|
||||
{
|
||||
public:
|
||||
FragmentProfileEntry(winrt::guid profileGuid, hstring json) :
|
||||
_profileGuid{ profileGuid },
|
||||
Json{ json } {}
|
||||
|
||||
winrt::guid ProfileGuid() const noexcept { return _profileGuid; }
|
||||
til::property<hstring> Json;
|
||||
|
||||
private:
|
||||
winrt::guid _profileGuid;
|
||||
};
|
||||
|
||||
struct FragmentColorSchemeEntry : FragmentColorSchemeEntryT<FragmentColorSchemeEntry>
|
||||
{
|
||||
public:
|
||||
FragmentColorSchemeEntry(hstring schemeName, hstring json) :
|
||||
_schemeName{ schemeName },
|
||||
_json{ json } {}
|
||||
|
||||
hstring ColorSchemeName() const noexcept { return _schemeName; }
|
||||
hstring Json() const noexcept { return _json; }
|
||||
|
||||
private:
|
||||
hstring _schemeName;
|
||||
hstring _json;
|
||||
};
|
||||
|
||||
struct FragmentSettings : FragmentSettingsT<FragmentSettings>
|
||||
{
|
||||
public:
|
||||
FragmentSettings(hstring source, hstring json, hstring filename) :
|
||||
_source{ source },
|
||||
_Json{ json },
|
||||
_filename{ filename } {}
|
||||
|
||||
hstring Source() const noexcept { return _source; }
|
||||
hstring Filename() const noexcept { return _filename; }
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> ModifiedProfiles() const noexcept { return _modifiedProfiles; }
|
||||
void ModifiedProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& modifiedProfiles) noexcept { _modifiedProfiles = modifiedProfiles; }
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> NewProfiles() const noexcept { return _newProfiles; }
|
||||
void NewProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& newProfiles) noexcept { _newProfiles = newProfiles; }
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry> ColorSchemes() const noexcept { return _colorSchemes; }
|
||||
void ColorSchemes(const Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry>& colorSchemes) noexcept { _colorSchemes = colorSchemes; }
|
||||
WINRT_PROPERTY(hstring, Json);
|
||||
|
||||
public:
|
||||
// views
|
||||
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> ModifiedProfilesView() const noexcept { return _modifiedProfiles ? _modifiedProfiles.GetView() : nullptr; }
|
||||
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> NewProfilesView() const noexcept { return _newProfiles ? _newProfiles.GetView() : nullptr; }
|
||||
Windows::Foundation::Collections::IVectorView<Model::FragmentColorSchemeEntry> ColorSchemesView() const noexcept { return _colorSchemes ? _colorSchemes.GetView() : nullptr; }
|
||||
|
||||
private:
|
||||
hstring _source;
|
||||
hstring _filename;
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _modifiedProfiles;
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _newProfiles;
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry> _colorSchemes;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
|
||||
@@ -5,9 +5,16 @@ import "GlobalAppSettings.idl";
|
||||
import "Profile.idl";
|
||||
import "TerminalWarnings.idl";
|
||||
import "DefaultTerminal.idl";
|
||||
import "ActionArgs.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
enum FragmentScope
|
||||
{
|
||||
User,
|
||||
Machine
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass CascadiaSettings {
|
||||
static CascadiaSettings LoadDefaults();
|
||||
static CascadiaSettings LoadAll();
|
||||
@@ -20,6 +27,9 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
static String ApplicationDisplayName { get; };
|
||||
static String ApplicationVersion { get; };
|
||||
|
||||
static Windows.Foundation.Collections.IMap<Microsoft.Terminal.Settings.Model.ShortcutAction, String> AvailableShortcutActionsAndNames { get; };
|
||||
static IActionArgs GetEmptyArgsForAction(Microsoft.Terminal.Settings.Model.ShortcutAction shortcutAction);
|
||||
|
||||
CascadiaSettings(String userJSON, String inboxJSON);
|
||||
|
||||
CascadiaSettings Copy();
|
||||
@@ -40,6 +50,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Profile DuplicateProfile(Profile sourceProfile);
|
||||
|
||||
ActionMap ActionMap { get; };
|
||||
Windows.Foundation.Collections.IVectorView<ExtensionPackage> Extensions { get; };
|
||||
|
||||
IVectorView<SettingsLoadWarnings> Warnings { get; };
|
||||
Windows.Foundation.IReference<SettingsLoadErrors> GetLoadingError { get; };
|
||||
@@ -58,4 +69,35 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
|
||||
void ExpandCommands();
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FragmentProfileEntry
|
||||
{
|
||||
Guid ProfileGuid { get; };
|
||||
String Json { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FragmentColorSchemeEntry
|
||||
{
|
||||
String ColorSchemeName { get; };
|
||||
String Json { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FragmentSettings
|
||||
{
|
||||
String Source { get; };
|
||||
String Json { get; };
|
||||
String Filename { get; };
|
||||
IVectorView<FragmentProfileEntry> ModifiedProfilesView { get; };
|
||||
IVectorView<FragmentProfileEntry> NewProfilesView { get; };
|
||||
IVectorView<FragmentColorSchemeEntry> ColorSchemesView { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ExtensionPackage
|
||||
{
|
||||
String Source { get; };
|
||||
String DisplayName { get; };
|
||||
String Icon { get; };
|
||||
FragmentScope Scope { get; };
|
||||
IVectorView<FragmentSettings> FragmentsView { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
|
||||
#include "SshHostGenerator.h"
|
||||
#endif
|
||||
#include "PowershellInstallationProfileGenerator.h"
|
||||
|
||||
#include "ApplicationState.h"
|
||||
#include "DefaultTerminal.h"
|
||||
@@ -124,6 +125,20 @@ SettingsLoader SettingsLoader::Default(const std::string_view& userJSON, const s
|
||||
return loader;
|
||||
}
|
||||
|
||||
std::vector<Model::ExtensionPackage> SettingsLoader::LoadExtensionPackages()
|
||||
{
|
||||
SettingsLoader loader{};
|
||||
loader.GenerateExtensionPackagesFromProfileGenerators();
|
||||
loader.FindFragmentsAndMergeIntoUserSettings(true);
|
||||
|
||||
std::vector<Model::ExtensionPackage> extensionPackages;
|
||||
for (auto [_, extPkg] : loader.extensionPackageMap)
|
||||
{
|
||||
extensionPackages.emplace_back(std::move(*extPkg));
|
||||
}
|
||||
return extensionPackages;
|
||||
}
|
||||
|
||||
// The SettingsLoader class is an internal implementation detail of CascadiaSettings.
|
||||
// Member methods aren't safe against misuse and you need to ensure to call them in a specific order.
|
||||
// See CascadiaSettings::LoadAll() for a specific usage example.
|
||||
@@ -174,19 +189,163 @@ SettingsLoader::SettingsLoader(const std::string_view& userJSON, const std::stri
|
||||
_userProfileCount = userSettings.profiles.size();
|
||||
}
|
||||
|
||||
// This method is used to generate the JSON writer used for writing json in a styled format.
|
||||
// We use it a few times throughout the loader, so we lazy load it and cache it here.
|
||||
Json::StreamWriterBuilder SettingsLoader::_getJsonStyledWriter()
|
||||
{
|
||||
static bool jsonWriterInitialized = false;
|
||||
static Json::StreamWriterBuilder styledWriter;
|
||||
if (!jsonWriterInitialized)
|
||||
{
|
||||
styledWriter["indentation"] = " ";
|
||||
styledWriter["commentStyle"] = "All";
|
||||
styledWriter.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons
|
||||
styledWriter.settings_["precision"] = 6; // prevent values like 1.1000000000000001
|
||||
jsonWriterInitialized = true;
|
||||
}
|
||||
return styledWriter;
|
||||
}
|
||||
|
||||
// Generate dynamic profiles and add them to the list of "inbox" profiles
|
||||
// (meaning profiles specified by the application rather by the user).
|
||||
void SettingsLoader::GenerateProfiles()
|
||||
{
|
||||
_executeGenerator(PowershellCoreProfileGenerator{});
|
||||
_executeGenerator(WslDistroGenerator{});
|
||||
_executeGenerator(AzureCloudShellGenerator{});
|
||||
_executeGenerator(VisualStudioGenerator{});
|
||||
auto generateProfiles = [&]<typename T>() {
|
||||
T generator{};
|
||||
if (!_ignoredNamespaces.contains(generator.GetNamespace()))
|
||||
{
|
||||
_executeGenerator(generator, inboxSettings.profiles);
|
||||
}
|
||||
return generator;
|
||||
};
|
||||
|
||||
bool isPowerShellInstalled;
|
||||
{
|
||||
auto powerShellGenerator = generateProfiles.template operator()<PowershellCoreProfileGenerator>();
|
||||
isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty();
|
||||
}
|
||||
|
||||
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
|
||||
{
|
||||
if (isPowerShellInstalled)
|
||||
{
|
||||
// PowerShell is installed, mark the installer profile for deletion (if found)
|
||||
const winrt::guid profileGuid{ L"{965a10f2-b0f2-55dc-a3c2-2ddbf639bf89}" };
|
||||
for (const auto& profile : userSettings.profiles)
|
||||
{
|
||||
if (profile->Guid() == profileGuid)
|
||||
{
|
||||
profile->Deleted(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// PowerShell isn't installed --> generate the installer stub profile
|
||||
generateProfiles.template operator()<PowershellInstallationProfileGenerator>();
|
||||
}
|
||||
}
|
||||
|
||||
generateProfiles.template operator()<WslDistroGenerator>();
|
||||
generateProfiles.template operator()<AzureCloudShellGenerator>();
|
||||
generateProfiles.template operator()<VisualStudioGenerator>();
|
||||
|
||||
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
|
||||
_executeGenerator(SshHostGenerator{});
|
||||
generateProfiles.template operator()<SshHostGenerator>();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Generate ExtensionPackage objects from the profile generators.
|
||||
void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
|
||||
{
|
||||
auto generateExtensionPackages = [&]<typename T>() {
|
||||
T generator{};
|
||||
std::vector<winrt::com_ptr<implementation::Profile>> profilesList;
|
||||
_executeGenerator(generator, profilesList);
|
||||
|
||||
// These are needed for the FragmentSettings object
|
||||
std::vector<Model::FragmentProfileEntry> profileEntries;
|
||||
Json::Value profilesListJson{ Json::ValueType::arrayValue };
|
||||
|
||||
for (const auto& profile : profilesList)
|
||||
{
|
||||
const auto profileJson = profile->ToJson();
|
||||
profilesListJson.append(profileJson);
|
||||
profileEntries.push_back(winrt::make<FragmentProfileEntry>(profile->Guid(), hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) }));
|
||||
}
|
||||
|
||||
// Manually construct the JSON for the FragmentSettings object
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
json[JsonKey(ProfilesKey)] = profilesListJson;
|
||||
|
||||
auto generatorExtension = winrt::make_self<FragmentSettings>(hstring{ generator.GetNamespace() }, hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), json)) }, hstring{ L"settings.json" });
|
||||
generatorExtension->NewProfiles(winrt::single_threaded_vector<Model::FragmentProfileEntry>(std::move(profileEntries)));
|
||||
|
||||
auto extPkg = _registerFragment(std::move(*generatorExtension), FragmentScope::Machine);
|
||||
extPkg->DisplayName(hstring{ generator.GetDisplayName() });
|
||||
extPkg->Icon(hstring{ generator.GetIcon() });
|
||||
return generator;
|
||||
};
|
||||
|
||||
bool isPowerShellInstalled;
|
||||
{
|
||||
auto powerShellGenerator = generateExtensionPackages.template operator()<PowershellCoreProfileGenerator>();
|
||||
isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty();
|
||||
}
|
||||
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
|
||||
{
|
||||
generateExtensionPackages.template operator()<PowershellInstallationProfileGenerator>();
|
||||
_patchInstallPowerShellProfile(isPowerShellInstalled);
|
||||
}
|
||||
|
||||
generateExtensionPackages.template operator()<WslDistroGenerator>();
|
||||
generateExtensionPackages.template operator()<AzureCloudShellGenerator>();
|
||||
generateExtensionPackages.template operator()<VisualStudioGenerator>();
|
||||
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
|
||||
generateExtensionPackages.template operator()<SshHostGenerator>();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Retrieve the "Install Latest PowerShell" profile and add a comment to the JSON to indicate it's conditionally applied.
|
||||
// If PowerShell is installed, delete the profile from the extension package.
|
||||
void SettingsLoader::_patchInstallPowerShellProfile(bool isPowerShellInstalled)
|
||||
{
|
||||
const hstring pwshInstallerNamespace{ PowershellInstallationProfileGenerator::Namespace };
|
||||
if (extensionPackageMap.contains(pwshInstallerNamespace))
|
||||
{
|
||||
if (const auto& fragExtList = extensionPackageMap[pwshInstallerNamespace]->Fragments(); fragExtList.Size() > 0)
|
||||
{
|
||||
auto fragExt = get_self<FragmentSettings>(fragExtList.GetAt(0));
|
||||
|
||||
// We want the comment to be the first thing in the object,
|
||||
// "closeOnExit" is the first property, so target that.
|
||||
auto fragExtJson = _parseJSON(til::u16u8(fragExt->Json()));
|
||||
fragExtJson[JsonKey(ProfilesKey)][0]["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore);
|
||||
fragExt->Json(hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), fragExtJson)) });
|
||||
|
||||
if (const auto& profileEntryList = fragExt->NewProfiles(); profileEntryList.Size() > 0)
|
||||
{
|
||||
if (isPowerShellInstalled)
|
||||
{
|
||||
// PowerShell is installed, so the installer profile was marked for deletion in GenerateProfiles().
|
||||
// Remove the profile object from the fragment so it doesn't show up in the settings UI.
|
||||
profileEntryList.RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We want the comment to be the first thing in the object,
|
||||
// "closeOnExit" is the first property, so target that.
|
||||
auto profileEntry = get_self<FragmentProfileEntry>(profileEntryList.GetAt(0));
|
||||
auto profileJson = _parseJSON(til::u16u8(profileEntry->Json()));
|
||||
profileJson["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore);
|
||||
profileEntry->Json(hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A new settings.json gets a special treatment:
|
||||
// 1. The default profile is a PowerShell 7+ one, if one was generated,
|
||||
// and falls back to the standard PowerShell 5 profile otherwise.
|
||||
@@ -242,21 +401,27 @@ void SettingsLoader::MergeInboxIntoUserSettings()
|
||||
// merge them. Unfortunately however the "updates" key in fragment profiles make this impossible:
|
||||
// The targeted profile might be one that got created as part of SettingsLoader::MergeInboxIntoUserSettings.
|
||||
// Additionally the GUID in "updates" will conflict with existing GUIDs in .inboxSettings.
|
||||
void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
|
||||
void SettingsLoader::FindFragmentsAndMergeIntoUserSettings(bool generateExtensionPackages)
|
||||
{
|
||||
ParsedSettings fragmentSettings;
|
||||
|
||||
const auto parseAndLayerFragmentFiles = [&](const std::filesystem::path& path, const winrt::hstring& source) {
|
||||
const auto parseAndLayerFragmentFiles = [&](const std::filesystem::path& path, const winrt::hstring& source, FragmentScope scope) {
|
||||
for (const auto& fragmentExt : std::filesystem::directory_iterator{ path })
|
||||
{
|
||||
if (fragmentExt.path().extension() == jsonExtension)
|
||||
const auto fragExtPath = fragmentExt.path();
|
||||
if (fragExtPath.extension() == jsonExtension)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto content = til::io::read_file_as_utf8_string_if_exists(fragmentExt.path());
|
||||
const auto content = til::io::read_file_as_utf8_string_if_exists(fragExtPath);
|
||||
if (!content.empty())
|
||||
{
|
||||
_parseFragment(source, content, fragmentSettings);
|
||||
_parseFragment(source,
|
||||
content,
|
||||
fragmentSettings,
|
||||
generateExtensionPackages ?
|
||||
static_cast<std::optional<ParseFragmentMetadata>>(ParseFragmentMetadata{ fragExtPath.filename().wstring(), scope }) :
|
||||
std::nullopt);
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
@@ -278,9 +443,11 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
|
||||
const auto filename = fragmentExtFolder.path().filename();
|
||||
const auto& source = filename.native();
|
||||
|
||||
if (!_ignoredNamespaces.contains(std::wstring_view{ source }) && fragmentExtFolder.is_directory())
|
||||
if (fragmentExtFolder.is_directory())
|
||||
{
|
||||
parseAndLayerFragmentFiles(fragmentExtFolder.path(), winrt::hstring{ source });
|
||||
parseAndLayerFragmentFiles(fragmentExtFolder.path(),
|
||||
winrt::hstring{ source },
|
||||
rfid == FOLDERID_LocalAppData ? FragmentScope::User : FragmentScope::Machine); // scope
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,11 +479,8 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
|
||||
|
||||
for (const auto& ext : extensions)
|
||||
{
|
||||
const auto packageName = ext.Package().Id().FamilyName();
|
||||
if (_ignoredNamespaces.contains(std::wstring_view{ packageName }))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto& package = ext.Package();
|
||||
const auto packageName = package.Id().FamilyName();
|
||||
|
||||
// Likewise, getting the public folder from an extension is an async operation.
|
||||
auto foundFolder = extractValueFromTaskWithoutMainThreadAwait(ext.GetPublicFolderAsync());
|
||||
@@ -334,7 +498,18 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
|
||||
|
||||
if (std::filesystem::is_directory(path))
|
||||
{
|
||||
parseAndLayerFragmentFiles(path, packageName);
|
||||
// MSIX does not support machine-wide scope
|
||||
// See https://github.com/microsoft/winget-cli/discussions/1983
|
||||
parseAndLayerFragmentFiles(path,
|
||||
packageName,
|
||||
FragmentScope::User);
|
||||
|
||||
if (generateExtensionPackages)
|
||||
{
|
||||
auto extPkg = extensionPackageMap[packageName];
|
||||
extPkg->Icon(package.Logo().AbsoluteUri());
|
||||
extPkg->DisplayName(package.DisplayName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,7 +520,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
|
||||
void SettingsLoader::MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content)
|
||||
{
|
||||
ParsedSettings fragmentSettings;
|
||||
_parseFragment(source, content, fragmentSettings);
|
||||
_parseFragment(source, content, fragmentSettings, std::nullopt);
|
||||
}
|
||||
|
||||
// Call this method before passing SettingsLoader to the CascadiaSettings constructor.
|
||||
@@ -724,15 +899,23 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source
|
||||
|
||||
// Just like _parse, but is to be used for fragment files, which don't support anything but color
|
||||
// schemes and profiles. Additionally this function supports profiles which specify an "updates" key.
|
||||
void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings)
|
||||
// - fragmentMeta: If set, construct and register FragmentSettings objects. Provides metadata necessary for doing so.
|
||||
// Otherwise, completely skip over that extra work and apply parsed settings to the user settings, if allowed by disabledProfileSources ("_ignoredNamespaces").
|
||||
void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, const std::optional<ParseFragmentMetadata>& fragmentMeta)
|
||||
{
|
||||
auto json = _parseJson(content);
|
||||
|
||||
const bool buildFragmentSettings = fragmentMeta.has_value();
|
||||
const bool applyToUserSettings = !buildFragmentSettings && !_ignoredNamespaces.contains(std::wstring_view{ source });
|
||||
winrt::com_ptr<implementation::FragmentSettings> fragmentSettings = buildFragmentSettings ?
|
||||
winrt::make_self<FragmentSettings>(source, hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), json.root)) }, hstring{ fragmentMeta->jsonFilename }) :
|
||||
nullptr;
|
||||
|
||||
settings.clear();
|
||||
|
||||
// Load GlobalAppSettings and ColorSchemes
|
||||
{
|
||||
settings.globals = winrt::make_self<GlobalAppSettings>();
|
||||
|
||||
std::vector<Model::FragmentColorSchemeEntry> fragmentColorSchemes;
|
||||
for (const auto& schemeJson : json.colorSchemes)
|
||||
{
|
||||
try
|
||||
@@ -740,72 +923,111 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str
|
||||
if (const auto scheme = ColorScheme::FromJson(schemeJson))
|
||||
{
|
||||
scheme->Origin(OriginTag::Fragment);
|
||||
// Don't add the color scheme to the Fragment's GlobalSettings; that will
|
||||
// cause layering issues later. Add them to a staging area for later processing.
|
||||
// (search for STAGED COLORS to find the next step)
|
||||
settings.colorSchemes.emplace(scheme->Name(), std::move(scheme));
|
||||
if (buildFragmentSettings)
|
||||
{
|
||||
fragmentColorSchemes.emplace_back(winrt::make<FragmentColorSchemeEntry>(scheme->Name(), hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), schemeJson)) }));
|
||||
}
|
||||
else if (applyToUserSettings)
|
||||
{
|
||||
// Don't add the color scheme to the Fragment's GlobalSettings; that will
|
||||
// cause layering issues later. Add them to a staging area for later processing.
|
||||
// (search for STAGED COLORS to find the next step)
|
||||
settings.colorSchemes.emplace(scheme->Name(), std::move(scheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
}
|
||||
|
||||
// Parse out actions from the fragment. Manually opt-out of keybinding
|
||||
// parsing - fragments shouldn't be allowed to bind actions to keys
|
||||
// directly. We may want to revisit circa GH#2205
|
||||
settings.globals->LayerActionsFrom(json.root, OriginTag::Fragment, false);
|
||||
if (buildFragmentSettings)
|
||||
{
|
||||
fragmentSettings->ColorSchemes(fragmentColorSchemes.empty() ? nullptr : single_threaded_vector<Model::FragmentColorSchemeEntry>(std::move(fragmentColorSchemes)));
|
||||
}
|
||||
else if (applyToUserSettings)
|
||||
{
|
||||
// Parse out actions from the fragment. Manually opt-out of keybinding
|
||||
// parsing - fragments shouldn't be allowed to bind actions to keys
|
||||
// directly. We may want to revisit circa GH#2205
|
||||
settings.globals = winrt::make_self<GlobalAppSettings>();
|
||||
settings.globals->LayerActionsFrom(json.root, OriginTag::Fragment, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Load new and modified profiles
|
||||
{
|
||||
const auto size = json.profilesList.size();
|
||||
settings.profiles.reserve(size);
|
||||
settings.profilesByGuid.reserve(size);
|
||||
if (applyToUserSettings)
|
||||
{
|
||||
const auto size = json.profilesList.size();
|
||||
settings.profiles.reserve(size);
|
||||
settings.profilesByGuid.reserve(size);
|
||||
}
|
||||
|
||||
std::vector<Model::FragmentProfileEntry> newProfiles;
|
||||
std::vector<Model::FragmentProfileEntry> modifiedProfiles;
|
||||
for (const auto& profileJson : json.profilesList)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto profile = _parseProfile(OriginTag::Fragment, source, profileJson);
|
||||
// GH#9962: Discard Guid-less, Name-less profiles, but...
|
||||
// allow ones with an Updates field, as those are special for fragments.
|
||||
// We need to make sure to only call Guid() if HasGuid() is true,
|
||||
// as Guid() will dynamically generate a return value otherwise.
|
||||
auto profile = _parseProfile(OriginTag::Fragment, source, profileJson);
|
||||
const auto guid = profile->HasGuid() ? profile->Guid() : profile->Updates();
|
||||
auto destinationSet = profile->HasGuid() ? &newProfiles : &modifiedProfiles;
|
||||
if (guid != winrt::guid{})
|
||||
{
|
||||
_appendProfile(std::move(profile), guid, settings);
|
||||
if (buildFragmentSettings)
|
||||
{
|
||||
destinationSet->emplace_back(winrt::make<FragmentProfileEntry>(guid, hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) }));
|
||||
}
|
||||
else if (applyToUserSettings)
|
||||
{
|
||||
_appendProfile(std::move(profile), guid, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
}
|
||||
if (buildFragmentSettings)
|
||||
{
|
||||
fragmentSettings->NewProfiles(newProfiles.empty() ? nullptr : single_threaded_vector<Model::FragmentProfileEntry>(std::move(newProfiles)));
|
||||
fragmentSettings->ModifiedProfiles(modifiedProfiles.empty() ? nullptr : single_threaded_vector<Model::FragmentProfileEntry>(std::move(modifiedProfiles)));
|
||||
_registerFragment(std::move(*fragmentSettings), fragmentMeta->scope);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& fragmentProfile : settings.profiles)
|
||||
// Merge profiles, color schemes, and globals into the user settings (aka inheritance)
|
||||
if (applyToUserSettings)
|
||||
{
|
||||
if (const auto updates = fragmentProfile->Updates(); updates != winrt::guid{})
|
||||
for (const auto& fragmentProfile : settings.profiles)
|
||||
{
|
||||
if (const auto it = userSettings.profilesByGuid.find(updates); it != userSettings.profilesByGuid.end())
|
||||
if (const auto updates = fragmentProfile->Updates(); updates != winrt::guid{})
|
||||
{
|
||||
it->second->AddMostImportantParent(fragmentProfile);
|
||||
if (const auto it = userSettings.profilesByGuid.find(updates); it != userSettings.profilesByGuid.end())
|
||||
{
|
||||
it->second->AddMostImportantParent(fragmentProfile);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_addUserProfileParent(fragmentProfile);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// STAGED COLORS are processed here: we merge them into the partially-loaded
|
||||
// settings directly so that we can resolve conflicts between user-generated
|
||||
// color schemes and fragment-originated ones.
|
||||
for (const auto& [_, fragmentColorScheme] : settings.colorSchemes)
|
||||
{
|
||||
_addUserProfileParent(fragmentProfile);
|
||||
_addOrMergeUserColorScheme(fragmentColorScheme);
|
||||
}
|
||||
}
|
||||
|
||||
// STAGED COLORS are processed here: we merge them into the partially-loaded
|
||||
// settings directly so that we can resolve conflicts between user-generated
|
||||
// color schemes and fragment-originated ones.
|
||||
for (const auto& fragmentColorScheme : settings.colorSchemes)
|
||||
{
|
||||
_addOrMergeUserColorScheme(fragmentColorScheme.second);
|
||||
// Add the parsed fragment globals as a parent of the user's settings.
|
||||
// Later, in FinalizeInheritance, this will result in the action map from
|
||||
// the fragments being applied before the user's own settings.
|
||||
userSettings.globals->AddLeastImportantParent(settings.globals);
|
||||
}
|
||||
|
||||
// Add the parsed fragment globals as a parent of the user's settings.
|
||||
// Later, in FinalizeInheritance, this will result in the action map from
|
||||
// the fragments being applied before the user's own settings.
|
||||
userSettings.globals->AddLeastImportantParent(settings.globals);
|
||||
}
|
||||
|
||||
SettingsLoader::JsonSettings SettingsLoader::_parseJson(const std::string_view& content)
|
||||
@@ -905,7 +1127,8 @@ void SettingsLoader::_addUserProfileParent(const winrt::com_ptr<implementation::
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& newScheme)
|
||||
// returns true if the scheme was successfully added, otherwise false
|
||||
bool SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& newScheme)
|
||||
{
|
||||
// On entry, all the user color schemes have been loaded. Therefore, any insertions of inbox or fragment schemes
|
||||
// will fail; we can leverage this to detect when they are equivalent and delete the user's duplicate copies.
|
||||
@@ -931,36 +1154,33 @@ void SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementat
|
||||
userSettings.colorSchemeRemappings.emplace(newScheme->Name(), newName);
|
||||
// And re-add it to the end.
|
||||
userSettings.colorSchemes.emplace(newName, std::move(existingScheme));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// As the name implies it executes a generator.
|
||||
// Generated profiles are added to .inboxSettings. Used by GenerateProfiles().
|
||||
void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator)
|
||||
void SettingsLoader::_executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
|
||||
{
|
||||
const auto generatorNamespace = generator.GetNamespace();
|
||||
if (_ignoredNamespaces.contains(generatorNamespace))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto previousSize = inboxSettings.profiles.size();
|
||||
|
||||
const auto previousSize = profilesList.size();
|
||||
try
|
||||
{
|
||||
generator.GenerateProfiles(inboxSettings.profiles);
|
||||
generator.GenerateProfiles(profilesList);
|
||||
}
|
||||
CATCH_LOG_MSG("Dynamic Profile Namespace: \"%.*s\"", gsl::narrow<int>(generatorNamespace.size()), generatorNamespace.data())
|
||||
|
||||
// If the generator produced some profiles we're going to give them default attributes.
|
||||
// By setting the Origin/Source/etc. here, we deduplicate some code and ensure they aren't missing accidentally.
|
||||
if (inboxSettings.profiles.size() > previousSize)
|
||||
if (profilesList.size() > previousSize)
|
||||
{
|
||||
const winrt::hstring source{ generatorNamespace };
|
||||
|
||||
for (const auto& profile : std::span(inboxSettings.profiles).subspan(previousSize))
|
||||
for (const auto& profile : std::span(profilesList).subspan(previousSize))
|
||||
{
|
||||
profile->Origin(OriginTag::Generated);
|
||||
profile->Source(source);
|
||||
@@ -968,6 +1188,23 @@ void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator
|
||||
}
|
||||
}
|
||||
|
||||
winrt::com_ptr<ExtensionPackage> SettingsLoader::_registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope)
|
||||
{
|
||||
const auto src = fragment.Source();
|
||||
if (auto extPkg = extensionPackageMap[src])
|
||||
{
|
||||
extPkg->Fragments().Append(fragment);
|
||||
return extPkg;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto newExtPkg = winrt::make_self<ExtensionPackage>(src, scope);
|
||||
newExtPkg->Fragments().Append(fragment);
|
||||
extensionPackageMap[src] = newExtPkg;
|
||||
return newExtPkg;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates a CascadiaSettings from whatever's saved on disk, or instantiates
|
||||
// a new one with the default values. If we're running as a packaged app,
|
||||
@@ -1040,7 +1277,7 @@ try
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
// Fragments might reference user profiles created by a generator.
|
||||
// --> FindFragmentsAndMergeIntoUserSettings must be called after MergeInboxIntoUserSettings.
|
||||
loader.FindFragmentsAndMergeIntoUserSettings();
|
||||
loader.FindFragmentsAndMergeIntoUserSettings(false);
|
||||
loader.FinalizeLayering();
|
||||
|
||||
// DisableDeletedProfiles returns true whenever we encountered any new generated/dynamic profiles.
|
||||
@@ -1508,7 +1745,11 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const
|
||||
auto activeProfileCount = gsl::narrow_cast<int>(_activeProfiles.Size());
|
||||
for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++)
|
||||
{
|
||||
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
|
||||
const auto& profile = _activeProfiles.GetAt(profileIndex);
|
||||
if (!profile.Deleted())
|
||||
{
|
||||
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// We keep track of the "remaining profiles" - those that have not yet been resolved
|
||||
|
||||
@@ -28,6 +28,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
Command::Command() = default;
|
||||
|
||||
Model::Command Command::NewUserCommand()
|
||||
{
|
||||
auto newCmd{ winrt::make_self<Command>() };
|
||||
newCmd->_Origin = OriginTag::User;
|
||||
return *newCmd;
|
||||
}
|
||||
|
||||
Model::Command Command::CopyAsUserCommand(Model::Command originalCmd)
|
||||
{
|
||||
auto command{ winrt::get_self<Command>(originalCmd) };
|
||||
auto copy{ command->Copy() };
|
||||
copy->_Origin = OriginTag::User;
|
||||
return *copy;
|
||||
}
|
||||
|
||||
com_ptr<Command> Command::Copy() const
|
||||
{
|
||||
auto command{ winrt::make_self<Command>() };
|
||||
@@ -113,6 +128,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return hstring{ _ID };
|
||||
}
|
||||
|
||||
void Command::ID(const hstring& ID) noexcept
|
||||
{
|
||||
const auto oldID = _ID;
|
||||
_ID = ID;
|
||||
IDChanged.raise(*this, oldID);
|
||||
}
|
||||
|
||||
void Command::GenerateID()
|
||||
{
|
||||
if (_ActionAndArgs)
|
||||
@@ -120,8 +142,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto actionAndArgsImpl{ winrt::get_self<implementation::ActionAndArgs>(_ActionAndArgs) };
|
||||
if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty())
|
||||
{
|
||||
_ID = generatedID;
|
||||
_IDWasGenerated = true;
|
||||
ID(generatedID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,6 +153,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return _IDWasGenerated;
|
||||
}
|
||||
|
||||
Model::ActionAndArgs Command::ActionAndArgs() const noexcept
|
||||
{
|
||||
return _ActionAndArgs;
|
||||
}
|
||||
|
||||
void Command::ActionAndArgs(const Model::ActionAndArgs& value) noexcept
|
||||
{
|
||||
_ActionAndArgs = value;
|
||||
}
|
||||
|
||||
void Command::Name(const hstring& value)
|
||||
{
|
||||
if (!_name.has_value() || _name.value() != value)
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
struct Command : CommandT<Command>
|
||||
{
|
||||
Command();
|
||||
static Model::Command NewUserCommand();
|
||||
static Model::Command CopyAsUserCommand(Model::Command originalCmd);
|
||||
com_ptr<Command> Copy() const;
|
||||
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json,
|
||||
@@ -73,9 +75,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void Name(const hstring& name);
|
||||
|
||||
hstring ID() const noexcept;
|
||||
void ID(const hstring& ID) noexcept;
|
||||
void GenerateID();
|
||||
bool IDWasGenerated();
|
||||
|
||||
// we cannot use the WINRT_PROPERTY macro for ActionAndArgs because the setter has some additional logic regarding the ID
|
||||
Model::ActionAndArgs ActionAndArgs() const noexcept;
|
||||
void ActionAndArgs(const Model::ActionAndArgs& value) noexcept;
|
||||
|
||||
hstring IconPath() const noexcept;
|
||||
void IconPath(const hstring& val);
|
||||
|
||||
@@ -86,12 +93,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
hstring iconPath);
|
||||
|
||||
WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
|
||||
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);
|
||||
WINRT_PROPERTY(OriginTag, Origin);
|
||||
WINRT_PROPERTY(winrt::hstring, Description, L"");
|
||||
|
||||
public:
|
||||
til::typed_event<Model::Command, winrt::hstring> IDChanged;
|
||||
|
||||
private:
|
||||
Json::Value _originalJson;
|
||||
Model::ActionAndArgs _ActionAndArgs{};
|
||||
Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command> _subcommands{ nullptr };
|
||||
std::optional<std::wstring> _name;
|
||||
std::wstring _ID;
|
||||
|
||||
@@ -35,13 +35,17 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
[default_interface] runtimeclass Command : ISettingsModelObject
|
||||
{
|
||||
Command();
|
||||
static Command NewUserCommand();
|
||||
static Command CopyAsUserCommand(Command originalCmd);
|
||||
|
||||
String Name { get; };
|
||||
String ID { get; };
|
||||
String Name;
|
||||
Boolean HasName();
|
||||
String ID;
|
||||
void GenerateID();
|
||||
|
||||
String Description { get; };
|
||||
|
||||
ActionAndArgs ActionAndArgs { get; };
|
||||
ActionAndArgs ActionAndArgs;
|
||||
|
||||
String IconPath;
|
||||
|
||||
@@ -51,5 +55,6 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
static IVector<Command> ParsePowerShellMenuComplete(String json, Int32 replaceLength);
|
||||
static IVector<Command> HistoryToCommands(IVector<String> commandHistory, String commandline, Boolean directories, String iconPath);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Command, String> IDChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle);
|
||||
|
||||
// Actions
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::ResizeDirection, ResizeDirection);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::FocusDirection, FocusDirection);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SplitDirection, SplitDirection);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SplitType, SplitType);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SettingsTarget, SettingsTarget);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::MoveTabDirection, MoveTabDirection);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::ScrollToMarkDirection, ScrollToMarkDirection);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode, CommandPaletteLaunchMode);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SuggestionsSource, SuggestionsSource);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::FindMatchDirection, FindMatchDirection);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::DesktopBehavior, DesktopBehavior);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::MonitorBehavior, MonitorBehavior);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::ClearBufferType, ClearBufferType);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SelectOutputDirection, SelectOutputDirection);
|
||||
|
||||
// FontWeight is special because the JsonUtils::ConversionTrait for it
|
||||
// creates a FontWeight object, but we need to use the uint16_t value.
|
||||
winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint16_t> EnumMappings::FontWeight()
|
||||
|
||||
@@ -49,6 +49,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle> IntenseTextStyle();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Core::AdjustTextMode> AdjustIndistinguishableColors();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::PathTranslationStyle> PathTranslationStyle();
|
||||
|
||||
// Actions
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::ResizeDirection> ResizeDirection();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::FocusDirection> FocusDirection();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SplitDirection> SplitDirection();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SplitType> SplitType();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SettingsTarget> SettingsTarget();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::MoveTabDirection> MoveTabDirection();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::ScrollToMarkDirection> ScrollToMarkDirection();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode> CommandPaletteLaunchMode();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SuggestionsSource> SuggestionsSource();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::FindMatchDirection> FindMatchDirection();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::DesktopBehavior> DesktopBehavior();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::MonitorBehavior> MonitorBehavior();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::ClearBufferType> ClearBufferType();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SelectOutputDirection> SelectOutputDirection();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -31,5 +31,21 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
static Windows.Foundation.Collections.IMap<String, UInt16> FontWeight { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.IntenseStyle> IntenseTextStyle { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.PathTranslationStyle> PathTranslationStyle { get; };
|
||||
|
||||
// Actions
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.ResizeDirection> ResizeDirection { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.FocusDirection> FocusDirection { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SplitDirection> SplitDirection { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SplitType> SplitType { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SettingsTarget> SettingsTarget { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.MoveTabDirection> MoveTabDirection { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.ScrollToMarkDirection> ScrollToMarkDirection { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.CommandPaletteLaunchMode> CommandPaletteLaunchMode { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SuggestionsSource> SuggestionsSource { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.FindMatchDirection> FindMatchDirection { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.DesktopBehavior> DesktopBehavior { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.MonitorBehavior> MonitorBehavior { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.ClearBufferType> ClearBufferType { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SelectOutputDirection> SelectOutputDirection { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,14 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
globals->_NewTabMenu->Append(get_self<NewTabMenuEntry>(entry)->Copy());
|
||||
}
|
||||
}
|
||||
if (_DisabledProfileSources)
|
||||
{
|
||||
globals->_DisabledProfileSources = winrt::single_threaded_vector<hstring>();
|
||||
for (const auto& src : *_DisabledProfileSources)
|
||||
{
|
||||
globals->_DisabledProfileSources->Append(src);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
public:
|
||||
virtual ~IDynamicProfileGenerator() = default;
|
||||
virtual std::wstring_view GetNamespace() const noexcept = 0;
|
||||
virtual void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const = 0;
|
||||
virtual std::wstring_view GetDisplayName() const noexcept = 0;
|
||||
virtual std::wstring_view GetIcon() const noexcept = 0;
|
||||
virtual void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) = 0;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -136,7 +136,7 @@ Author(s):
|
||||
X(ConvergedAlignment, BackgroundImageAlignment, "backgroundImageAlignment", ConvergedAlignment::Horizontal_Center | ConvergedAlignment::Vertical_Center) \
|
||||
X(hstring, BackgroundImagePath, "backgroundImage") \
|
||||
X(Model::IntenseStyle, IntenseTextStyle, "intenseTextStyle", Model::IntenseStyle::Bright) \
|
||||
X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Never) \
|
||||
X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Automatic) \
|
||||
X(bool, UseAcrylic, "useAcrylic", false)
|
||||
|
||||
// Intentionally omitted Appearance settings:
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
<DependentUpon>KeyChordSerialization.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PowershellCoreProfileGenerator.h" />
|
||||
<ClInclude Include="PowershellInstallationProfileGenerator.h" />
|
||||
<ClInclude Include="Profile.h">
|
||||
<DependentUpon>Profile.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -167,6 +168,7 @@
|
||||
<DependentUpon>KeyChordSerialization.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PowershellCoreProfileGenerator.cpp" />
|
||||
<ClCompile Include="PowershellInstallationProfileGenerator.cpp" />
|
||||
<ClCompile Include="Profile.cpp">
|
||||
<DependentUpon>Profile.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
<ClCompile Include="PowershellCoreProfileGenerator.cpp">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PowershellInstallationProfileGenerator.cpp">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WslDistroGenerator.cpp">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClCompile>
|
||||
@@ -57,6 +60,9 @@
|
||||
<ClInclude Include="PowershellCoreProfileGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PowershellInstallationProfileGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WslDistroGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -15,334 +15,311 @@
|
||||
#include <winrt/Windows.Management.Deployment.h>
|
||||
#include <appmodel.h>
|
||||
#include <shlobj.h>
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::wstring_view POWERSHELL_PFN{ L"Microsoft.PowerShell_8wekyb3d8bbwe" };
|
||||
static constexpr std::wstring_view POWERSHELL_PREVIEW_PFN{ L"Microsoft.PowerShellPreview_8wekyb3d8bbwe" };
|
||||
static constexpr std::wstring_view PWSH_EXE{ L"pwsh.exe" };
|
||||
static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" };
|
||||
static constexpr std::wstring_view POWERSHELL_PREVIEW_ICON{ L"ms-appx:///ProfileIcons/pwsh-preview.png" };
|
||||
static constexpr std::wstring_view GENERATOR_POWERSHELL_ICON{ L"ms-appx:///ProfileGeneratorIcons/PowerShell.png" };
|
||||
static constexpr std::wstring_view POWERSHELL_PREFERRED_PROFILE_NAME{ L"PowerShell" };
|
||||
|
||||
namespace
|
||||
{
|
||||
enum PowerShellFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// These flags are used as a sort key, so they encode some native ordering.
|
||||
// They are ordered such that the "most important" flags have the largest
|
||||
// impact on the sort space. For example, since we want Preview to be very polar
|
||||
// we give it the highest flag value.
|
||||
// The "ideal" powershell instance has 0 flags (stable, native, Program Files location)
|
||||
//
|
||||
// With this ordering, the sort space ends up being (for PowerShell 6)
|
||||
// (numerically greater values are on the left; this is flipped in the final sort)
|
||||
//
|
||||
// <-- Less Valued .................................... More Valued -->
|
||||
// | All instances of PS 6 | All PS7 |
|
||||
// | Preview | Stable | ~~~ |
|
||||
// | Non-Native | Native | Non-Native | Native | ~~~ |
|
||||
// | Trd | Pack | Trd | Pack | Trd | Pack | Trd | Pack | ~~~ |
|
||||
// (where Pack is a stand-in for store, scoop, dotnet, though they have their own orders,
|
||||
// and Trd is a stand-in for "Traditional" (Program Files))
|
||||
//
|
||||
// In short, flags with larger magnitudes are pushed further down (therefore valued less)
|
||||
|
||||
// distribution method (choose one)
|
||||
Store = 1 << 0, // distributed via the store
|
||||
Scoop = 1 << 1, // installed via Scoop
|
||||
Dotnet = 1 << 2, // installed as a dotnet global tool
|
||||
Traditional = 1 << 3, // installed in traditional Program Files locations
|
||||
|
||||
// native architecture (choose one)
|
||||
WOWARM = 1 << 4, // non-native (Windows-on-Windows, ARM variety)
|
||||
WOWx86 = 1 << 5, // non-native (Windows-on-Windows, x86 variety)
|
||||
|
||||
// build type (choose one)
|
||||
Preview = 1 << 6, // preview version
|
||||
};
|
||||
DEFINE_ENUM_FLAG_OPERATORS(PowerShellFlags);
|
||||
|
||||
struct PowerShellInstance
|
||||
{
|
||||
int majorVersion; // 0 = we don't know, sort last.
|
||||
PowerShellFlags flags;
|
||||
std::filesystem::path executablePath;
|
||||
|
||||
constexpr bool operator<(const PowerShellInstance& second) const
|
||||
{
|
||||
if (majorVersion != second.majorVersion)
|
||||
{
|
||||
return majorVersion < second.majorVersion;
|
||||
}
|
||||
if (flags != second.flags)
|
||||
{
|
||||
return flags > second.flags; // flags are inverted because "0" is ideal; see above
|
||||
}
|
||||
return executablePath < second.executablePath; // fall back to path sorting
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Generates a name, based on flags, for a powershell instance.
|
||||
// Return value:
|
||||
// - the name
|
||||
std::wstring Name() const
|
||||
{
|
||||
std::wstringstream namestream;
|
||||
namestream << L"PowerShell";
|
||||
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::Store))
|
||||
{
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::Preview))
|
||||
{
|
||||
namestream << L" Preview";
|
||||
}
|
||||
namestream << L" (msix)";
|
||||
}
|
||||
else if (WI_IsFlagSet(flags, PowerShellFlags::Dotnet))
|
||||
{
|
||||
namestream << L" (dotnet global)";
|
||||
}
|
||||
else if (WI_IsFlagSet(flags, PowerShellFlags::Scoop))
|
||||
{
|
||||
namestream << L" (scoop)";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (majorVersion < 7)
|
||||
{
|
||||
namestream << L" Core";
|
||||
}
|
||||
if (majorVersion != 0)
|
||||
{
|
||||
namestream << L" " << majorVersion;
|
||||
}
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::Preview))
|
||||
{
|
||||
namestream << L" Preview";
|
||||
}
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::WOWx86))
|
||||
{
|
||||
namestream << L" (x86)";
|
||||
}
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::WOWARM))
|
||||
{
|
||||
namestream << L" (ARM)";
|
||||
}
|
||||
}
|
||||
return namestream.str();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
using namespace ::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
// Function Description:
|
||||
// - Finds all powershell instances with the traditional layout under a directory.
|
||||
// - The "traditional" directory layout requires that pwsh.exe exist in a versioned directory, as in
|
||||
// ROOT\6\pwsh.exe
|
||||
// Arguments:
|
||||
// - directory: the directory under which to search
|
||||
// - flags: flags to apply to all found instances
|
||||
// - out: the list into which to accumulate these instances.
|
||||
static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstring_view directory, PowerShellFlags flags, std::vector<PowerShellInstance>& out)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(directory.data()) };
|
||||
if (std::filesystem::exists(root))
|
||||
DEFINE_ENUM_FLAG_OPERATORS(PowershellCoreProfileGenerator::PowerShellFlags);
|
||||
|
||||
constexpr bool PowershellCoreProfileGenerator::PowerShellInstance::operator<(const PowerShellInstance& second) const
|
||||
{
|
||||
for (const auto& versionedDir : std::filesystem::directory_iterator(root))
|
||||
if (majorVersion != second.majorVersion)
|
||||
{
|
||||
const auto versionedPath = versionedDir.path();
|
||||
const auto executable = versionedPath / PWSH_EXE;
|
||||
if (std::filesystem::exists(executable))
|
||||
return majorVersion < second.majorVersion;
|
||||
}
|
||||
if (flags != second.flags)
|
||||
{
|
||||
return flags > second.flags; // flags are inverted because "0" is ideal; see above
|
||||
}
|
||||
return executablePath < second.executablePath; // fall back to path sorting
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Generates a name, based on flags, for a powershell instance.
|
||||
// Return value:
|
||||
// - the name
|
||||
std::wstring PowershellCoreProfileGenerator::PowerShellInstance::Name() const
|
||||
{
|
||||
std::wstringstream namestream;
|
||||
namestream << L"PowerShell";
|
||||
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::Store))
|
||||
{
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::Preview))
|
||||
{
|
||||
const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos;
|
||||
const auto previewFlag = preview ? PowerShellFlags::Preview : PowerShellFlags::None;
|
||||
out.emplace_back(PowerShellInstance{ std::stoi(versionedPath.filename()),
|
||||
PowerShellFlags::Traditional | flags | previewFlag,
|
||||
executable });
|
||||
namestream << L" Preview";
|
||||
}
|
||||
namestream << L" (msix)";
|
||||
}
|
||||
else if (WI_IsFlagSet(flags, PowerShellFlags::Dotnet))
|
||||
{
|
||||
namestream << L" (dotnet global)";
|
||||
}
|
||||
else if (WI_IsFlagSet(flags, PowerShellFlags::Scoop))
|
||||
{
|
||||
namestream << L" (scoop)";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (majorVersion < 7)
|
||||
{
|
||||
namestream << L" Core";
|
||||
}
|
||||
if (majorVersion != 0)
|
||||
{
|
||||
namestream << L" " << majorVersion;
|
||||
}
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::Preview))
|
||||
{
|
||||
namestream << L" Preview";
|
||||
}
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::WOWx86))
|
||||
{
|
||||
namestream << L" (x86)";
|
||||
}
|
||||
if (WI_IsFlagSet(flags, PowerShellFlags::WOWARM))
|
||||
{
|
||||
namestream << L" (ARM)";
|
||||
}
|
||||
}
|
||||
return namestream.str();
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Finds all powershell instances with the traditional layout under a directory.
|
||||
// - The "traditional" directory layout requires that pwsh.exe exist in a versioned directory, as in
|
||||
// ROOT\6\pwsh.exe
|
||||
// Arguments:
|
||||
// - directory: the directory under which to search
|
||||
// - flags: flags to apply to all found instances
|
||||
// - out: the list into which to accumulate these instances.
|
||||
static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstring_view directory, PowershellCoreProfileGenerator::PowerShellFlags flags, std::vector<PowershellCoreProfileGenerator::PowerShellInstance>& out)
|
||||
{
|
||||
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(directory.data()) };
|
||||
if (std::filesystem::exists(root))
|
||||
{
|
||||
for (const auto& versionedDir : std::filesystem::directory_iterator(root))
|
||||
{
|
||||
const auto versionedPath = versionedDir.path();
|
||||
const auto executable = versionedPath / PWSH_EXE;
|
||||
if (std::filesystem::exists(executable))
|
||||
{
|
||||
const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos;
|
||||
const auto previewFlag = preview ? PowershellCoreProfileGenerator::PowerShellFlags::Preview : PowershellCoreProfileGenerator::PowerShellFlags::None;
|
||||
out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{ std::stoi(versionedPath.filename()),
|
||||
PowershellCoreProfileGenerator::PowerShellFlags::Traditional | flags | previewFlag,
|
||||
executable });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Finds the store package, if one exists, for a given package family name
|
||||
// Arguments:
|
||||
// - packageFamilyName: the package family name
|
||||
// Return Value:
|
||||
// - a package, or nullptr.
|
||||
static winrt::Windows::ApplicationModel::Package _getStorePackage(const std::wstring_view packageFamilyName) noexcept
|
||||
try
|
||||
{
|
||||
winrt::Windows::Management::Deployment::PackageManager packageManager;
|
||||
auto foundPackages = packageManager.FindPackagesForUser(L"", packageFamilyName);
|
||||
auto iterator = foundPackages.First();
|
||||
if (!iterator.HasCurrent())
|
||||
// Function Description:
|
||||
// - Finds the store package, if one exists, for a given package family name
|
||||
// Arguments:
|
||||
// - packageFamilyName: the package family name
|
||||
// Return Value:
|
||||
// - a package, or nullptr.
|
||||
static winrt::Windows::ApplicationModel::Package _getStorePackage(const std::wstring_view packageFamilyName) noexcept
|
||||
try
|
||||
{
|
||||
winrt::Windows::Management::Deployment::PackageManager packageManager;
|
||||
auto foundPackages = packageManager.FindPackagesForUser(L"", packageFamilyName);
|
||||
auto iterator = foundPackages.First();
|
||||
if (!iterator.HasCurrent())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return iterator.Current();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return nullptr;
|
||||
}
|
||||
return iterator.Current();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Finds all powershell instances that have App Execution Aliases in the standard location
|
||||
// Arguments:
|
||||
// - out: the list into which to accumulate these instances.
|
||||
static void _accumulateStorePowerShellInstances(std::vector<PowerShellInstance>& out)
|
||||
{
|
||||
wil::unique_cotaskmem_string localAppDataFolder;
|
||||
if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder)))
|
||||
// Function Description:
|
||||
// - Finds all powershell instances that have App Execution Aliases in the standard location
|
||||
// Arguments:
|
||||
// - out: the list into which to accumulate these instances.
|
||||
static void _accumulateStorePowerShellInstances(std::vector<PowershellCoreProfileGenerator::PowerShellInstance>& out)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path appExecAliasPath{ localAppDataFolder.get() };
|
||||
appExecAliasPath /= L"Microsoft";
|
||||
appExecAliasPath /= L"WindowsApps";
|
||||
|
||||
if (std::filesystem::exists(appExecAliasPath))
|
||||
{
|
||||
// App execution aliases for preview powershell
|
||||
const auto previewPath = appExecAliasPath / POWERSHELL_PREVIEW_PFN;
|
||||
if (std::filesystem::exists(previewPath))
|
||||
wil::unique_cotaskmem_string localAppDataFolder;
|
||||
if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder)))
|
||||
{
|
||||
const auto previewPackage = _getStorePackage(POWERSHELL_PREVIEW_PFN);
|
||||
if (previewPackage)
|
||||
{
|
||||
out.emplace_back(PowerShellInstance{
|
||||
gsl::narrow_cast<int>(previewPackage.Id().Version().Major),
|
||||
PowerShellFlags::Store | PowerShellFlags::Preview,
|
||||
previewPath / PWSH_EXE });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// App execution aliases for stable powershell
|
||||
const auto gaPath = appExecAliasPath / POWERSHELL_PFN;
|
||||
if (std::filesystem::exists(gaPath))
|
||||
std::filesystem::path appExecAliasPath{ localAppDataFolder.get() };
|
||||
appExecAliasPath /= L"Microsoft";
|
||||
appExecAliasPath /= L"WindowsApps";
|
||||
|
||||
if (std::filesystem::exists(appExecAliasPath))
|
||||
{
|
||||
const auto gaPackage = _getStorePackage(POWERSHELL_PFN);
|
||||
if (gaPackage)
|
||||
// App execution aliases for preview powershell
|
||||
const auto previewPath = appExecAliasPath / POWERSHELL_PREVIEW_PFN;
|
||||
if (std::filesystem::exists(previewPath))
|
||||
{
|
||||
out.emplace_back(PowerShellInstance{
|
||||
gaPackage.Id().Version().Major,
|
||||
PowerShellFlags::Store,
|
||||
gaPath / PWSH_EXE,
|
||||
});
|
||||
const auto previewPackage = _getStorePackage(POWERSHELL_PREVIEW_PFN);
|
||||
if (previewPackage)
|
||||
{
|
||||
out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{
|
||||
gsl::narrow_cast<int>(previewPackage.Id().Version().Major),
|
||||
PowershellCoreProfileGenerator::PowerShellFlags::Store | PowershellCoreProfileGenerator::PowerShellFlags::Preview,
|
||||
previewPath / PWSH_EXE });
|
||||
}
|
||||
}
|
||||
|
||||
// App execution aliases for stable powershell
|
||||
const auto gaPath = appExecAliasPath / POWERSHELL_PFN;
|
||||
if (std::filesystem::exists(gaPath))
|
||||
{
|
||||
const auto gaPackage = _getStorePackage(POWERSHELL_PFN);
|
||||
if (gaPackage)
|
||||
{
|
||||
out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{
|
||||
gaPackage.Id().Version().Major,
|
||||
PowershellCoreProfileGenerator::PowerShellFlags::Store,
|
||||
gaPath / PWSH_EXE,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Finds a powershell instance that's just a pwsh.exe in a folder.
|
||||
// - This function cannot determine the version number of such a powershell instance.
|
||||
// Arguments:
|
||||
// - directory: the directory under which to search
|
||||
// - flags: flags to apply to all found instances
|
||||
// - out: the list into which to accumulate these instances.
|
||||
static void _accumulatePwshExeInDirectory(const std::wstring_view directory, const PowerShellFlags flags, std::vector<PowerShellInstance>& out)
|
||||
{
|
||||
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(directory.data()) };
|
||||
const auto pwshPath = root / PWSH_EXE;
|
||||
if (std::filesystem::exists(pwshPath))
|
||||
// Function Description:
|
||||
// - Finds a powershell instance that's just a pwsh.exe in a folder.
|
||||
// - This function cannot determine the version number of such a powershell instance.
|
||||
// Arguments:
|
||||
// - directory: the directory under which to search
|
||||
// - flags: flags to apply to all found instances
|
||||
// - out: the list into which to accumulate these instances.
|
||||
static void _accumulatePwshExeInDirectory(const std::wstring_view directory, const PowershellCoreProfileGenerator::PowerShellFlags flags, std::vector<PowershellCoreProfileGenerator::PowerShellInstance>& out)
|
||||
{
|
||||
out.emplace_back(PowerShellInstance{ 0 /* we can't tell */, flags, pwshPath });
|
||||
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(directory.data()) };
|
||||
const auto pwshPath = root / PWSH_EXE;
|
||||
if (std::filesystem::exists(pwshPath))
|
||||
{
|
||||
out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{ 0 /* we can't tell */, flags, pwshPath });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Builds a comprehensive priority-ordered list of powershell instances.
|
||||
// Return value:
|
||||
// - a comprehensive priority-ordered list of powershell instances.
|
||||
static std::vector<PowerShellInstance> _collectPowerShellInstances()
|
||||
{
|
||||
std::vector<PowerShellInstance> versions;
|
||||
// Function Description:
|
||||
// - Builds a comprehensive priority-ordered list of powershell instances.
|
||||
// Return value:
|
||||
// - a comprehensive priority-ordered list of powershell instances.
|
||||
static std::vector<PowershellCoreProfileGenerator::PowerShellInstance> _collectPowerShellInstances()
|
||||
{
|
||||
std::vector<PowershellCoreProfileGenerator::PowerShellInstance> versions;
|
||||
|
||||
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles%\\PowerShell", PowerShellFlags::None, versions);
|
||||
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::None, versions);
|
||||
|
||||
#if defined(_M_AMD64) || defined(_M_ARM64) // No point in looking for WOW if we're not somewhere it exists
|
||||
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(x86)%\\PowerShell", PowerShellFlags::WOWx86, versions);
|
||||
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(x86)%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::WOWx86, versions);
|
||||
#endif
|
||||
|
||||
#if defined(_M_ARM64) // no point in looking for WOA if we're not on ARM64
|
||||
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(Arm)%\\PowerShell", PowerShellFlags::WOWARM, versions);
|
||||
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(Arm)%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::WOWARM, versions);
|
||||
#endif
|
||||
|
||||
_accumulateStorePowerShellInstances(versions);
|
||||
_accumulateStorePowerShellInstances(versions);
|
||||
|
||||
_accumulatePwshExeInDirectory(L"%USERPROFILE%\\.dotnet\\tools", PowerShellFlags::Dotnet, versions);
|
||||
_accumulatePwshExeInDirectory(L"%USERPROFILE%\\scoop\\shims", PowerShellFlags::Scoop, versions);
|
||||
_accumulatePwshExeInDirectory(L"%USERPROFILE%\\.dotnet\\tools", PowershellCoreProfileGenerator::PowerShellFlags::Dotnet, versions);
|
||||
_accumulatePwshExeInDirectory(L"%USERPROFILE%\\scoop\\shims", PowershellCoreProfileGenerator::PowerShellFlags::Scoop, versions);
|
||||
|
||||
std::sort(versions.rbegin(), versions.rend()); // sort in reverse (best first)
|
||||
std::sort(versions.rbegin(), versions.rend()); // sort in reverse (best first)
|
||||
|
||||
return versions;
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
// Legacy GUIDs:
|
||||
// - PowerShell Core 574e775e-4f2a-5b96-ac1e-a2962a402336
|
||||
static constexpr winrt::guid PowershellCoreGuid{ 0x574e775e, 0x4f2a, 0x5b96, { 0xac, 0x1e, 0xa2, 0x96, 0x2a, 0x40, 0x23, 0x36 } };
|
||||
// Legacy GUIDs:
|
||||
// - PowerShell Core 574e775e-4f2a-5b96-ac1e-a2962a402336
|
||||
static constexpr winrt::guid PowershellCoreGuid{ 0x574e775e, 0x4f2a, 0x5b96, { 0xac, 0x1e, 0xa2, 0x96, 0x2a, 0x40, 0x23, 0x36 } };
|
||||
|
||||
std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept
|
||||
{
|
||||
return PowershellCoreGeneratorNamespace;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Checks if pwsh is installed, and if it is, creates a profile to launch it.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a vector with the PowerShell Core profile, if available.
|
||||
void PowershellCoreProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
{
|
||||
const auto psInstances = _collectPowerShellInstances();
|
||||
auto first = true;
|
||||
|
||||
for (const auto& psI : psInstances)
|
||||
std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept
|
||||
{
|
||||
const auto name = psI.Name();
|
||||
auto profile{ CreateDynamicProfile(name) };
|
||||
return PowershellCoreGeneratorNamespace;
|
||||
}
|
||||
|
||||
const auto& unquotedCommandline = psI.executablePath.native();
|
||||
std::wstring quotedCommandline;
|
||||
quotedCommandline.reserve(unquotedCommandline.size() + 2);
|
||||
quotedCommandline.push_back(L'"');
|
||||
quotedCommandline.append(unquotedCommandline);
|
||||
quotedCommandline.push_back(L'"');
|
||||
profile->Commandline(winrt::hstring{ quotedCommandline });
|
||||
std::wstring_view PowershellCoreProfileGenerator::GetDisplayName() const noexcept
|
||||
{
|
||||
return RS_(L"PowershellCoreProfileGeneratorDisplayName");
|
||||
}
|
||||
|
||||
profile->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY });
|
||||
profile->DefaultAppearance().DarkColorSchemeName(L"Campbell");
|
||||
profile->DefaultAppearance().LightColorSchemeName(L"Campbell");
|
||||
profile->Icon(winrt::hstring{ WI_IsFlagSet(psI.flags, PowerShellFlags::Preview) ? POWERSHELL_PREVIEW_ICON : POWERSHELL_ICON });
|
||||
std::wstring_view PowershellCoreProfileGenerator::GetIcon() const noexcept
|
||||
{
|
||||
return GENERATOR_POWERSHELL_ICON;
|
||||
}
|
||||
|
||||
if (first)
|
||||
// Method Description:
|
||||
// - Checks if pwsh is installed, and if it is, creates a profile to launch it.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a vector with the PowerShell Core profile, if available.
|
||||
void PowershellCoreProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
GetPowerShellInstances();
|
||||
auto first = true;
|
||||
|
||||
for (const auto& psI : _powerShellInstances)
|
||||
{
|
||||
// Give the first ("algorithmically best") profile the official, and original, "PowerShell Core" GUID.
|
||||
// This will turn the anchored default profile into "PowerShell Core Latest for Native Architecture through Store"
|
||||
// (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version.
|
||||
profile->Guid(PowershellCoreGuid);
|
||||
profile->Name(winrt::hstring{ POWERSHELL_PREFERRED_PROFILE_NAME });
|
||||
const auto name = psI.Name();
|
||||
auto profile{ CreateDynamicProfile(name) };
|
||||
|
||||
first = false;
|
||||
const auto& unquotedCommandline = psI.executablePath.native();
|
||||
std::wstring quotedCommandline;
|
||||
quotedCommandline.reserve(unquotedCommandline.size() + 2);
|
||||
quotedCommandline.push_back(L'"');
|
||||
quotedCommandline.append(unquotedCommandline);
|
||||
quotedCommandline.push_back(L'"');
|
||||
profile->Commandline(winrt::hstring{ quotedCommandline });
|
||||
|
||||
profile->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY });
|
||||
profile->DefaultAppearance().DarkColorSchemeName(L"Campbell");
|
||||
profile->DefaultAppearance().LightColorSchemeName(L"Campbell");
|
||||
profile->Icon(winrt::hstring{ WI_IsFlagSet(psI.flags, PowerShellFlags::Preview) ? POWERSHELL_PREVIEW_ICON : POWERSHELL_ICON });
|
||||
|
||||
if (first)
|
||||
{
|
||||
// Give the first ("algorithmically best") profile the official, and original, "PowerShell Core" GUID.
|
||||
// This will turn the anchored default profile into "PowerShell Core Latest for Native Architecture through Store"
|
||||
// (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version.
|
||||
profile->Guid(PowershellCoreGuid);
|
||||
profile->Name(winrt::hstring{ POWERSHELL_PREFERRED_PROFILE_NAME });
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
profiles.emplace_back(std::move(profile));
|
||||
}
|
||||
}
|
||||
|
||||
profiles.emplace_back(std::move(profile));
|
||||
std::vector<PowershellCoreProfileGenerator::PowerShellInstance> PowershellCoreProfileGenerator::GetPowerShellInstances() noexcept
|
||||
{
|
||||
if (_powerShellInstances.empty())
|
||||
{
|
||||
_powerShellInstances = _collectPowerShellInstances();
|
||||
}
|
||||
return _powerShellInstances;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Returns the thing it's named for.
|
||||
// Return value:
|
||||
// - the thing it says in the name
|
||||
const std::wstring_view PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()
|
||||
{
|
||||
return POWERSHELL_PREFERRED_PROFILE_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Returns the thing it's named for.
|
||||
// Return value:
|
||||
// - the thing it says in the name
|
||||
const std::wstring_view PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()
|
||||
{
|
||||
return POWERSHELL_PREFERRED_PROFILE_NAME;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,60 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
public:
|
||||
static const std::wstring_view GetPreferredPowershellProfileName();
|
||||
|
||||
enum PowerShellFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// These flags are used as a sort key, so they encode some native ordering.
|
||||
// They are ordered such that the "most important" flags have the largest
|
||||
// impact on the sort space. For example, since we want Preview to be very polar
|
||||
// we give it the highest flag value.
|
||||
// The "ideal" powershell instance has 0 flags (stable, native, Program Files location)
|
||||
//
|
||||
// With this ordering, the sort space ends up being (for PowerShell 6)
|
||||
// (numerically greater values are on the left; this is flipped in the final sort)
|
||||
//
|
||||
// <-- Less Valued .................................... More Valued -->
|
||||
// | All instances of PS 6 | All PS7 |
|
||||
// | Preview | Stable | ~~~ |
|
||||
// | Non-Native | Native | Non-Native | Native | ~~~ |
|
||||
// | Trd | Pack | Trd | Pack | Trd | Pack | Trd | Pack | ~~~ |
|
||||
// (where Pack is a stand-in for store, scoop, dotnet, though they have their own orders,
|
||||
// and Trd is a stand-in for "Traditional" (Program Files))
|
||||
//
|
||||
// In short, flags with larger magnitudes are pushed further down (therefore valued less)
|
||||
|
||||
// distribution method (choose one)
|
||||
Store = 1 << 0, // distributed via the store
|
||||
Scoop = 1 << 1, // installed via Scoop
|
||||
Dotnet = 1 << 2, // installed as a dotnet global tool
|
||||
Traditional = 1 << 3, // installed in traditional Program Files locations
|
||||
|
||||
// native architecture (choose one)
|
||||
WOWARM = 1 << 4, // non-native (Windows-on-Windows, ARM variety)
|
||||
WOWx86 = 1 << 5, // non-native (Windows-on-Windows, x86 variety)
|
||||
|
||||
// build type (choose one)
|
||||
Preview = 1 << 6, // preview version
|
||||
};
|
||||
|
||||
struct PowerShellInstance
|
||||
{
|
||||
int majorVersion; // 0 = we don't know, sort last.
|
||||
PowerShellFlags flags;
|
||||
std::filesystem::path executablePath;
|
||||
|
||||
constexpr bool operator<(const PowerShellInstance& second) const;
|
||||
std::wstring Name() const;
|
||||
};
|
||||
|
||||
std::wstring_view GetNamespace() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
std::wstring_view GetDisplayName() const noexcept override;
|
||||
std::wstring_view GetIcon() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
std::vector<PowerShellInstance> GetPowerShellInstances() noexcept;
|
||||
|
||||
private:
|
||||
std::vector<PowerShellInstance> _powerShellInstances;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "PowershellInstallationProfileGenerator.h"
|
||||
#include "DynamicProfileUtils.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" };
|
||||
static constexpr std::wstring_view GENERATOR_POWERSHELL_ICON{ L"ms-appx:///ProfileGeneratorIcons/PowerShell.png" };
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
std::wstring_view PowershellInstallationProfileGenerator::Namespace{ L"Windows.Terminal.InstallPowerShell" };
|
||||
|
||||
std::wstring_view PowershellInstallationProfileGenerator::GetNamespace() const noexcept
|
||||
{
|
||||
return Namespace;
|
||||
}
|
||||
|
||||
std::wstring_view PowershellInstallationProfileGenerator::GetDisplayName() const noexcept
|
||||
{
|
||||
return RS_(L"PowerShellInstallationProfileGeneratorDisplayName");
|
||||
}
|
||||
|
||||
std::wstring_view PowershellInstallationProfileGenerator::GetIcon() const noexcept
|
||||
{
|
||||
return GENERATOR_POWERSHELL_ICON;
|
||||
}
|
||||
|
||||
void PowershellInstallationProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
auto profile{ CreateDynamicProfile(RS_(L"PowerShellInstallationProfileName")) };
|
||||
profile->Commandline(winrt::hstring{ fmt::format(FMT_COMPILE(L"cmd /k winget install --interactive --id Microsoft.PowerShell --source winget & echo. & echo {} & exit"), RS_(L"PowerShellInstallationInstallerGuidance")) });
|
||||
profile->Icon(winrt::hstring{ POWERSHELL_ICON });
|
||||
profile->CloseOnExit(CloseOnExitMode::Never);
|
||||
|
||||
profiles.emplace_back(std::move(profile));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- PowershellInstallationProfileGenerator
|
||||
|
||||
Abstract:
|
||||
- This is the dynamic profile generator for a PowerShell stub. Checks if pwsh is
|
||||
installed, and if it is NOT installed, creates a profile that installs the
|
||||
latest PowerShell.
|
||||
|
||||
Author(s):
|
||||
- Carlos Zamora - March 2025
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IDynamicProfileGenerator.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
class PowershellInstallationProfileGenerator final : public IDynamicProfileGenerator
|
||||
{
|
||||
public:
|
||||
static std::wstring_view Namespace;
|
||||
std::wstring_view GetNamespace() const noexcept override;
|
||||
std::wstring_view GetDisplayName() const noexcept override;
|
||||
std::wstring_view GetIcon() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
};
|
||||
};
|
||||
@@ -740,4 +740,309 @@
|
||||
<data name="OpenCWDCommandKey" xml:space="preserve">
|
||||
<value>Open current working directory</value>
|
||||
</data>
|
||||
<data name="WslDistroGeneratorDisplayName" xml:space="preserve">
|
||||
<value>WSL Distro Profile Generator</value>
|
||||
<comment>The display name of a dynamic profile generator for WSL distros</comment>
|
||||
</data>
|
||||
<data name="PowershellCoreProfileGeneratorDisplayName" xml:space="preserve">
|
||||
<value>PowerShell Profile Generator</value>
|
||||
<comment>The display name of a dynamic profile generator for PowerShell</comment>
|
||||
</data>
|
||||
<data name="PowershellInstallationProfileGeneratorDisplayName" xml:space="preserve">
|
||||
<value>PowerShell Installation Generator</value>
|
||||
<comment>The display name of a dynamic profile generator that installs the latest PowerShell</comment>
|
||||
</data>
|
||||
<data name="PowershellInstallationProfileName" xml:space="preserve">
|
||||
<value>Install Latest PowerShell</value>
|
||||
<comment>The display name of a profile generated by the PowerShellInstallationProfileGenerator. This profile installs the latest PowerShell.</comment>
|
||||
</data>
|
||||
<data name="AzureCloudShellGeneratorDisplayName" xml:space="preserve">
|
||||
<value>Azure Cloud Shell Profile Generator</value>
|
||||
<comment>The display name of a dynamic profile generator for Azure Cloud Shell</comment>
|
||||
</data>
|
||||
<data name="VisualStudioGeneratorDisplayName" xml:space="preserve">
|
||||
<value>Visual Studio Profile Generator</value>
|
||||
<comment>The display name of a dynamic profile generator for Visual Studio</comment>
|
||||
</data>
|
||||
<data name="SshHostGeneratorDisplayName" xml:space="preserve">
|
||||
<value>SSH Host Profile Generator</value>
|
||||
<comment>The display name of a dynamic profile generator for SSH hosts</comment>
|
||||
</data>
|
||||
<data name="PowerShellInstallationInstallerGuidance" xml:space="preserve">
|
||||
<value>Restart Windows Terminal to apply the new profile.</value>
|
||||
<comment>Guidance displayed by the installer directing the user to restart the app.</comment>
|
||||
</data>
|
||||
<data name="PowerShellInstallationProfileJsonComment" xml:space="preserve">
|
||||
<value>This profile only appears if PowerShell is not installed</value>
|
||||
</data>
|
||||
<data name="CopyText" xml:space="preserve">
|
||||
<value>Copy Text</value>
|
||||
</data>
|
||||
<data name="PasteText" xml:space="preserve">
|
||||
<value>Paste Text</value>
|
||||
</data>
|
||||
<data name="OpenNewTabDropdown" xml:space="preserve">
|
||||
<value>Open New Tab Dropdown</value>
|
||||
</data>
|
||||
<data name="DuplicateTab" xml:space="preserve">
|
||||
<value>Duplicate Tab</value>
|
||||
</data>
|
||||
<data name="NewTab" xml:space="preserve">
|
||||
<value>New Tab</value>
|
||||
</data>
|
||||
<data name="CloseWindow" xml:space="preserve">
|
||||
<value>Close Window</value>
|
||||
</data>
|
||||
<data name="CloseTab" xml:space="preserve">
|
||||
<value>Close Tab</value>
|
||||
</data>
|
||||
<data name="ClosePane" xml:space="preserve">
|
||||
<value>Close Pane</value>
|
||||
</data>
|
||||
<data name="NextTab" xml:space="preserve">
|
||||
<value>Next Tab</value>
|
||||
</data>
|
||||
<data name="PrevTab" xml:space="preserve">
|
||||
<value>Previous Tab</value>
|
||||
</data>
|
||||
<data name="SendInput" xml:space="preserve">
|
||||
<value>Send Input</value>
|
||||
</data>
|
||||
<data name="SplitPane" xml:space="preserve">
|
||||
<value>Split Pane</value>
|
||||
</data>
|
||||
<data name="ToggleSplitOrientation" xml:space="preserve">
|
||||
<value>Toggle Split Orientation</value>
|
||||
</data>
|
||||
<data name="TogglePaneZoom" xml:space="preserve">
|
||||
<value>Toggle Pane Zoom</value>
|
||||
</data>
|
||||
<data name="SwitchToTab" xml:space="preserve">
|
||||
<value>Switch To Tab</value>
|
||||
</data>
|
||||
<data name="AdjustFontSize" xml:space="preserve">
|
||||
<value>Adjust Font Size</value>
|
||||
</data>
|
||||
<data name="ResetFontSize" xml:space="preserve">
|
||||
<value>Reset Font Size</value>
|
||||
</data>
|
||||
<data name="ScrollUp" xml:space="preserve">
|
||||
<value>Scroll Up</value>
|
||||
</data>
|
||||
<data name="ScrollDown" xml:space="preserve">
|
||||
<value>Scroll Down</value>
|
||||
</data>
|
||||
<data name="ScrollUpPage" xml:space="preserve">
|
||||
<value>Scroll Up Page</value>
|
||||
</data>
|
||||
<data name="ScrollDownPage" xml:space="preserve">
|
||||
<value>Scroll Down Page</value>
|
||||
</data>
|
||||
<data name="ScrollToTop" xml:space="preserve">
|
||||
<value>Scroll To Top</value>
|
||||
</data>
|
||||
<data name="ScrollToBottom" xml:space="preserve">
|
||||
<value>Scroll To Bottom</value>
|
||||
</data>
|
||||
<data name="ScrollToMark" xml:space="preserve">
|
||||
<value>Scroll To Mark</value>
|
||||
</data>
|
||||
<data name="AddMark" xml:space="preserve">
|
||||
<value>Add Mark</value>
|
||||
</data>
|
||||
<data name="ClearMark" xml:space="preserve">
|
||||
<value>Clear Mark</value>
|
||||
</data>
|
||||
<data name="ClearAllMarks" xml:space="preserve">
|
||||
<value>Clear All Marks</value>
|
||||
</data>
|
||||
<data name="ResizePane" xml:space="preserve">
|
||||
<value>Resize Pane</value>
|
||||
</data>
|
||||
<data name="MoveFocus" xml:space="preserve">
|
||||
<value>Move Focus</value>
|
||||
</data>
|
||||
<data name="MovePane" xml:space="preserve">
|
||||
<value>Move Pane</value>
|
||||
</data>
|
||||
<data name="SwapPane" xml:space="preserve">
|
||||
<value>Swap Pane</value>
|
||||
</data>
|
||||
<data name="Find" xml:space="preserve">
|
||||
<value>Find</value>
|
||||
</data>
|
||||
<data name="ToggleShaderEffects" xml:space="preserve">
|
||||
<value>Toggle Shader Effects</value>
|
||||
</data>
|
||||
<data name="ToggleFocusMode" xml:space="preserve">
|
||||
<value>Toggle Focus Mode</value>
|
||||
</data>
|
||||
<data name="ToggleFullscreen" xml:space="preserve">
|
||||
<value>Toggle Fullscreen</value>
|
||||
</data>
|
||||
<data name="ToggleAlwaysOnTop" xml:space="preserve">
|
||||
<value>Toggle Always On Top</value>
|
||||
</data>
|
||||
<data name="OpenSettings" xml:space="preserve">
|
||||
<value>Open Settings</value>
|
||||
</data>
|
||||
<data name="SetFocusMode" xml:space="preserve">
|
||||
<value>Set Focus Mode</value>
|
||||
</data>
|
||||
<data name="SetFullScreen" xml:space="preserve">
|
||||
<value>Set Full Screen</value>
|
||||
</data>
|
||||
<data name="SetMaximized" xml:space="preserve">
|
||||
<value>Set Maximized</value>
|
||||
</data>
|
||||
<data name="SetColorScheme" xml:space="preserve">
|
||||
<value>Set Color Scheme</value>
|
||||
</data>
|
||||
<data name="SetTabColor" xml:space="preserve">
|
||||
<value>Set Tab Color</value>
|
||||
</data>
|
||||
<data name="OpenTabColorPicker" xml:space="preserve">
|
||||
<value>Open Tab Color Picker</value>
|
||||
</data>
|
||||
<data name="RenameTab" xml:space="preserve">
|
||||
<value>Rename Tab</value>
|
||||
</data>
|
||||
<data name="OpenTabRenamer" xml:space="preserve">
|
||||
<value>Open Tab Renamer</value>
|
||||
</data>
|
||||
<data name="ExecuteCommandline" xml:space="preserve">
|
||||
<value>Execute Command Line</value>
|
||||
</data>
|
||||
<data name="ToggleCommandPalette" xml:space="preserve">
|
||||
<value>Toggle Command Palette</value>
|
||||
</data>
|
||||
<data name="CloseOtherTabs" xml:space="preserve">
|
||||
<value>Close Other Tabs</value>
|
||||
</data>
|
||||
<data name="CloseTabsAfter" xml:space="preserve">
|
||||
<value>Close Tabs After</value>
|
||||
</data>
|
||||
<data name="TabSearch" xml:space="preserve">
|
||||
<value>Tab Search</value>
|
||||
</data>
|
||||
<data name="MoveTab" xml:space="preserve">
|
||||
<value>Move Tab</value>
|
||||
</data>
|
||||
<data name="BreakIntoDebugger" xml:space="preserve">
|
||||
<value>Break Into Debugger</value>
|
||||
</data>
|
||||
<data name="TogglePaneReadOnly" xml:space="preserve">
|
||||
<value>Toggle Pane Read-Only</value>
|
||||
</data>
|
||||
<data name="EnablePaneReadOnly" xml:space="preserve">
|
||||
<value>Enable Pane Read-Only</value>
|
||||
</data>
|
||||
<data name="DisablePaneReadOnly" xml:space="preserve">
|
||||
<value>Disable Pane Read-Only</value>
|
||||
</data>
|
||||
<data name="FindMatch" xml:space="preserve">
|
||||
<value>Find Match</value>
|
||||
</data>
|
||||
<data name="NewWindow" xml:space="preserve">
|
||||
<value>New Window</value>
|
||||
</data>
|
||||
<data name="IdentifyWindow" xml:space="preserve">
|
||||
<value>Identify Window</value>
|
||||
</data>
|
||||
<data name="IdentifyWindows" xml:space="preserve">
|
||||
<value>Identify Windows</value>
|
||||
</data>
|
||||
<data name="RenameWindow" xml:space="preserve">
|
||||
<value>Rename Window</value>
|
||||
</data>
|
||||
<data name="OpenWindowRenamer" xml:space="preserve">
|
||||
<value>Open Window Renamer</value>
|
||||
</data>
|
||||
<data name="DisplayWorkingDirectory" xml:space="preserve">
|
||||
<value>Display Working Directory</value>
|
||||
</data>
|
||||
<data name="SearchForText" xml:space="preserve">
|
||||
<value>Search For Text</value>
|
||||
</data>
|
||||
<data name="GlobalSummon" xml:space="preserve">
|
||||
<value>Global Summon</value>
|
||||
</data>
|
||||
<data name="QuakeMode" xml:space="preserve">
|
||||
<value>Quake Mode</value>
|
||||
</data>
|
||||
<data name="FocusPane" xml:space="preserve">
|
||||
<value>Focus Pane</value>
|
||||
</data>
|
||||
<data name="OpenSystemMenu" xml:space="preserve">
|
||||
<value>Open System Menu</value>
|
||||
</data>
|
||||
<data name="ExportBuffer" xml:space="preserve">
|
||||
<value>Export Buffer</value>
|
||||
</data>
|
||||
<data name="ClearBuffer" xml:space="preserve">
|
||||
<value>Clear Buffer</value>
|
||||
</data>
|
||||
<data name="MultipleActions" xml:space="preserve">
|
||||
<value>Multiple Actions</value>
|
||||
</data>
|
||||
<data name="Quit" xml:space="preserve">
|
||||
<value>Quit</value>
|
||||
</data>
|
||||
<data name="AdjustOpacity" xml:space="preserve">
|
||||
<value>Adjust Opacity</value>
|
||||
</data>
|
||||
<data name="RestoreLastClosed" xml:space="preserve">
|
||||
<value>Restore Last Closed</value>
|
||||
</data>
|
||||
<data name="SelectAll" xml:space="preserve">
|
||||
<value>Select All</value>
|
||||
</data>
|
||||
<data name="SelectCommand" xml:space="preserve">
|
||||
<value>Select Command</value>
|
||||
</data>
|
||||
<data name="SelectOutput" xml:space="preserve">
|
||||
<value>Select Output</value>
|
||||
</data>
|
||||
<data name="MarkMode" xml:space="preserve">
|
||||
<value>Mark Mode</value>
|
||||
</data>
|
||||
<data name="ToggleBlockSelection" xml:space="preserve">
|
||||
<value>Toggle Block Selection</value>
|
||||
</data>
|
||||
<data name="SwitchSelectionEndpoint" xml:space="preserve">
|
||||
<value>Switch Selection Endpoint</value>
|
||||
</data>
|
||||
<data name="Suggestions" xml:space="preserve">
|
||||
<value>Suggestions</value>
|
||||
</data>
|
||||
<data name="ColorSelection" xml:space="preserve">
|
||||
<value>Color Selection</value>
|
||||
</data>
|
||||
<data name="ShowContextMenu" xml:space="preserve">
|
||||
<value>Show Context Menu</value>
|
||||
</data>
|
||||
<data name="ExpandSelectionToWord" xml:space="preserve">
|
||||
<value>Expand Selection To Word</value>
|
||||
</data>
|
||||
<data name="CloseOtherPanes" xml:space="preserve">
|
||||
<value>Close Other Panes</value>
|
||||
</data>
|
||||
<data name="RestartConnection" xml:space="preserve">
|
||||
<value>Restart Connection</value>
|
||||
</data>
|
||||
<data name="ToggleBroadcastInput" xml:space="preserve">
|
||||
<value>Toggle Broadcast Input</value>
|
||||
</data>
|
||||
<data name="OpenScratchpad" xml:space="preserve">
|
||||
<value>Open Scratchpad</value>
|
||||
</data>
|
||||
<data name="OpenAbout" xml:space="preserve">
|
||||
<value>Open About</value>
|
||||
</data>
|
||||
<data name="QuickFix" xml:space="preserve">
|
||||
<value>Quick Fix</value>
|
||||
</data>
|
||||
<data name="OpenCWD" xml:space="preserve">
|
||||
<value>Open Current Working Directory</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
#include "../../inc/DefaultSettings.h"
|
||||
|
||||
#include "DynamicProfileUtils.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::wstring_view SshHostGeneratorNamespace{ L"Windows.Terminal.SSH" };
|
||||
|
||||
static constexpr std::wstring_view PROFILE_TITLE_PREFIX = L"SSH - ";
|
||||
static constexpr std::wstring_view PROFILE_ICON_PATH = L"ms-appx:///ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.png";
|
||||
static constexpr std::wstring_view GENERATOR_ICON_PATH = L"ms-appx:///ProfileGeneratorIcons/SSH.png";
|
||||
|
||||
// OpenSSH is installed under System32 when installed via Optional Features
|
||||
static constexpr std::wstring_view SSH_EXE_PATH1 = L"%SystemRoot%\\System32\\OpenSSH\\ssh.exe";
|
||||
@@ -132,13 +134,23 @@ std::wstring_view SshHostGenerator::GetNamespace() const noexcept
|
||||
return SshHostGeneratorNamespace;
|
||||
}
|
||||
|
||||
std::wstring_view SshHostGenerator::GetDisplayName() const noexcept
|
||||
{
|
||||
return RS_(L"SshHostGeneratorDisplayName");
|
||||
}
|
||||
|
||||
std::wstring_view SshHostGenerator::GetIcon() const noexcept
|
||||
{
|
||||
return GENERATOR_ICON_PATH;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Generate a list of profiles for each detected OpenSSH host.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <A list of SSH host profiles.>
|
||||
void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
std::wstring sshExePath;
|
||||
if (_tryFindSshExePath(sshExePath))
|
||||
|
||||
@@ -24,7 +24,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
public:
|
||||
std::wstring_view GetNamespace() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
std::wstring_view GetDisplayName() const noexcept override;
|
||||
std::wstring_view GetIcon() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
|
||||
private:
|
||||
static const std::wregex _configKeyValueRegex;
|
||||
|
||||
@@ -35,10 +35,11 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::CursorStyle)
|
||||
// - Helper for converting a user-specified adjustTextMode value to its corresponding enum
|
||||
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::AdjustTextMode)
|
||||
{
|
||||
JSON_MAPPINGS(3) = {
|
||||
JSON_MAPPINGS(4) = {
|
||||
pair_type{ "never", ValueType::Never },
|
||||
pair_type{ "indexed", ValueType::Indexed },
|
||||
pair_type{ "always", ValueType::Always },
|
||||
pair_type{ "automatic", ValueType::Automatic },
|
||||
};
|
||||
|
||||
// Override mapping parser to add boolean parsing
|
||||
|
||||
@@ -6,17 +6,29 @@
|
||||
#include "VisualStudioGenerator.h"
|
||||
#include "VsDevCmdGenerator.h"
|
||||
#include "VsDevShellGenerator.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
std::wstring_view VisualStudioGenerator::Namespace{ L"Windows.Terminal.VisualStudio" };
|
||||
static constexpr std::wstring_view IconPath{ L"ms-appx:///ProfileGeneratorIcons/VisualStudio.png" };
|
||||
|
||||
std::wstring_view VisualStudioGenerator::GetNamespace() const noexcept
|
||||
{
|
||||
return Namespace;
|
||||
}
|
||||
|
||||
void VisualStudioGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
std::wstring_view VisualStudioGenerator::GetDisplayName() const noexcept
|
||||
{
|
||||
return RS_(L"VisualStudioGeneratorDisplayName");
|
||||
}
|
||||
|
||||
std::wstring_view VisualStudioGenerator::GetIcon() const noexcept
|
||||
{
|
||||
return IconPath;
|
||||
}
|
||||
|
||||
void VisualStudioGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
const auto instances = VsSetupConfiguration::QueryInstances();
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
public:
|
||||
static std::wstring_view Namespace;
|
||||
std::wstring_view GetNamespace() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
std::wstring_view GetDisplayName() const noexcept override;
|
||||
std::wstring_view GetIcon() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
|
||||
class IVisualStudioProfileGenerator
|
||||
{
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
#include "../../inc/DefaultSettings.h"
|
||||
|
||||
#include "DynamicProfileUtils.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::wstring_view WslHomeDirectory{ L"~" };
|
||||
static constexpr std::wstring_view DockerDistributionPrefix{ L"docker-desktop" };
|
||||
static constexpr std::wstring_view RancherDistributionPrefix{ L"rancher-desktop" };
|
||||
static constexpr std::wstring_view IconPath{ L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png" };
|
||||
static constexpr std::wstring_view GeneratorIconPath{ L"ms-appx:///ProfileGeneratorIcons/WSL.png" };
|
||||
|
||||
// The WSL entries are structured as such:
|
||||
// HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss
|
||||
@@ -47,6 +50,16 @@ std::wstring_view WslDistroGenerator::GetNamespace() const noexcept
|
||||
return WslGeneratorNamespace;
|
||||
}
|
||||
|
||||
std::wstring_view WslDistroGenerator::GetDisplayName() const noexcept
|
||||
{
|
||||
return RS_(L"WslDistroGeneratorDisplayName");
|
||||
}
|
||||
|
||||
std::wstring_view WslDistroGenerator::GetIcon() const noexcept
|
||||
{
|
||||
return GeneratorIconPath;
|
||||
}
|
||||
|
||||
static winrt::com_ptr<implementation::Profile> makeProfile(const std::wstring& distName)
|
||||
{
|
||||
const auto WSLDistro{ CreateDynamicProfile(distName) };
|
||||
@@ -65,7 +78,7 @@ static winrt::com_ptr<implementation::Profile> makeProfile(const std::wstring& d
|
||||
{
|
||||
WSLDistro->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY });
|
||||
}
|
||||
WSLDistro->Icon(L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png");
|
||||
WSLDistro->Icon(winrt::hstring{ IconPath });
|
||||
WSLDistro->PathTranslationStyle(winrt::Microsoft::Terminal::Control::PathTranslationStyle::WSL);
|
||||
return WSLDistro;
|
||||
}
|
||||
@@ -226,7 +239,7 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey,
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - A list of WSL profiles.
|
||||
void WslDistroGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
void WslDistroGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
auto wslRootKey{ openWslRegKey() };
|
||||
if (wslRootKey)
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
public:
|
||||
std::wstring_view GetNamespace() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
std::wstring_view GetDisplayName() const noexcept override;
|
||||
std::wstring_view GetIcon() const noexcept override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
};
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user