mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-19 13:06:47 +00:00
Compare commits
289 Commits
dev/cazamo
...
selfhost-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c8e99689f | ||
|
|
274010f2f3 | ||
|
|
3606188eec | ||
|
|
4485e50cbc | ||
|
|
ab25a1e286 | ||
|
|
7802e20e40 | ||
|
|
e321c009a2 | ||
|
|
b968f9188c | ||
|
|
5460324d59 | ||
|
|
a2adcd38e9 | ||
|
|
d14a98409d | ||
|
|
cf50777605 | ||
|
|
253dedf477 | ||
|
|
ec1fbc2f9c | ||
|
|
d699a0874d | ||
|
|
bb47e9ea9a | ||
|
|
af6392302a | ||
|
|
eb3094ded2 | ||
|
|
23894780f8 | ||
|
|
3d9d32bb1f | ||
|
|
ba9a82068e | ||
|
|
406312f2f8 | ||
|
|
625753cc41 | ||
|
|
a80316dfa5 | ||
|
|
9703815f59 | ||
|
|
07efae4bee | ||
|
|
f3b4d82185 | ||
|
|
f11137a604 | ||
|
|
0dff336556 | ||
|
|
b88a8c5884 | ||
|
|
3f15d6e4c0 | ||
|
|
cf56a9a987 | ||
|
|
49a74c810f | ||
|
|
addf247d09 | ||
|
|
2d7a9d8949 | ||
|
|
a6f8d8c5f5 | ||
|
|
5fd110016f | ||
|
|
e923513d2a | ||
|
|
3ac5414352 | ||
|
|
57930ad432 | ||
|
|
2dc16e43a7 | ||
|
|
32234cafeb | ||
|
|
6c6dd46e02 | ||
|
|
51528e9cfd | ||
|
|
40b4aa2c94 | ||
|
|
ee6af59f10 | ||
|
|
d4d216ca33 | ||
|
|
7d00b25fbf | ||
|
|
ba375ec2a2 | ||
|
|
5e48a45ba2 | ||
|
|
14d83b5f5c | ||
|
|
3e31bda6f2 | ||
|
|
4d35c14966 | ||
|
|
abef25d29c | ||
|
|
7793c5c5bc | ||
|
|
7b8b60bdef | ||
|
|
ccf1cc9e83 | ||
|
|
ebc03e98f4 | ||
|
|
80fc299c10 | ||
|
|
43d46dbc99 | ||
|
|
0ec5aef050 | ||
|
|
02a1e37aae | ||
|
|
0480d651cb | ||
|
|
193e5733bf | ||
|
|
2b16acd4cf | ||
|
|
f35bf206e6 | ||
|
|
78711f91d5 | ||
|
|
44dfa21748 | ||
|
|
c78a5302e8 | ||
|
|
cdb907d94d | ||
|
|
3e601f5b66 | ||
|
|
c2c75c80ed | ||
|
|
3c6015d97b | ||
|
|
45cfcd6eca | ||
|
|
ca4015f5f9 | ||
|
|
4c744e6ab3 | ||
|
|
6437b9f508 | ||
|
|
428821b40c | ||
|
|
3d92f27de7 | ||
|
|
db00b90306 | ||
|
|
e725f1e936 | ||
|
|
e62dfa2177 | ||
|
|
2b4aeb2b11 | ||
|
|
2f1d8d2dca | ||
|
|
754bf04ab3 | ||
|
|
c51558ff4c | ||
|
|
5a1b822833 | ||
|
|
8c0d7c6191 | ||
|
|
b3e9c267f5 | ||
|
|
936afd6b01 | ||
|
|
dc874c3b3f | ||
|
|
ae16a5e0e1 | ||
|
|
bdadbc9f39 | ||
|
|
d8c8807ee7 | ||
|
|
75e4f06230 | ||
|
|
3e7ab3861a | ||
|
|
ddfac907f4 | ||
|
|
f1633e0360 | ||
|
|
12a61c595e | ||
|
|
48a36be263 | ||
|
|
88b64cda6a | ||
|
|
d0938e2a24 | ||
|
|
f425746169 | ||
|
|
e28d47888c | ||
|
|
0a3e17eebb | ||
|
|
22ab9363ef | ||
|
|
c134402507 | ||
|
|
85933e2231 | ||
|
|
cfaa09d63a | ||
|
|
ca3eb87301 | ||
|
|
5e70911a68 | ||
|
|
360b92e567 | ||
|
|
5ee630ec82 | ||
|
|
aa4921268e | ||
|
|
12f3aa9d06 | ||
|
|
bdf42c2d9c | ||
|
|
a2e57b4b49 | ||
|
|
af2d22f343 | ||
|
|
aa6f9bcb4c | ||
|
|
194f37e9dd | ||
|
|
96c6a442ca | ||
|
|
7c4dfff451 | ||
|
|
a0e014f277 | ||
|
|
6e293a5ee8 | ||
|
|
dd25ed762f | ||
|
|
dca7df50c8 | ||
|
|
9fc69721c9 | ||
|
|
e05b2bbe37 | ||
|
|
5c2307c531 | ||
|
|
d57c7a1f03 | ||
|
|
71bf90f295 | ||
|
|
10d1fc8d60 | ||
|
|
44510dce1b | ||
|
|
6a361ef276 | ||
|
|
eccd87f303 | ||
|
|
6c3253968f | ||
|
|
c2417bb3f5 | ||
|
|
68ed03d4f3 | ||
|
|
ef560bf48e | ||
|
|
7243d220e5 | ||
|
|
10e1e46945 | ||
|
|
2093660ac1 | ||
|
|
b43191d2c5 | ||
|
|
7c907fed6e | ||
|
|
db528c94fc | ||
|
|
ddc88c83b2 | ||
|
|
c1e1eab81d | ||
|
|
534c2d9d1c | ||
|
|
c8d13d4270 | ||
|
|
12100c506e | ||
|
|
1d20599186 | ||
|
|
b6e4b62e15 | ||
|
|
0979cd6c44 | ||
|
|
61e952c58e | ||
|
|
cc40fb2ce9 | ||
|
|
b529557669 | ||
|
|
ac1048632a | ||
|
|
a28864ff6c | ||
|
|
a0d62ab40b | ||
|
|
216cc3fa66 | ||
|
|
d41793470f | ||
|
|
df73d75541 | ||
|
|
4f77204325 | ||
|
|
77022e92f1 | ||
|
|
f1ab16e7d5 | ||
|
|
2083b2ff9e | ||
|
|
62b2cdbb52 | ||
|
|
50baa7b48a | ||
|
|
d4eaec37f0 | ||
|
|
0ad6296665 | ||
|
|
7300b5bb7a | ||
|
|
bcceb85057 | ||
|
|
352e0a211a | ||
|
|
e0bb8409b3 | ||
|
|
0946a43ee8 | ||
|
|
c8d0c0aab7 | ||
|
|
2357653de5 | ||
|
|
be193b21eb | ||
|
|
52970ef854 | ||
|
|
826fc087b0 | ||
|
|
a7533faf45 | ||
|
|
052dc78af5 | ||
|
|
2bb1b6c6ad | ||
|
|
fd8b083a46 | ||
|
|
6789ec0765 | ||
|
|
6bf09df7ad | ||
|
|
e0b047762f | ||
|
|
77d56e0b15 | ||
|
|
36cefec30a | ||
|
|
03f57acd3e | ||
|
|
dde4d0d1fa | ||
|
|
863840ee0f | ||
|
|
1951f30434 | ||
|
|
524d658699 | ||
|
|
ef775a87c9 | ||
|
|
978fd6e2ba | ||
|
|
35651bc92c | ||
|
|
a3fbc64384 | ||
|
|
b6254f8294 | ||
|
|
6ba704d036 | ||
|
|
032d15f1f3 | ||
|
|
5e5e13ed14 | ||
|
|
ba5fab4009 | ||
|
|
63ad45a2de | ||
|
|
921c94b69c | ||
|
|
8f89dd4efa | ||
|
|
47b06e0b01 | ||
|
|
91a0d0e26d | ||
|
|
18bd6a847f | ||
|
|
66fe08f964 | ||
|
|
642d0ab2b7 | ||
|
|
8cc82de489 | ||
|
|
052d063686 | ||
|
|
8bcbd0bd42 | ||
|
|
93a789cbc1 | ||
|
|
9dff28f23d | ||
|
|
67c1128201 | ||
|
|
90627b3ae5 | ||
|
|
112cdf52ff | ||
|
|
7a27354fb3 | ||
|
|
04870c90f8 | ||
|
|
38f30c3ecb | ||
|
|
254f3ee50a | ||
|
|
4d47cd5866 | ||
|
|
0a11643f1d | ||
|
|
17075d6744 | ||
|
|
25a8851986 | ||
|
|
de5f7af25d | ||
|
|
092b3558f3 | ||
|
|
c2446334e6 | ||
|
|
3982358188 | ||
|
|
e54b4a4143 | ||
|
|
0d528f84f2 | ||
|
|
eec4f6bf4a | ||
|
|
8bec5ec4a0 | ||
|
|
dd46338942 | ||
|
|
83e3b8ca60 | ||
|
|
f7971af392 | ||
|
|
6bc711de06 | ||
|
|
f622d80004 | ||
|
|
4cec7e9b4b | ||
|
|
cf920e7d58 | ||
|
|
389ba20a98 | ||
|
|
dd8606ff9b | ||
|
|
7bc1457d42 | ||
|
|
e9e04d4e70 | ||
|
|
58e8f3c11c | ||
|
|
8df9523a77 | ||
|
|
fd0640997d | ||
|
|
fb74fc8c6a | ||
|
|
5f4087ff00 | ||
|
|
81889a685c | ||
|
|
e82c627ebe | ||
|
|
d726165330 | ||
|
|
57e1f26d14 | ||
|
|
b49997b4b4 | ||
|
|
2086e0f3af | ||
|
|
6107c3e551 | ||
|
|
46469aa5e3 | ||
|
|
c869b47e13 | ||
|
|
9531069538 | ||
|
|
521e301541 | ||
|
|
842326daa5 | ||
|
|
fb7c80938b | ||
|
|
29d0d57656 | ||
|
|
cbd61b0a7d | ||
|
|
1cc9835454 | ||
|
|
86914bdfc1 | ||
|
|
e0b003ad4d | ||
|
|
f89368c19b | ||
|
|
5582e1bcc8 | ||
|
|
a23c1a24dc | ||
|
|
5f9add4000 | ||
|
|
11126f9b37 | ||
|
|
e31202b0b8 | ||
|
|
e6dc314c17 | ||
|
|
2d4030683a | ||
|
|
262d95aae5 | ||
|
|
63ba8e19fd | ||
|
|
1b39db7ab0 | ||
|
|
2dd8f409b2 | ||
|
|
049c043279 | ||
|
|
a1da6c117e | ||
|
|
7c9ffb0e02 | ||
|
|
84df8197d4 | ||
|
|
5b3aa54b56 | ||
|
|
ef6bb8a73c | ||
|
|
4e144425f0 | ||
|
|
f353323a23 |
18
.github/actions/spelling/expect/expect.txt
vendored
18
.github/actions/spelling/expect/expect.txt
vendored
@@ -146,6 +146,7 @@ bytebuffer
|
||||
cac
|
||||
cacafire
|
||||
CALLCONV
|
||||
CANDRABINDU
|
||||
capslock
|
||||
CARETBLINKINGENABLED
|
||||
CARRIAGERETURN
|
||||
@@ -156,6 +157,7 @@ CBash
|
||||
cbiex
|
||||
CBN
|
||||
cbt
|
||||
Ccc
|
||||
CCCBB
|
||||
cch
|
||||
CCHAR
|
||||
@@ -293,7 +295,6 @@ CREATESTRUCT
|
||||
CREATESTRUCTW
|
||||
createvpack
|
||||
crisman
|
||||
CRLFs
|
||||
crloew
|
||||
CRTLIBS
|
||||
csbi
|
||||
@@ -593,6 +594,7 @@ fesb
|
||||
FFAF
|
||||
ffd
|
||||
FFDE
|
||||
FFFD
|
||||
FFFDb
|
||||
fgbg
|
||||
FGCOLOR
|
||||
@@ -613,6 +615,7 @@ FINDREGEX
|
||||
FINDSTRINGEXACT
|
||||
FINDUP
|
||||
FIter
|
||||
FITZPATRICK
|
||||
FIXEDFILEINFO
|
||||
Flg
|
||||
flyouts
|
||||
@@ -879,10 +882,12 @@ jconcpp
|
||||
JLO
|
||||
JOBOBJECT
|
||||
JOBOBJECTINFOCLASS
|
||||
JONGSEONG
|
||||
JPN
|
||||
jsoncpp
|
||||
jsprovider
|
||||
jumplist
|
||||
JUNGSEONG
|
||||
KAttrs
|
||||
kawa
|
||||
Kazu
|
||||
@@ -901,6 +906,7 @@ keyups
|
||||
KILLACTIVE
|
||||
KILLFOCUS
|
||||
kinda
|
||||
KIYEOK
|
||||
KLF
|
||||
KLMNO
|
||||
KLMNOPQRST
|
||||
@@ -1010,6 +1016,7 @@ luma
|
||||
lval
|
||||
LVB
|
||||
LVERTICAL
|
||||
LVT
|
||||
LWA
|
||||
LWIN
|
||||
lwkmvj
|
||||
@@ -1205,6 +1212,7 @@ ntuser
|
||||
NTVDM
|
||||
ntverp
|
||||
nugetversions
|
||||
NUKTA
|
||||
nullness
|
||||
nullonfailure
|
||||
nullopts
|
||||
@@ -1467,7 +1475,6 @@ READMODE
|
||||
rectread
|
||||
redef
|
||||
redefinable
|
||||
Redir
|
||||
redist
|
||||
REDSCROLL
|
||||
REFCLSID
|
||||
@@ -1485,6 +1492,7 @@ renderengine
|
||||
rendersize
|
||||
reparented
|
||||
reparenting
|
||||
REPH
|
||||
replatformed
|
||||
Replymessage
|
||||
repositorypath
|
||||
@@ -1514,6 +1522,7 @@ rgw
|
||||
RIGHTALIGN
|
||||
RIGHTBUTTON
|
||||
riid
|
||||
ris
|
||||
RIS
|
||||
roadmap
|
||||
robomac
|
||||
@@ -1919,6 +1928,7 @@ vga
|
||||
vgaoem
|
||||
viewkind
|
||||
viewports
|
||||
VIRAMA
|
||||
Virt
|
||||
VIRTTERM
|
||||
vkey
|
||||
@@ -1969,8 +1979,8 @@ wchars
|
||||
WCIA
|
||||
WCIW
|
||||
WCSHELPER
|
||||
wcsicmp
|
||||
wcsrev
|
||||
wcswidth
|
||||
wddm
|
||||
wddmcon
|
||||
WDDMCONSOLECONTEXT
|
||||
@@ -2125,6 +2135,7 @@ XFORM
|
||||
XIn
|
||||
XManifest
|
||||
XMath
|
||||
XNamespace
|
||||
xorg
|
||||
XPan
|
||||
XResource
|
||||
@@ -2156,6 +2167,7 @@ Zabcdefghijklmn
|
||||
Zabcdefghijklmnopqrstuvwxyz
|
||||
ZCmd
|
||||
ZCtrl
|
||||
ZWJs
|
||||
zxcvbnm
|
||||
ZYXWVU
|
||||
ZYXWVUTd
|
||||
|
||||
@@ -629,7 +629,8 @@
|
||||
"folder",
|
||||
"separator",
|
||||
"remainingProfiles",
|
||||
"matchProfiles"
|
||||
"matchProfiles",
|
||||
"action"
|
||||
]
|
||||
},
|
||||
"NewTabMenuEntry": {
|
||||
@@ -781,6 +782,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ActionEntry": {
|
||||
"description": "An action in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "action"
|
||||
},
|
||||
"profile": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The ID of the action to show in this entry"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SwitchToAdjacentTabArgs": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -2054,11 +2077,14 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/RemainingProfilesEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/ActionEntry"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Keybinding": {
|
||||
"FullCommand": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"command": {
|
||||
@@ -2186,21 +2212,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keys": {
|
||||
"description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/$defs/KeyChordSegment"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/$defs/KeyChordSegment"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"icon": {
|
||||
"$ref": "#/$defs/Icon"
|
||||
},
|
||||
@@ -2235,10 +2246,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"$ref": "#/$defs/Keybinding/properties/command"
|
||||
"$ref": "#/$defs/FullCommand/properties/command"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/$defs/Keybinding/properties/name"
|
||||
"$ref": "#/$defs/FullCommand/properties/name"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2261,6 +2272,44 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The ID of the command this keybinding should execute.",
|
||||
"type": "string"
|
||||
},
|
||||
"keys": {
|
||||
"description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/$defs/KeyChordSegment"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/$defs/KeyChordSegment"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"keys",
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"keys"
|
||||
]
|
||||
}
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Globals": {
|
||||
"additionalProperties": true,
|
||||
"description": "Properties that affect the entire window, regardless of the profile settings.",
|
||||
@@ -2464,12 +2513,12 @@
|
||||
"actions": {
|
||||
"description": "Properties are specific to each custom action.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Keybinding"
|
||||
"$ref": "#/$defs/FullCommand"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"keybindings": {
|
||||
"description": "[deprecated] Use actions instead.",
|
||||
"description": "A list of keychords bound to action IDs",
|
||||
"deprecated": true,
|
||||
"items": {
|
||||
"$ref": "#/$defs/Keybinding"
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
#include "Row.hpp"
|
||||
|
||||
#include <isa_availability.h>
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include "textBuffer.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../types/inc/CodepointWidthDetector.hpp"
|
||||
|
||||
// It would be nice to add checked array access in the future, but it's a little annoying to do so without impacting
|
||||
// performance (including Debug performance). Other languages are a little bit more ergonomic there than C++.
|
||||
@@ -568,6 +566,7 @@ void ROW::ReplaceAttributes(const til::CoordType beginIndex, const til::CoordTyp
|
||||
void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars)
|
||||
try
|
||||
{
|
||||
assert(width >= 1 && width <= 2);
|
||||
WriteHelper h{ *this, columnBegin, _columnCount, chars };
|
||||
if (!h.IsValid())
|
||||
{
|
||||
@@ -666,56 +665,89 @@ catch (...)
|
||||
|
||||
[[msvc::forceinline]] void ROW::WriteHelper::_replaceTextUnicode(size_t ch, std::wstring_view::const_iterator it) noexcept
|
||||
{
|
||||
const auto end = chars.end();
|
||||
auto& cwd = CodepointWidthDetector::Singleton();
|
||||
|
||||
while (it != end)
|
||||
// Check if the new text joins with the existing contents of the row to form a single grapheme cluster.
|
||||
if (it == chars.begin())
|
||||
{
|
||||
unsigned int width = 1;
|
||||
auto ptr = &*it;
|
||||
const auto wch = *ptr;
|
||||
size_t advance = 1;
|
||||
|
||||
++it;
|
||||
|
||||
// Even in our slow-path we can avoid calling IsGlyphFullWidth if the current character is ASCII.
|
||||
// It also allows us to skip the surrogate pair decoding at the same time.
|
||||
if (wch >= 0x80)
|
||||
auto colPrev = colBeg;
|
||||
while (colPrev > 0 && row._uncheckedIsTrailer(--colPrev))
|
||||
{
|
||||
if (til::is_surrogate(wch))
|
||||
}
|
||||
|
||||
const auto chPrev = row._uncheckedCharOffset(colPrev);
|
||||
const std::wstring_view charsPrev{ row._chars.data() + chPrev, ch - chPrev };
|
||||
|
||||
GraphemeState state;
|
||||
cwd.GraphemeNext(state, charsPrev);
|
||||
cwd.GraphemeNext(state, chars);
|
||||
|
||||
if (state.len > 0)
|
||||
{
|
||||
colBegDirty = colPrev;
|
||||
colEnd = colPrev;
|
||||
|
||||
const auto colEndNew = gsl::narrow_cast<uint16_t>(colEnd + state.width);
|
||||
if (colEndNew > colLimit)
|
||||
{
|
||||
if (it != end && til::is_leading_surrogate(wch) && til::is_trailing_surrogate(*it))
|
||||
{
|
||||
advance = 2;
|
||||
++it;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = &UNICODE_REPLACEMENT;
|
||||
}
|
||||
colEndDirty = colLimit;
|
||||
charsConsumed = ch - chBeg;
|
||||
return;
|
||||
}
|
||||
|
||||
width = IsGlyphFullWidth({ ptr, advance }) + 1u;
|
||||
}
|
||||
// Fill our char-offset buffer with 1 entry containing the mapping from the
|
||||
// current column (colEnd) to the start of the glyph in the string (ch)...
|
||||
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(chPrev);
|
||||
// ...followed by 0-N entries containing an indication that the
|
||||
// columns are just a wide-glyph extension of the preceding one.
|
||||
while (colEnd < colEndNew)
|
||||
{
|
||||
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(chPrev | CharOffsetsTrailer);
|
||||
}
|
||||
|
||||
const auto colEndNew = gsl::narrow_cast<uint16_t>(colEnd + width);
|
||||
if (colEndNew > colLimit)
|
||||
ch += state.len;
|
||||
it += state.len;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The non-ASCII character we have encountered may be a combining mark, like "a^" which is then displayed as "â".
|
||||
// In order to recognize both characters as a single grapheme, we need to back up by 1 ASCII character
|
||||
// and let MeasureNext() find the next proper grapheme boundary.
|
||||
--colEnd;
|
||||
--ch;
|
||||
--it;
|
||||
}
|
||||
|
||||
if (const auto end = chars.end(); it != end)
|
||||
{
|
||||
GraphemeState state{ .beg = &*it };
|
||||
|
||||
do
|
||||
{
|
||||
colEndDirty = colLimit;
|
||||
charsConsumed = ch - chBeg;
|
||||
return;
|
||||
}
|
||||
cwd.GraphemeNext(state, chars);
|
||||
|
||||
// Fill our char-offset buffer with 1 entry containing the mapping from the
|
||||
// current column (colEnd) to the start of the glyph in the string (ch)...
|
||||
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(ch);
|
||||
// ...followed by 0-N entries containing an indication that the
|
||||
// columns are just a wide-glyph extension of the preceding one.
|
||||
while (colEnd < colEndNew)
|
||||
{
|
||||
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(ch | CharOffsetsTrailer);
|
||||
}
|
||||
const auto colEndNew = gsl::narrow_cast<uint16_t>(colEnd + state.width);
|
||||
if (colEndNew > colLimit)
|
||||
{
|
||||
colEndDirty = colLimit;
|
||||
charsConsumed = ch - chBeg;
|
||||
return;
|
||||
}
|
||||
|
||||
ch += advance;
|
||||
// Fill our char-offset buffer with 1 entry containing the mapping from the
|
||||
// current column (colEnd) to the start of the glyph in the string (ch)...
|
||||
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(ch);
|
||||
// ...followed by 0-N entries containing an indication that the
|
||||
// columns are just a wide-glyph extension of the preceding one.
|
||||
while (colEnd < colEndNew)
|
||||
{
|
||||
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(ch | CharOffsetsTrailer);
|
||||
}
|
||||
|
||||
ch += state.len;
|
||||
it += state.len;
|
||||
} while (it != end);
|
||||
}
|
||||
|
||||
colEndDirty = colEnd;
|
||||
@@ -1058,7 +1090,7 @@ std::wstring_view ROW::GetText() const noexcept
|
||||
|
||||
std::wstring_view ROW::GetText(til::CoordType columnBegin, til::CoordType columnEnd) const noexcept
|
||||
{
|
||||
const til::CoordType columns = _columnCount;
|
||||
const auto columns = GetReadableColumnCount();
|
||||
const auto colBeg = clamp(columnBegin, 0, columns);
|
||||
const auto colEnd = clamp(columnEnd, colBeg, columns);
|
||||
const size_t chBeg = _uncheckedCharOffset(gsl::narrow_cast<size_t>(colBeg));
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "textBuffer.hpp"
|
||||
|
||||
#include <til/hash.h>
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include "UTextAdapter.h"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../types/inc/CodepointWidthDetector.hpp"
|
||||
#include "../renderer/base/renderer.hpp"
|
||||
#include "../types/inc/utils.hpp"
|
||||
#include "search.h"
|
||||
@@ -408,17 +406,23 @@ void TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
|
||||
// Given the character offset `position` in the `chars` string, this function returns the starting position of the next grapheme.
|
||||
// For instance, given a `chars` of L"x\uD83D\uDE42y" and a `position` of 1 it'll return 3.
|
||||
// GraphemePrev would do the exact inverse of this operation.
|
||||
// In the future, these functions are expected to also deliver information about how many columns a grapheme occupies.
|
||||
// (I know that mere UTF-16 code point iteration doesn't handle graphemes, but that's what we're working towards.)
|
||||
size_t TextBuffer::GraphemeNext(const std::wstring_view& chars, size_t position) noexcept
|
||||
{
|
||||
return til::utf16_iterate_next(chars, position);
|
||||
auto& cwd = CodepointWidthDetector::Singleton();
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
GraphemeState state{ .beg = chars.data() + position };
|
||||
cwd.GraphemeNext(state, chars);
|
||||
return position + state.len;
|
||||
}
|
||||
|
||||
// It's the counterpart to GraphemeNext. See GraphemeNext.
|
||||
size_t TextBuffer::GraphemePrev(const std::wstring_view& chars, size_t position) noexcept
|
||||
{
|
||||
return til::utf16_iterate_prev(chars, position);
|
||||
auto& cwd = CodepointWidthDetector::Singleton();
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
GraphemeState state{ .beg = chars.data() + position };
|
||||
cwd.GraphemePrev(state, chars);
|
||||
return position - state.len;
|
||||
}
|
||||
|
||||
// Ever wondered how much space a piece of text needs before inserting it? This function will tell you!
|
||||
@@ -445,7 +449,7 @@ size_t TextBuffer::FitTextIntoColumns(const std::wstring_view& chars, til::Coord
|
||||
{
|
||||
}
|
||||
|
||||
const auto dist = gsl::narrow_cast<size_t>(it - beg);
|
||||
auto dist = gsl::narrow_cast<size_t>(it - beg);
|
||||
auto col = gsl::narrow_cast<til::CoordType>(dist);
|
||||
|
||||
if (it == asciiEnd) [[likely]]
|
||||
@@ -455,33 +459,26 @@ size_t TextBuffer::FitTextIntoColumns(const std::wstring_view& chars, til::Coord
|
||||
}
|
||||
|
||||
// Unicode slow-path where we need to count text and columns separately.
|
||||
for (;;)
|
||||
auto& cwd = CodepointWidthDetector::Singleton();
|
||||
const auto len = chars.size();
|
||||
|
||||
// The non-ASCII character we have encountered may be a combining mark, like "a^" which is then displayed as "â".
|
||||
// In order to recognize both characters as a single grapheme, we need to back up by 1 ASCII character
|
||||
// and let GraphemeNext() find the next proper grapheme boundary.
|
||||
if (dist != 0)
|
||||
{
|
||||
auto ptr = &*it;
|
||||
const auto wch = *ptr;
|
||||
size_t len = 1;
|
||||
dist--;
|
||||
col--;
|
||||
}
|
||||
|
||||
col++;
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
GraphemeState state{ .beg = chars.data() + dist };
|
||||
|
||||
// Even in our slow-path we can avoid calling IsGlyphFullWidth if the current character is ASCII.
|
||||
// It also allows us to skip the surrogate pair decoding at the same time.
|
||||
if (wch >= 0x80)
|
||||
{
|
||||
if (til::is_surrogate(wch))
|
||||
{
|
||||
const auto it2 = it + 1;
|
||||
if (til::is_leading_surrogate(wch) && it2 != end && til::is_trailing_surrogate(*it2))
|
||||
{
|
||||
len = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = &UNICODE_REPLACEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
col += IsGlyphFullWidth({ ptr, len });
|
||||
}
|
||||
while (dist < len)
|
||||
{
|
||||
cwd.GraphemeNext(state, chars);
|
||||
dist += state.len;
|
||||
col += state.width;
|
||||
|
||||
// If we ran out of columns, we need to always return `columnLimit` and not `cols`,
|
||||
// because if we tried inserting a wide glyph into just 1 remaining column it will
|
||||
@@ -490,17 +487,13 @@ size_t TextBuffer::FitTextIntoColumns(const std::wstring_view& chars, til::Coord
|
||||
if (col > columnLimit)
|
||||
{
|
||||
columns = columnLimit;
|
||||
return gsl::narrow_cast<size_t>(it - beg);
|
||||
}
|
||||
|
||||
// But if we simply ran out of text we just need to return the actual number of columns.
|
||||
it += len;
|
||||
if (it == end)
|
||||
{
|
||||
columns = col;
|
||||
return chars.size();
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
|
||||
// But if we simply ran out of text we just need to return the actual number of columns.
|
||||
columns = col;
|
||||
return chars.size();
|
||||
}
|
||||
|
||||
// Pretend as if `position` is a regular cursor in the TextBuffer.
|
||||
@@ -993,7 +986,7 @@ const til::CoordType TextBuffer::GetFirstRowIndex() const noexcept
|
||||
|
||||
const Viewport TextBuffer::GetSize() const noexcept
|
||||
{
|
||||
return Viewport::FromDimensions({ _width, _height });
|
||||
return Viewport::FromDimensions({}, { _width, _height });
|
||||
}
|
||||
|
||||
void TextBuffer::_SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept
|
||||
@@ -1597,7 +1590,7 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
|
||||
}
|
||||
}
|
||||
|
||||
bufferSize.IncrementInBoundsCircular(result);
|
||||
bufferSize.IncrementInBounds(result);
|
||||
}
|
||||
|
||||
if (_GetDelimiterClassAt(result, wordDelimiters) != initialDelimiter)
|
||||
|
||||
@@ -174,6 +174,23 @@
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- We need to include some additional files in the package for things like WinGet's Command Not Found -->
|
||||
<Target Name="IncludeAdditionalFilesInPackage">
|
||||
<PropertyGroup>
|
||||
<WinGetAdditionalPackageFileRoot>$(SolutionDir)\src\cascadia\TerminalControl</WinGetAdditionalPackageFileRoot>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<WinGetAdditionalPackageFile Include="$(WinGetAdditionalPackageFileRoot)\Microsoft.Management.Deployment.winmd">
|
||||
<PackagePath>Microsoft.Management.Deployment.winmd</PackagePath>
|
||||
</WinGetAdditionalPackageFile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AppxPackagePayload Include="%(WinGetAdditionalPackageFile.Identity))">
|
||||
<TargetPath>%(WinGetAdditionalPackageFile.PackagePath)</TargetPath>
|
||||
</AppxPackagePayload>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- This is required to get the package dependency in the AppXManifest. -->
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
ActionPaletteItem::ActionPaletteItem(const Microsoft::Terminal::Settings::Model::Command& command) :
|
||||
ActionPaletteItem::ActionPaletteItem(const Microsoft::Terminal::Settings::Model::Command& command, const winrt::hstring keyChordText) :
|
||||
_Command(command)
|
||||
{
|
||||
Name(command.Name());
|
||||
KeyChordText(command.KeyChordText());
|
||||
KeyChordText(keyChordText);
|
||||
Icon(command.IconPath());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace winrt::TerminalApp::implementation
|
||||
struct ActionPaletteItem : ActionPaletteItemT<ActionPaletteItem, PaletteItem>
|
||||
{
|
||||
ActionPaletteItem() = default;
|
||||
ActionPaletteItem(const Microsoft::Terminal::Settings::Model::Command& command);
|
||||
ActionPaletteItem(const Microsoft::Terminal::Settings::Model::Command& command, const winrt::hstring keyChordText);
|
||||
|
||||
WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, nullptr);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass ActionPaletteItem : PaletteItem
|
||||
{
|
||||
ActionPaletteItem(Microsoft.Terminal.Settings.Model.Command command);
|
||||
ActionPaletteItem(Microsoft.Terminal.Settings.Model.Command command, String keyChordText);
|
||||
|
||||
Microsoft.Terminal.Settings.Model.Command Command { get; };
|
||||
}
|
||||
|
||||
@@ -1264,6 +1264,54 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleSaveTask(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (Feature_SaveTask::IsEnabled())
|
||||
{
|
||||
if (args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<SaveTaskArgs>())
|
||||
{
|
||||
if (realArgs.Commandline().empty())
|
||||
{
|
||||
if (const auto termControl{ _GetActiveControl() })
|
||||
{
|
||||
if (termControl.HasSelection())
|
||||
{
|
||||
const auto selections{ termControl.SelectedText(true) };
|
||||
const auto selection = std::accumulate(selections.begin(), selections.end(), std::wstring());
|
||||
realArgs.Commandline(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
winrt::Microsoft::Terminal::Control::KeyChord keyChord = nullptr;
|
||||
if (!realArgs.KeyChord().empty())
|
||||
{
|
||||
keyChord = KeyChordSerialization::FromString(winrt::to_hstring(realArgs.KeyChord()));
|
||||
}
|
||||
_settings.GlobalSettings().ActionMap().AddSendInputAction(realArgs.Name(), realArgs.Commandline(), keyChord);
|
||||
_settings.WriteSettingsToDisk();
|
||||
ActionSaved(realArgs.Commandline(), realArgs.Name(), realArgs.KeyChord());
|
||||
}
|
||||
catch (const winrt::hresult_error& ex)
|
||||
{
|
||||
auto code = ex.code();
|
||||
auto message = ex.message();
|
||||
ActionSaveFailed(message);
|
||||
args.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleSelectCommand(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
@@ -1340,7 +1388,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// requires context from the control)
|
||||
// then get that here.
|
||||
const bool shouldGetContext = realArgs.UseCommandline() ||
|
||||
WI_IsFlagSet(source, SuggestionsSource::CommandHistory);
|
||||
WI_IsAnyFlagSet(source, SuggestionsSource::CommandHistory | SuggestionsSource::QuickFixes);
|
||||
if (shouldGetContext)
|
||||
{
|
||||
if (const auto& control{ _GetActiveControl() })
|
||||
@@ -1373,7 +1421,19 @@ namespace winrt::TerminalApp::implementation
|
||||
if (WI_IsFlagSet(source, SuggestionsSource::CommandHistory) &&
|
||||
context != nullptr)
|
||||
{
|
||||
const auto recentCommands = Command::HistoryToCommands(context.History(), currentCommandline, false);
|
||||
// \ue81c --> History icon
|
||||
const auto recentCommands = Command::HistoryToCommands(context.History(), currentCommandline, false, hstring{ L"\ue81c" });
|
||||
for (const auto& t : recentCommands)
|
||||
{
|
||||
commandsCollection.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (WI_IsFlagSet(source, SuggestionsSource::QuickFixes) &&
|
||||
context != nullptr)
|
||||
{
|
||||
// \ue74c --> OEM icon
|
||||
const auto recentCommands = Command::HistoryToCommands(context.QuickFixes(), hstring{ L"" }, false, hstring{ L"\ue74c" });
|
||||
for (const auto& t : recentCommands)
|
||||
{
|
||||
commandsCollection.push_back(t);
|
||||
@@ -1473,4 +1533,5 @@ namespace winrt::TerminalApp::implementation
|
||||
_ShowAboutDialog();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ void AppCommandlineArgs::_buildParser()
|
||||
_buildMovePaneParser();
|
||||
_buildSwapPaneParser();
|
||||
_buildFocusPaneParser();
|
||||
_buildSaveParser();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -537,6 +538,72 @@ void AppCommandlineArgs::_buildFocusPaneParser()
|
||||
setupSubcommand(_focusPaneShort);
|
||||
}
|
||||
|
||||
void AppCommandlineArgs::_buildSaveParser()
|
||||
{
|
||||
_saveCommand = _app.add_subcommand("x-save", RS_A(L"SaveActionDesc"));
|
||||
|
||||
auto setupSubcommand = [this](auto* subcommand) {
|
||||
subcommand->add_option("--name,-n", _saveInputName, RS_A(L"SaveActionArgDesc"));
|
||||
subcommand->add_option("--keychord,-k", _keyChordOption, RS_A(L"KeyChordArgDesc"));
|
||||
subcommand->add_option("command,", _commandline, RS_A(L"CmdCommandArgDesc"));
|
||||
subcommand->positionals_at_end(true);
|
||||
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
subcommand->callback([&, this]() {
|
||||
// Build the NewTab action from the values we've parsed on the commandline.
|
||||
ActionAndArgs saveAction{};
|
||||
saveAction.Action(ShortcutAction::SaveTask);
|
||||
// _getNewTerminalArgs MUST be called before parsing any other options,
|
||||
// as it might clear those options while finding the commandline
|
||||
SaveTaskArgs args{};
|
||||
|
||||
if (!_commandline.empty())
|
||||
{
|
||||
std::ostringstream cmdlineBuffer;
|
||||
|
||||
for (const auto& arg : _commandline)
|
||||
{
|
||||
if (cmdlineBuffer.tellp() != 0)
|
||||
{
|
||||
// If there's already something in here, prepend a space
|
||||
cmdlineBuffer << ' ';
|
||||
}
|
||||
|
||||
if (arg.find(" ") != std::string::npos)
|
||||
{
|
||||
cmdlineBuffer << '"' << arg << '"';
|
||||
}
|
||||
else
|
||||
{
|
||||
cmdlineBuffer << arg;
|
||||
}
|
||||
}
|
||||
|
||||
args.Commandline(winrt::to_hstring(cmdlineBuffer.str()));
|
||||
}
|
||||
|
||||
if (!_keyChordOption.empty())
|
||||
{
|
||||
args.KeyChord(winrt::to_hstring(_keyChordOption));
|
||||
}
|
||||
|
||||
if (!_saveInputName.empty())
|
||||
{
|
||||
winrt::hstring hString = winrt::to_hstring(_saveInputName);
|
||||
args.Name(hString);
|
||||
}
|
||||
|
||||
saveAction.Args(args);
|
||||
_startupActions.push_back(saveAction);
|
||||
});
|
||||
};
|
||||
|
||||
setupSubcommand(_saveCommand);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
|
||||
// that subcommand to support all the properties in a NewTerminalArgs.
|
||||
@@ -710,7 +777,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
|
||||
*_focusPaneCommand ||
|
||||
*_focusPaneShort ||
|
||||
*_newPaneShort.subcommand ||
|
||||
*_newPaneCommand.subcommand);
|
||||
*_newPaneCommand.subcommand ||
|
||||
*_saveCommand);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -93,6 +93,7 @@ private:
|
||||
CLI::App* _swapPaneCommand;
|
||||
CLI::App* _focusPaneCommand;
|
||||
CLI::App* _focusPaneShort;
|
||||
CLI::App* _saveCommand;
|
||||
|
||||
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
|
||||
|
||||
@@ -123,6 +124,8 @@ private:
|
||||
bool _focusPrevTab{ false };
|
||||
|
||||
int _focusPaneTarget{ -1 };
|
||||
std::string _saveInputName;
|
||||
std::string _keyChordOption;
|
||||
// Are you adding more args here? Make sure to reset them in _resetStateToDefault
|
||||
|
||||
const Commandline* _currentCommandline{ nullptr };
|
||||
@@ -141,6 +144,7 @@ private:
|
||||
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand);
|
||||
void _addNewTerminalArgs(NewTerminalSubcommand& subcommand);
|
||||
void _buildParser();
|
||||
void _buildSaveParser();
|
||||
void _buildNewTabParser();
|
||||
void _buildSplitPaneParser();
|
||||
void _buildFocusTabParser();
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "AppLogic.h"
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
#include "AppLogic.g.cpp"
|
||||
#include "FindTargetWindowResult.g.cpp"
|
||||
#include "SettingsLoadEventArgs.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
#include <WtExeUtils.h>
|
||||
#include <wil/token_helpers.h>
|
||||
|
||||
#include "SettingsLoadEventArgs.h"
|
||||
#include "../../types/inc/CodepointWidthDetector.hpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
|
||||
#include "AppLogic.g.cpp"
|
||||
#include "FindTargetWindowResult.g.cpp"
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel;
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
@@ -433,13 +435,6 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
if (initialLoad)
|
||||
{
|
||||
// Register for directory change notification.
|
||||
_RegisterSettingsChange();
|
||||
return;
|
||||
}
|
||||
|
||||
// Here, we successfully reloaded the settings, and created a new
|
||||
// TerminalSettings object.
|
||||
|
||||
|
||||
@@ -950,21 +950,27 @@ namespace winrt::TerminalApp::implementation
|
||||
void CommandPalette::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap)
|
||||
{
|
||||
_actionMap = actionMap;
|
||||
_setCommands();
|
||||
}
|
||||
|
||||
void CommandPalette::SetCommands(const Collections::IVector<Command>& actions)
|
||||
void CommandPalette::_setCommands()
|
||||
{
|
||||
_allCommands.Clear();
|
||||
for (const auto& action : actions)
|
||||
if (_actionMap)
|
||||
{
|
||||
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
|
||||
auto filteredCommand{ winrt::make<FilteredCommand>(actionPaletteItem) };
|
||||
_allCommands.Append(filteredCommand);
|
||||
}
|
||||
_allCommands.Clear();
|
||||
const auto expandedCommands{ _actionMap.ExpandedCommands() };
|
||||
for (const auto& action : expandedCommands)
|
||||
{
|
||||
const auto keyChordText{ KeyChordSerialization::ToString(_actionMap.GetKeyBindingForAction(action.ID())) };
|
||||
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action, keyChordText) };
|
||||
auto filteredCommand{ winrt::make<FilteredCommand>(actionPaletteItem) };
|
||||
_allCommands.Append(filteredCommand);
|
||||
}
|
||||
|
||||
if (Visibility() == Visibility::Visible && _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
_updateFilteredActions();
|
||||
if (Visibility() == Visibility::Visible && _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
_updateFilteredActions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1178,7 +1184,8 @@ namespace winrt::TerminalApp::implementation
|
||||
for (const auto& nameAndCommand : parentCommand.NestedCommands())
|
||||
{
|
||||
const auto action = nameAndCommand.Value();
|
||||
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
|
||||
// nested commands cannot have keys bound to them, so just pass in the command and no keys
|
||||
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action, L"") };
|
||||
auto nestedFilteredCommand{ winrt::make<FilteredCommand>(nestedActionPaletteItem) };
|
||||
_currentNestedCommands.Append(nestedFilteredCommand);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> FilteredActions();
|
||||
|
||||
void SetCommands(const Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command>& actions);
|
||||
void SetTabs(const Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase>& tabs, const Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase>& mruTabs);
|
||||
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
|
||||
|
||||
@@ -81,6 +80,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
bool _lastFilterTextWasEmpty{ true };
|
||||
|
||||
void _setCommands();
|
||||
|
||||
void _filterTextChanged(const Windows::Foundation::IInspectable& sender,
|
||||
const Windows::UI::Xaml::RoutedEventArgs& args);
|
||||
void _previewKeyDownHandler(const Windows::Foundation::IInspectable& sender,
|
||||
|
||||
@@ -20,8 +20,6 @@ namespace TerminalApp
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<FilteredCommand> FilteredActions { get; };
|
||||
|
||||
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
|
||||
|
||||
void SetTabs(Windows.Foundation.Collections.IObservableVector<TabBase> tabs, Windows.Foundation.Collections.IObservableVector<TabBase> mruTabs);
|
||||
|
||||
void SetActionMap(Microsoft.Terminal.Settings.Model.IActionMapView actionMap);
|
||||
|
||||
@@ -21,11 +21,22 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// This class is a wrapper of PaletteItem, that is used as an item of a filterable list in CommandPalette.
|
||||
// It manages a highlighted text that is computed by matching search filter characters to item name
|
||||
FilteredCommand::FilteredCommand(const winrt::TerminalApp::PaletteItem& item) :
|
||||
_Item(item),
|
||||
_Filter(L""),
|
||||
_Weight(0)
|
||||
FilteredCommand::FilteredCommand(const winrt::TerminalApp::PaletteItem& item)
|
||||
{
|
||||
// Actually implement the ctor in _constructFilteredCommand
|
||||
_constructFilteredCommand(item);
|
||||
}
|
||||
|
||||
// We need to actually implement the ctor in a separate helper. This is
|
||||
// because we have a FilteredTask class which derives from FilteredCommand.
|
||||
// HOWEVER, for cppwinrt ~ r e a s o n s ~, it doesn't actually derive from
|
||||
// FilteredCommand directly, so we can't just use the FilteredCommand ctor
|
||||
// directly in the base class.
|
||||
void FilteredCommand::_constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item)
|
||||
{
|
||||
_Item = item;
|
||||
_Filter = L"";
|
||||
_Weight = 0;
|
||||
_HighlightedName = _computeHighlightedName();
|
||||
|
||||
// Recompute the highlighted name if the item name changes
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace winrt::TerminalApp::implementation
|
||||
FilteredCommand() = default;
|
||||
FilteredCommand(const winrt::TerminalApp::PaletteItem& item);
|
||||
|
||||
void UpdateFilter(const winrt::hstring& filter);
|
||||
virtual void UpdateFilter(const winrt::hstring& filter);
|
||||
|
||||
static int Compare(const winrt::TerminalApp::FilteredCommand& first, const winrt::TerminalApp::FilteredCommand& second);
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace winrt::TerminalApp::implementation
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, PropertyChanged.raise);
|
||||
WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise);
|
||||
|
||||
protected:
|
||||
void _constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item);
|
||||
|
||||
private:
|
||||
winrt::TerminalApp::HighlightedText _computeHighlightedName();
|
||||
int _computeWeight();
|
||||
|
||||
@@ -6,7 +6,7 @@ import "HighlightedTextControl.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass FilteredCommand : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
[default_interface] unsealed runtimeclass FilteredCommand : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
FilteredCommand();
|
||||
FilteredCommand(PaletteItem item);
|
||||
|
||||
@@ -27,10 +27,10 @@ static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
|
||||
static const int AnimationDurationInMilliseconds = 200;
|
||||
static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds)));
|
||||
|
||||
Pane::Pane(const IPaneContent& content, const bool lastFocused) :
|
||||
_content{ content },
|
||||
Pane::Pane(IPaneContent content, const bool lastFocused) :
|
||||
_lastActive{ lastFocused }
|
||||
{
|
||||
_setPaneContent(std::move(content));
|
||||
_root.Children().Append(_borderFirst);
|
||||
|
||||
const auto& control{ _content.GetRoot() };
|
||||
@@ -43,6 +43,9 @@ Pane::Pane(const IPaneContent& content, const bool lastFocused) :
|
||||
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ContentLostFocusHandler });
|
||||
}
|
||||
|
||||
_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });
|
||||
_manipulationStartedRevoker = _root.ManipulationStarted(winrt::auto_revoke, { this, &Pane::_ManipulationStartedHandler });
|
||||
|
||||
// When our border is tapped, make sure to transfer focus to our control.
|
||||
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
|
||||
// Colors::Transparent! The border won't get Tapped events, and they'll fall
|
||||
@@ -73,6 +76,8 @@ Pane::Pane(std::shared_ptr<Pane> first,
|
||||
_root.Children().Append(_borderFirst);
|
||||
_root.Children().Append(_borderSecond);
|
||||
|
||||
_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });
|
||||
|
||||
_ApplySplitDefinitions();
|
||||
|
||||
// Register event handlers on our children to handle their Close events
|
||||
@@ -243,14 +248,13 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n
|
||||
// decreasing the size of our first child.
|
||||
// Return Value:
|
||||
// - false if we couldn't resize this pane in the given direction, else true.
|
||||
bool Pane::_Resize(const ResizeDirection& direction)
|
||||
bool Pane::_Resize(const ResizeDirection& direction, float amount)
|
||||
{
|
||||
if (!DirectionMatchesSplit(direction, _splitState))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto amount = .05f;
|
||||
if (direction == ResizeDirection::Right || direction == ResizeDirection::Down)
|
||||
{
|
||||
amount = -amount;
|
||||
@@ -284,7 +288,7 @@ bool Pane::_Resize(const ResizeDirection& direction)
|
||||
// - direction: The direction to move the separator in.
|
||||
// Return Value:
|
||||
// - true if we or a child handled this resize request.
|
||||
bool Pane::ResizePane(const ResizeDirection& direction)
|
||||
bool Pane::ResizePane(const ResizeDirection& direction, float amount)
|
||||
{
|
||||
// If we're a leaf, do nothing. We can't possibly have a descendant with a
|
||||
// separator the correct direction.
|
||||
@@ -301,7 +305,7 @@ bool Pane::ResizePane(const ResizeDirection& direction)
|
||||
const auto secondIsFocused = _secondChild->_lastActive;
|
||||
if (firstIsFocused || secondIsFocused)
|
||||
{
|
||||
return _Resize(direction);
|
||||
return _Resize(direction, amount);
|
||||
}
|
||||
|
||||
// If neither of our children were the focused pane, then recurse into
|
||||
@@ -315,17 +319,200 @@ bool Pane::ResizePane(const ResizeDirection& direction)
|
||||
// either.
|
||||
if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild())
|
||||
{
|
||||
return _firstChild->ResizePane(direction) || _Resize(direction);
|
||||
return _firstChild->ResizePane(direction, amount) || _Resize(direction, amount);
|
||||
}
|
||||
|
||||
if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild())
|
||||
{
|
||||
return _secondChild->ResizePane(direction) || _Resize(direction);
|
||||
return _secondChild->ResizePane(direction, amount) || _Resize(direction, amount);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handler for the _root's ManipulationStarted event. We use this to check if a
|
||||
// manipulation (read: drag) started inside our content. If it did, we _don't_
|
||||
// want to do our normal pane dragging.
|
||||
//
|
||||
// Consider the case that the TermControl might be selecting text, and the user
|
||||
// drags the mouse over the pane border. We don't want that to start moving the
|
||||
// border!
|
||||
void Pane::_ManipulationStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::UI::Xaml::Input::ManipulationStartedRoutedEventArgs& args)
|
||||
{
|
||||
// This is added to each _root. But it also bubbles, so only leaves should actually try to handle this.
|
||||
if (args.Handled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
args.Handled(true);
|
||||
|
||||
assert(_IsLeaf());
|
||||
|
||||
const auto contentSize = _content.GetRoot().ActualSize();
|
||||
auto transformCurrentPos = args.Position();
|
||||
auto transformOrigin = transformCurrentPos;
|
||||
|
||||
const auto transform_contentFromOurRoot = _root.TransformToVisual(_content.GetRoot());
|
||||
const auto transformInControlSpace = transform_contentFromOurRoot.TransformPoint(transformOrigin);
|
||||
|
||||
// If we clicked on the control. bail, and don't allow any manipulations
|
||||
// for this series of events.
|
||||
_shouldManipulate = !((transformInControlSpace.X >= 0 && transformInControlSpace.X < contentSize.x) &&
|
||||
(transformInControlSpace.Y >= 0 && transformInControlSpace.Y < contentSize.y));
|
||||
}
|
||||
|
||||
// Handler for the _root's ManipulationDelta event. This is the event raised
|
||||
// when a user clicks and drags somewhere inside the pane. We're going to use
|
||||
// this to determine if the user clicked on one of our borders. If they did,
|
||||
// we're going to need to ask our parent pane (or other ancestors) to resize
|
||||
// their split.
|
||||
//
|
||||
// Recall that a leaf itself is responsible for having the right borders, but it
|
||||
// is the parent of the leaf that actually controls how big a split is.
|
||||
//
|
||||
// When we do want to be resized, we'll pass the delta from this event upwards
|
||||
// via ManipulationRequested, which will be handled in
|
||||
// Pane::_handleOrBubbleManipulation.
|
||||
void Pane::_ManipulationDeltaHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs& args)
|
||||
{
|
||||
// sender is ORIGINALLY the root Grid of a leaf, and the leaf may or may not
|
||||
// have a border.
|
||||
if (args.Handled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!_shouldManipulate)
|
||||
{
|
||||
// Using our stored _shouldManipulate set up in
|
||||
// _ManipulationStartedHandler, bail if the manipulation didn't start
|
||||
// _on the border_.
|
||||
return;
|
||||
}
|
||||
|
||||
assert(_IsLeaf());
|
||||
|
||||
const auto delta = args.Delta().Translation;
|
||||
const auto transformOrigin = args.Position();
|
||||
|
||||
const auto contentSize = _content.GetRoot().ActualSize();
|
||||
|
||||
const auto transform_contentFromOurRoot = _root.TransformToVisual(_content.GetRoot());
|
||||
// This is the position of the drag relative to the bounds of our content.
|
||||
const auto transformInControlSpace = transform_contentFromOurRoot.TransformPoint(transformOrigin);
|
||||
|
||||
// Did we click somewhere in the bounds of our content?
|
||||
if ((transformInControlSpace.X >= 0 && transformInControlSpace.X < contentSize.x) &&
|
||||
(transformInControlSpace.Y >= 0 && transformInControlSpace.Y < contentSize.y))
|
||||
{
|
||||
// We did! Bail.
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, we know we clicked somewhere outside the bounds of our content. Set
|
||||
// border flags based on the side that was clicked on.
|
||||
Borders clicked = Borders::None;
|
||||
clicked |= (transformInControlSpace.X < 0) ? Borders::Left : Borders::None;
|
||||
clicked |= (transformInControlSpace.Y < 0) ? Borders::Top : Borders::None;
|
||||
clicked |= (transformInControlSpace.X > contentSize.x) ? Borders::Right : Borders::None;
|
||||
clicked |= (transformInControlSpace.Y > contentSize.y) ? Borders::Bottom : Borders::None;
|
||||
|
||||
// Ask our parent to resize their split.
|
||||
ManipulationRequested.raise(shared_from_this(), delta, clicked);
|
||||
}
|
||||
|
||||
// Handler for our child's own ManipulationRequested event. They will pass to us
|
||||
// (their immediate parent) the delta and the side that was clicked on.
|
||||
// * If we control that border, then we'll handle the resize ourself in _handleManipulation.
|
||||
// * If not, then we'll ask our own parent to try and resize that same border.
|
||||
void Pane::_handleOrBubbleManipulation(std::shared_ptr<Pane> sender,
|
||||
const winrt::Windows::Foundation::Point delta,
|
||||
Borders side)
|
||||
{
|
||||
if (side == Borders::None || _splitState == SplitState::None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool isFirstChild = sender == _firstChild;
|
||||
// We want to handle this drag in the following cases
|
||||
// * In a vertical split: if we're dragging the right of the first pane or the left of the second
|
||||
// * In a horizontal split: if we're dragging the bottom of the first pane or the top of the second
|
||||
const auto sideMatched = (_splitState == SplitState::Vertical) ? (isFirstChild && WI_IsFlagSet(side, Borders::Right)) || (!isFirstChild && WI_IsFlagSet(side, Borders::Left)) :
|
||||
(_splitState == SplitState::Horizontal) ? (isFirstChild && WI_IsFlagSet(side, Borders::Bottom)) || (!isFirstChild && WI_IsFlagSet(side, Borders::Top)) :
|
||||
false;
|
||||
|
||||
if (sideMatched)
|
||||
{
|
||||
_handleManipulation(delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bubble, with us as the new sender.
|
||||
ManipulationRequested.raise(shared_from_this(), delta, side);
|
||||
}
|
||||
}
|
||||
|
||||
// Actually handle resizing our split in response to a drag event. If we're
|
||||
// being called, then we know that the delta that's passed to us should be
|
||||
// applied to our own split. The delta that's passed in here is in PIXELS, not
|
||||
// DIPs.
|
||||
void Pane::_handleManipulation(const winrt::Windows::Foundation::Point delta)
|
||||
{
|
||||
const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
|
||||
|
||||
const auto weAreVertical = _splitState == SplitState::Vertical;
|
||||
const winrt::Windows::Foundation::Point translationForUs = (weAreVertical) ? Point{ delta.X, 0 } : Point{ 0, delta.Y };
|
||||
|
||||
// Decide on direction based on delta
|
||||
ResizeDirection dir = ResizeDirection::None;
|
||||
if (_splitState == SplitState::Vertical)
|
||||
{
|
||||
if (translationForUs.X < 0)
|
||||
{
|
||||
dir = ResizeDirection::Left;
|
||||
}
|
||||
else if (translationForUs.X > 0)
|
||||
{
|
||||
dir = ResizeDirection::Right;
|
||||
}
|
||||
}
|
||||
else if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
if (translationForUs.Y < 0)
|
||||
{
|
||||
dir = ResizeDirection::Up;
|
||||
}
|
||||
else if (translationForUs.Y > 0)
|
||||
{
|
||||
dir = ResizeDirection::Down;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize in the given direction
|
||||
if (dir != ResizeDirection::None)
|
||||
{
|
||||
// turn delta into a percentage
|
||||
base::ClampedNumeric<float> amount;
|
||||
base::ClampedNumeric<float> actualDimension;
|
||||
if (dir == ResizeDirection::Left || dir == ResizeDirection::Right)
|
||||
{
|
||||
amount = translationForUs.X;
|
||||
actualDimension = base::ClampedNumeric<float>(_root.ActualWidth());
|
||||
}
|
||||
else if (dir == ResizeDirection::Up || dir == ResizeDirection::Down)
|
||||
{
|
||||
amount = translationForUs.Y;
|
||||
actualDimension = base::ClampedNumeric<float>(_root.ActualHeight());
|
||||
}
|
||||
const auto scaledAmount = amount * scaleFactor;
|
||||
const auto percentDelta = scaledAmount / actualDimension;
|
||||
|
||||
_Resize(dir, percentDelta.Abs());
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempt to navigate from the sourcePane according to direction.
|
||||
// - If the direction is NextInOrder or PreviousInOrder, the next or previous
|
||||
@@ -985,17 +1172,7 @@ void Pane::_ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectab
|
||||
// - <none>
|
||||
void Pane::Close()
|
||||
{
|
||||
// Pane has two events, CloseRequested and Closed. CloseRequested is raised by the content asking to be closed,
|
||||
// but also by the window who owns the tab when it's closing. The event is then caught by the TerminalTab which
|
||||
// calls Close() which then raises the Closed event. Now, if this is the last pane in the window, this will result
|
||||
// in the window raising CloseRequested again which leads to infinite recursion, so we need to guard against that.
|
||||
// Ideally we would have just a single event in the future.
|
||||
if (_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_closed = true;
|
||||
_setPaneContent(nullptr);
|
||||
// Fire our Closed event to tell our parent that we should be removed.
|
||||
Closed.raise(nullptr, nullptr);
|
||||
}
|
||||
@@ -1007,7 +1184,7 @@ void Pane::Shutdown()
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
_content.Close();
|
||||
_setPaneContent(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1411,7 +1588,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
_borders = _GetCommonBorders();
|
||||
|
||||
// take the control, profile, id and isDefTermSession of the pane that _wasn't_ closed.
|
||||
_content = remainingChild->_content;
|
||||
_setPaneContent(remainingChild->_takePaneContent());
|
||||
_id = remainingChild->Id();
|
||||
|
||||
// Revoke the old event handlers. Remove both the handlers for the panes
|
||||
@@ -1716,6 +1893,34 @@ void Pane::_SetupChildCloseHandlers()
|
||||
});
|
||||
}
|
||||
|
||||
// With this method you take ownership of the control from this Pane.
|
||||
// Assign it to another Pane with _setPaneContent() or Close() it.
|
||||
IPaneContent Pane::_takePaneContent()
|
||||
{
|
||||
_closeRequestedRevoker.revoke();
|
||||
return std::move(_content);
|
||||
}
|
||||
|
||||
// This method safely sets the content of the Pane. It'll ensure to revoke and
|
||||
// assign event handlers, and to Close() the existing content if there's any.
|
||||
// The new content can be nullptr to remove any content.
|
||||
void Pane::_setPaneContent(IPaneContent content)
|
||||
{
|
||||
// The IPaneContent::Close() implementation may be buggy and raise the CloseRequested event again.
|
||||
// _takePaneContent() avoids this as it revokes the event handler.
|
||||
if (const auto c = _takePaneContent())
|
||||
{
|
||||
c.Close();
|
||||
}
|
||||
|
||||
_content = std::move(content);
|
||||
|
||||
if (_content)
|
||||
{
|
||||
_closeRequestedRevoker = _content.CloseRequested(winrt::auto_revoke, [this](auto&&, auto&&) { Close(); });
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets up row/column definitions for this pane. There are three total
|
||||
// row/cols. The middle one is for the separator. The first and third are for
|
||||
@@ -1847,6 +2052,9 @@ Borders Pane::_GetCommonBorders()
|
||||
// - <none>
|
||||
void Pane::_ApplySplitDefinitions()
|
||||
{
|
||||
// Remove our old handler, if we had one.
|
||||
_manipulationDeltaRevoker.revoke();
|
||||
|
||||
if (_splitState == SplitState::Vertical)
|
||||
{
|
||||
Controls::Grid::SetColumn(_borderFirst, 0);
|
||||
@@ -1871,6 +2079,17 @@ void Pane::_ApplySplitDefinitions()
|
||||
_firstChild->_ApplySplitDefinitions();
|
||||
_secondChild->_ApplySplitDefinitions();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(_IsLeaf());
|
||||
// If we're a leaf, then add a ManipulationDelta handler.
|
||||
_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });
|
||||
}
|
||||
|
||||
_root.ManipulationMode(Xaml::Input::ManipulationModes::TranslateX |
|
||||
Xaml::Input::ManipulationModes::TranslateRailsX |
|
||||
Xaml::Input::ManipulationModes::TranslateY |
|
||||
Xaml::Input::ManipulationModes::TranslateRailsY);
|
||||
_UpdateBorders();
|
||||
}
|
||||
|
||||
@@ -2254,6 +2473,9 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
|
||||
// Create a new pane from ourself
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
_firstChild->ManipulationRequested(_firstManipulatedToken);
|
||||
_secondChild->ManipulationRequested(_secondManipulatedToken);
|
||||
|
||||
// Since we are a parent we don't have borders normally,
|
||||
// so set them temporarily for when we update our split definition.
|
||||
_borders = _GetCommonBorders();
|
||||
@@ -2266,8 +2488,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
|
||||
else
|
||||
{
|
||||
// Move our control, guid, isDefTermSession into the first one.
|
||||
_firstChild = std::make_shared<Pane>(_content);
|
||||
_content = nullptr;
|
||||
_firstChild = std::make_shared<Pane>(_takePaneContent());
|
||||
_firstChild->_broadcastEnabled = _broadcastEnabled;
|
||||
}
|
||||
|
||||
@@ -2292,6 +2513,9 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
|
||||
|
||||
_ApplySplitDefinitions();
|
||||
|
||||
_firstManipulatedToken = _firstChild->ManipulationRequested({ this, &Pane::_handleOrBubbleManipulation });
|
||||
_secondManipulatedToken = _secondChild->ManipulationRequested({ this, &Pane::_handleOrBubbleManipulation });
|
||||
|
||||
// Register event handlers on our children to handle their Close events
|
||||
_SetupChildCloseHandlers();
|
||||
|
||||
@@ -2462,6 +2686,11 @@ bool Pane::_HasChild(const std::shared_ptr<Pane> child)
|
||||
});
|
||||
}
|
||||
|
||||
winrt::TerminalApp::TerminalPaneContent Pane::_getTerminalContent() const
|
||||
{
|
||||
return _IsLeaf() ? _content.try_as<winrt::TerminalApp::TerminalPaneContent>() : nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Recursive function that finds a pane with the given ID
|
||||
// Arguments:
|
||||
|
||||
@@ -62,7 +62,7 @@ struct PaneResources
|
||||
class Pane : public std::enable_shared_from_this<Pane>
|
||||
{
|
||||
public:
|
||||
Pane(const winrt::TerminalApp::IPaneContent& content,
|
||||
Pane(winrt::TerminalApp::IPaneContent content,
|
||||
const bool lastFocused = false);
|
||||
|
||||
Pane(std::shared_ptr<Pane> first,
|
||||
@@ -109,7 +109,8 @@ public:
|
||||
winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetTerminalArgsForPane(winrt::TerminalApp::BuildStartupKind kind) const;
|
||||
|
||||
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
|
||||
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction, float amount = .05f);
|
||||
|
||||
std::shared_ptr<Pane> NavigateDirection(const std::shared_ptr<Pane> sourcePane,
|
||||
const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
|
||||
const std::vector<uint32_t>& mruPanes);
|
||||
@@ -223,6 +224,7 @@ public:
|
||||
til::event<gotFocusArgs> GotFocus;
|
||||
til::event<winrt::delegate<std::shared_ptr<Pane>>> LostFocus;
|
||||
til::event<winrt::delegate<std::shared_ptr<Pane>>> Detached;
|
||||
til::event<winrt::delegate<std::shared_ptr<Pane>, winrt::Windows::Foundation::Point, Borders>> ManipulationRequested;
|
||||
|
||||
private:
|
||||
struct PanePoint;
|
||||
@@ -248,14 +250,22 @@ private:
|
||||
|
||||
std::optional<uint32_t> _id;
|
||||
std::weak_ptr<Pane> _parentChildPath{};
|
||||
bool _closed{ false };
|
||||
bool _lastActive{ false };
|
||||
winrt::event_token _firstClosedToken{ 0 };
|
||||
winrt::event_token _secondClosedToken{ 0 };
|
||||
|
||||
winrt::event_token _firstManipulatedToken{ 0 };
|
||||
winrt::event_token _secondManipulatedToken{ 0 };
|
||||
|
||||
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
|
||||
|
||||
winrt::Windows::UI::Xaml::UIElement::ManipulationDelta_revoker _manipulationDeltaRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::ManipulationStarted_revoker _manipulationStartedRevoker;
|
||||
bool _shouldManipulate{ false };
|
||||
|
||||
winrt::TerminalApp::IPaneContent::CloseRequested_revoker _closeRequestedRevoker;
|
||||
|
||||
Borders _borders{ Borders::None };
|
||||
|
||||
bool _zoomed{ false };
|
||||
@@ -264,11 +274,10 @@ private:
|
||||
bool _IsLeaf() const noexcept;
|
||||
bool _HasFocusedChild() const noexcept;
|
||||
void _SetupChildCloseHandlers();
|
||||
winrt::TerminalApp::IPaneContent _takePaneContent();
|
||||
void _setPaneContent(winrt::TerminalApp::IPaneContent content);
|
||||
bool _HasChild(const std::shared_ptr<Pane> child);
|
||||
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const
|
||||
{
|
||||
return _IsLeaf() ? _content.try_as<winrt::TerminalApp::TerminalPaneContent>() : nullptr;
|
||||
}
|
||||
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const;
|
||||
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
|
||||
const float splitSize,
|
||||
@@ -281,7 +290,9 @@ private:
|
||||
Borders _GetCommonBorders();
|
||||
winrt::Windows::UI::Xaml::Media::SolidColorBrush _ComputeBorderColor();
|
||||
|
||||
bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
void _handleOrBubbleManipulation(std::shared_ptr<Pane> sender, const winrt::Windows::Foundation::Point delta, Borders side);
|
||||
void _handleManipulation(const winrt::Windows::Foundation::Point delta);
|
||||
bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction, float amount);
|
||||
|
||||
std::shared_ptr<Pane> _FindParentOfPane(const std::shared_ptr<Pane> pane);
|
||||
std::pair<PanePoint, PanePoint> _GetOffsetsForPane(const PanePoint parentOffset) const;
|
||||
@@ -304,6 +315,11 @@ private:
|
||||
void _ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
|
||||
void _ManipulationStartedHandler(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::UI::Xaml::Input::ManipulationStartedRoutedEventArgs& e);
|
||||
void _ManipulationDeltaHandler(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs& e);
|
||||
|
||||
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
|
||||
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
|
||||
SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
|
||||
@@ -288,6 +288,15 @@
|
||||
<data name="CmdCommandArgDesc" xml:space="preserve">
|
||||
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
|
||||
</data>
|
||||
<data name="SaveActionDesc" xml:space="preserve">
|
||||
<value>Save command line as input action</value>
|
||||
</data>
|
||||
<data name="SaveActionArgDesc" xml:space="preserve">
|
||||
<value>An optional argument</value>
|
||||
</data>
|
||||
<data name="KeyChordArgDesc" xml:space="preserve">
|
||||
<value>An optional argument</value>
|
||||
</data>
|
||||
<data name="CmdFocusTabDesc" xml:space="preserve">
|
||||
<value>Move focus to another tab</value>
|
||||
</data>
|
||||
@@ -898,4 +907,21 @@
|
||||
<data name="RestartConnectionToolTip" xml:space="preserve">
|
||||
<value>Restart the active pane connection</value>
|
||||
</data>
|
||||
<data name="ActionSavedToast.Title" xml:space="preserve">
|
||||
<value>Action saved</value>
|
||||
</data>
|
||||
<data name="ActionSaveFailedToast.Title" xml:space="preserve">
|
||||
<value>Action save failed</value>
|
||||
</data>
|
||||
<data name="SnippetPaneTitle.Text" xml:space="preserve">
|
||||
<value>Snippets</value>
|
||||
<comment>Header for a page that includes small snippets of text for the user to enter</comment>
|
||||
</data>
|
||||
<data name="SnippetsFilterBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Filter snippets...</value>
|
||||
<comment>Placeholder text for a text box to filter snippets (on the same page as the 'SnippetPaneTitle')</comment>
|
||||
</data>
|
||||
<data name="SnippetPlayButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Input this command</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
void ScratchpadContent::Close()
|
||||
{
|
||||
CloseRequested.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
INewContentArgs ScratchpadContent::GetNewTerminalArgs(const BuildStartupKind /* kind */) const
|
||||
|
||||
@@ -47,7 +47,6 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
void SettingsPaneContent::Close()
|
||||
{
|
||||
CloseRequested.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
INewContentArgs SettingsPaneContent::GetNewTerminalArgs(const BuildStartupKind /*kind*/) const
|
||||
|
||||
125
src/cascadia/TerminalApp/SnippetsPaneContent.cpp
Normal file
125
src/cascadia/TerminalApp/SnippetsPaneContent.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "SnippetsPaneContent.h"
|
||||
#include "SnippetsPaneContent.g.cpp"
|
||||
#include "FilteredTask.g.cpp"
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
namespace WUX = Windows::UI::Xaml;
|
||||
namespace MUX = Microsoft::UI::Xaml;
|
||||
using IInspectable = Windows::Foundation::IInspectable;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
SnippetsPaneContent::SnippetsPaneContent()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// auto res = Windows::UI::Xaml::Application::Current().Resources();
|
||||
auto bg = Resources().Lookup(winrt::box_value(L"PageBackground"));
|
||||
Background(bg.try_as<WUX::Media::Brush>());
|
||||
}
|
||||
|
||||
void SnippetsPaneContent::_updateFilteredCommands()
|
||||
{
|
||||
const auto& queryString = _filterBox().Text();
|
||||
|
||||
// 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.
|
||||
// That'll also trigger a PropertyChanged for the Visibility property.
|
||||
for (const auto& t : _allTasks)
|
||||
{
|
||||
t.UpdateFilter(queryString);
|
||||
}
|
||||
}
|
||||
|
||||
void SnippetsPaneContent::UpdateSettings(const CascadiaSettings& settings)
|
||||
{
|
||||
_settings = settings;
|
||||
|
||||
// You'd think that `FilterToSendInput(queryString)` would work. It
|
||||
// doesn't! That uses the queryString as the current command the user
|
||||
// has typed, then relies on the suggestions UI to _also_ filter with that
|
||||
// string.
|
||||
|
||||
const auto tasks = _settings.GlobalSettings().ActionMap().FilterToSendInput(L""); // IVector<Model::Command>
|
||||
_allTasks = winrt::single_threaded_observable_vector<TerminalApp::FilteredTask>();
|
||||
for (const auto& t : tasks)
|
||||
{
|
||||
const auto& filtered{ winrt::make<FilteredTask>(t) };
|
||||
_allTasks.Append(filtered);
|
||||
}
|
||||
_treeView().ItemsSource(_allTasks);
|
||||
|
||||
_updateFilteredCommands();
|
||||
}
|
||||
|
||||
void SnippetsPaneContent::_filterTextChanged(const IInspectable& /*sender*/,
|
||||
const Windows::UI::Xaml::RoutedEventArgs& /*args*/)
|
||||
{
|
||||
_updateFilteredCommands();
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::FrameworkElement SnippetsPaneContent::GetRoot()
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
winrt::Windows::Foundation::Size SnippetsPaneContent::MinimumSize()
|
||||
{
|
||||
return { 1, 1 };
|
||||
}
|
||||
void SnippetsPaneContent::Focus(winrt::Windows::UI::Xaml::FocusState reason)
|
||||
{
|
||||
reason;
|
||||
// _box.Focus(reason);
|
||||
}
|
||||
void SnippetsPaneContent::Close()
|
||||
{
|
||||
CloseRequested.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
INewContentArgs SnippetsPaneContent::GetNewTerminalArgs(BuildStartupKind /*kind*/) const
|
||||
{
|
||||
return BaseContentArgs(L"snippets");
|
||||
}
|
||||
|
||||
winrt::hstring SnippetsPaneContent::Icon() const
|
||||
{
|
||||
static constexpr std::wstring_view glyph{ L"\xe70b" }; // QuickNote
|
||||
return winrt::hstring{ glyph };
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::Media::Brush SnippetsPaneContent::BackgroundBrush()
|
||||
{
|
||||
return Background();
|
||||
}
|
||||
|
||||
void SnippetsPaneContent::SetLastActiveControl(const Microsoft::Terminal::Control::TermControl& control)
|
||||
{
|
||||
_control = control;
|
||||
}
|
||||
|
||||
void SnippetsPaneContent::_runCommandButtonClicked(const Windows::Foundation::IInspectable& sender,
|
||||
const Windows::UI::Xaml::RoutedEventArgs&)
|
||||
{
|
||||
if (const auto& taskVM{ sender.try_as<WUX::Controls::Button>().DataContext().try_as<FilteredTask>() })
|
||||
{
|
||||
if (const auto& strongControl{ _control.get() })
|
||||
{
|
||||
// By using the last active control as the sender here, the
|
||||
// action dispatch will send this to the active control,
|
||||
// thinking that it is the control that requested this event.
|
||||
DispatchCommandRequested.raise(strongControl, taskVM->Command());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
140
src/cascadia/TerminalApp/SnippetsPaneContent.h
Normal file
140
src/cascadia/TerminalApp/SnippetsPaneContent.h
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
#include "SnippetsPaneContent.g.h"
|
||||
#include "FilteredTask.g.h"
|
||||
#include "FilteredCommand.h"
|
||||
#include "ActionPaletteItem.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct SnippetsPaneContent : SnippetsPaneContentT<SnippetsPaneContent>
|
||||
{
|
||||
SnippetsPaneContent();
|
||||
|
||||
winrt::Windows::UI::Xaml::FrameworkElement GetRoot();
|
||||
|
||||
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
|
||||
|
||||
winrt::Windows::Foundation::Size MinimumSize();
|
||||
void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic);
|
||||
void Close();
|
||||
winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(BuildStartupKind kind) const;
|
||||
|
||||
winrt::hstring Title() { return RS_(L"SnippetPaneTitle/Text"); }
|
||||
uint64_t TaskbarState() { return 0; }
|
||||
uint64_t TaskbarProgress() { return 0; }
|
||||
bool ReadOnly() { return false; }
|
||||
winrt::hstring Icon() const;
|
||||
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() const noexcept { return nullptr; }
|
||||
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
|
||||
|
||||
void SetLastActiveControl(const Microsoft::Terminal::Control::TermControl& control);
|
||||
|
||||
til::typed_event<> ConnectionStateChanged;
|
||||
til::typed_event<IPaneContent> CloseRequested;
|
||||
til::typed_event<IPaneContent, winrt::TerminalApp::BellEventArgs> BellRequested;
|
||||
til::typed_event<IPaneContent> TitleChanged;
|
||||
til::typed_event<IPaneContent> TabColorChanged;
|
||||
til::typed_event<IPaneContent> TaskbarProgressChanged;
|
||||
til::typed_event<IPaneContent> ReadOnlyChanged;
|
||||
til::typed_event<IPaneContent> FocusRequested;
|
||||
|
||||
til::typed_event<winrt::Windows::Foundation::IInspectable, Microsoft::Terminal::Settings::Model::Command> DispatchCommandRequested;
|
||||
|
||||
private:
|
||||
friend struct SnippetsPaneContentT<SnippetsPaneContent>; // for Xaml to bind events
|
||||
|
||||
winrt::weak_ref<Microsoft::Terminal::Control::TermControl> _control{ nullptr };
|
||||
winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<TerminalApp::FilteredTask> _allTasks{ nullptr };
|
||||
|
||||
void _runCommandButtonClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs&);
|
||||
void _filterTextChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
|
||||
|
||||
void _updateFilteredCommands();
|
||||
};
|
||||
|
||||
struct FilteredTask : FilteredTaskT<FilteredTask, TerminalApp::implementation::FilteredCommand>
|
||||
{
|
||||
FilteredTask() = default;
|
||||
|
||||
FilteredTask(const winrt::Microsoft::Terminal::Settings::Model::Command& command)
|
||||
{
|
||||
_constructFilteredCommand(winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(command, L""));
|
||||
_command = command;
|
||||
|
||||
// The Children() method must always return a non-null vector
|
||||
_children = winrt::single_threaded_observable_vector<TerminalApp::FilteredTask>();
|
||||
if (_command.HasNestedCommands())
|
||||
{
|
||||
for (const auto& [_, child] : _command.NestedCommands())
|
||||
{
|
||||
auto vm{ winrt::make<FilteredTask>(child) };
|
||||
_children.Append(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateFilter(const winrt::hstring& filter) override
|
||||
{
|
||||
TerminalApp::implementation::FilteredCommand::UpdateFilter(filter);
|
||||
for (const auto& c : _children)
|
||||
{
|
||||
c.UpdateFilter(filter);
|
||||
}
|
||||
|
||||
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Visibility" });
|
||||
}
|
||||
|
||||
winrt::hstring Input()
|
||||
{
|
||||
if (const auto& actionItem{ _Item.try_as<winrt::TerminalApp::ActionPaletteItem>() })
|
||||
{
|
||||
if (const auto& command{ actionItem.Command() })
|
||||
{
|
||||
if (const auto& sendInput{ command.ActionAndArgs().Args().try_as<winrt::Microsoft::Terminal::Settings::Model::SendInputArgs>() })
|
||||
{
|
||||
return sendInput.Input();
|
||||
}
|
||||
}
|
||||
}
|
||||
return L"";
|
||||
};
|
||||
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<TerminalApp::FilteredTask> Children() { return _children; }
|
||||
bool HasChildren() { return _children.Size() > 0; }
|
||||
winrt::Microsoft::Terminal::Settings::Model::Command Command() { return _command; }
|
||||
|
||||
// Used to control if this item is visible in the TreeView. Turns out,
|
||||
// TreeView is in fact sane enough to remove items entirely if they're
|
||||
// Collapsed.
|
||||
winrt::Windows::UI::Xaml::Visibility Visibility()
|
||||
{
|
||||
// Is there no filter, or do we match it?
|
||||
if (_Filter.empty() || _Weight > 0)
|
||||
{
|
||||
return winrt::Windows::UI::Xaml::Visibility::Visible;
|
||||
}
|
||||
// If we don't match, maybe one of our children does
|
||||
auto totalWeight = _Weight;
|
||||
for (const auto& c : _children)
|
||||
{
|
||||
totalWeight += c.Weight();
|
||||
}
|
||||
|
||||
return totalWeight > 0 ? winrt::Windows::UI::Xaml::Visibility::Visible : winrt::Windows::UI::Xaml::Visibility::Collapsed;
|
||||
};
|
||||
|
||||
private:
|
||||
winrt::Microsoft::Terminal::Settings::Model::Command _command{ nullptr };
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<TerminalApp::FilteredTask> _children{ nullptr };
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(SnippetsPaneContent);
|
||||
}
|
||||
236
src/cascadia/TerminalApp/SnippetsPaneContent.xaml
Normal file
236
src/cascadia/TerminalApp/SnippetsPaneContent.xaml
Normal file
@@ -0,0 +1,236 @@
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<UserControl x:Class="TerminalApp.SnippetsPaneContent"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:SettingsModel="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
AllowFocusOnInteraction="True"
|
||||
IsTabStop="True"
|
||||
TabNavigation="Cycle"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<Style x:Key="PlayButtonTemplate"
|
||||
TargetType="Button">
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="4" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="ButtonBaseElement"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Viewbox Width="14"
|
||||
Height="14">
|
||||
<Grid>
|
||||
<FontIcon x:Name="ButtonBackgroundIcon"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Foreground="{ThemeResource PlayButtonHoveredColor}"
|
||||
Glyph=""
|
||||
Visibility="Collapsed" />
|
||||
<FontIcon x:Name="ButtonOutlineIcon"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Foreground="{ThemeResource PlayButtonNormalColor}"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
|
||||
<VisualState x:Name="Normal">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ButtonBackgroundIcon.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ButtonBackgroundIcon.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Disabled" />
|
||||
</VisualStateGroup>
|
||||
|
||||
<VisualStateGroup x:Name="PlayButtonStates">
|
||||
<VisualState x:Name="Ready">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ButtonBackgroundIcon.Glyph" Value="" />
|
||||
<Setter Target="ButtonOutlineIcon.Glyph" Value="" />
|
||||
<Setter Target="StatusProgress.IsActive" Value="False" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="TaskItemTemplate"
|
||||
x:DataType="local:FilteredTask">
|
||||
<mux:TreeViewItem x:Name="rootItem"
|
||||
ItemsSource="{x:Bind Children}"
|
||||
Visibility="{x:Bind Visibility, Mode=OneWay}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter Grid.Column="0">
|
||||
<IconSourceElement Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Item.Icon), Mode=OneTime}"
|
||||
Visibility="Collapsed" />
|
||||
</ContentPresenter>
|
||||
|
||||
<Button x:Uid="SnippetPlayButton"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="1"
|
||||
Grid.Column="0"
|
||||
Margin="-28,0,0,0"
|
||||
Padding="3"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Click="_runCommandButtonClicked"
|
||||
Style="{StaticResource PlayButtonTemplate}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(HasChildren), Mode=OneWay}">
|
||||
|
||||
<Button.Content>
|
||||
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Glyph="" />
|
||||
<!--
|
||||
xE768 is Play, which is just an outline.
|
||||
xF5B0 is PlaySolid, also a good option. Seemed
|
||||
better to have a lightweight outline though
|
||||
-->
|
||||
</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>
|
||||
|
||||
<local:HighlightedTextControl Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind HighlightedName, Mode=OneWay}" />
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="12,0,0,6"
|
||||
FontFamily="Cascadia Mono, Consolas"
|
||||
IsTextSelectionEnabled="True"
|
||||
Style="{ThemeResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind Input}"
|
||||
Visibility="{Binding ElementName=rootItem, Path=IsSelected}" />
|
||||
</Grid>
|
||||
</mux:TreeViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<!-- same as in MainPage, this is SolidBackgroundFillColorTertiary -->
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Color x:Key="PageBackground">#282828</Color>
|
||||
<Color x:Key="PlayButtonHoveredColor">#90ef90</Color>
|
||||
<Color x:Key="PlayButtonNormalColor">#8888</Color>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Color x:Key="PageBackground">#F9F9F9</Color>
|
||||
<Color x:Key="PlayButtonHoveredColor">#257f01</Color>
|
||||
<Color x:Key="PlayButtonNormalColor">#88222222</Color>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<!-- Define resources for HighContrast mode here -->
|
||||
<StaticResource x:Key="PageBackground"
|
||||
ResourceKey="SystemColorWindowColorBrush" />
|
||||
</ResourceDictionary>
|
||||
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="{ThemeResource PageBackground}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock x:Name="_title"
|
||||
x:Uid="SnippetPaneTitle"
|
||||
Grid.Row="0"
|
||||
Margin="4"
|
||||
FontSize="24" />
|
||||
|
||||
<TextBox x:Name="_filterBox"
|
||||
x:Uid="SnippetsFilterBox"
|
||||
Grid.Row="1"
|
||||
Margin="8,0,8,8"
|
||||
TextChanged="_filterTextChanged" />
|
||||
|
||||
<mux:TreeView x:Name="_treeView"
|
||||
Grid.Row="2"
|
||||
CanDragItems="False"
|
||||
CanReorderItems="False"
|
||||
ItemTemplate="{StaticResource TaskItemTemplate}" />
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -508,7 +508,7 @@ namespace winrt::TerminalApp::implementation
|
||||
automationPeer.RaiseNotificationEvent(
|
||||
Automation::Peers::AutomationNotificationKind::ItemAdded,
|
||||
Automation::Peers::AutomationNotificationProcessing::MostRecent,
|
||||
paletteItem.Name() + L" " + paletteItem.KeyChordText(),
|
||||
paletteItem.Name(),
|
||||
L"SuggestionsControlSelectedItemChanged" /* unique name for this notification category */);
|
||||
}
|
||||
}
|
||||
@@ -751,17 +751,13 @@ namespace winrt::TerminalApp::implementation
|
||||
return _filteredActions;
|
||||
}
|
||||
|
||||
void SuggestionsControl::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap)
|
||||
{
|
||||
_actionMap = actionMap;
|
||||
}
|
||||
|
||||
void SuggestionsControl::SetCommands(const Collections::IVector<Command>& actions)
|
||||
{
|
||||
_allCommands.Clear();
|
||||
for (const auto& action : actions)
|
||||
{
|
||||
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
|
||||
// key chords aren't relevant in the suggestions control, so make the palette item with just the command and no keys
|
||||
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action, L"") };
|
||||
auto filteredCommand{ winrt::make<FilteredCommand>(actionPaletteItem) };
|
||||
_allCommands.Append(filteredCommand);
|
||||
}
|
||||
@@ -915,7 +911,7 @@ namespace winrt::TerminalApp::implementation
|
||||
for (const auto& nameAndCommand : parentCommand.NestedCommands())
|
||||
{
|
||||
const auto action = nameAndCommand.Value();
|
||||
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
|
||||
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action, L"") };
|
||||
auto nestedFilteredCommand{ winrt::make<FilteredCommand>(nestedActionPaletteItem) };
|
||||
_currentNestedCommands.Append(nestedFilteredCommand);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace winrt::TerminalApp::implementation
|
||||
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> FilteredActions();
|
||||
|
||||
void SetCommands(const Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command>& actions);
|
||||
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
|
||||
|
||||
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace TerminalApp
|
||||
SuggestionsMode Mode { get; set; };
|
||||
|
||||
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
|
||||
void SetActionMap(Microsoft.Terminal.Settings.Model.IActionMapView actionMap);
|
||||
void SelectNextItem(Boolean moveDown);
|
||||
|
||||
void Open(SuggestionsMode mode, IVector<Microsoft.Terminal.Settings.Model.Command> commands, String filterText, Windows.Foundation.Point anchor, Windows.Foundation.Size space, Single characterHeight);
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
MinHeight="0"
|
||||
Padding="16,0,12,0"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}"
|
||||
AutomationProperties.Name="{x:Bind Item.Name, Mode=OneWay}"
|
||||
FontSize="12" />
|
||||
</DataTemplate>
|
||||
|
||||
@@ -192,7 +192,8 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void TabBase::_UpdateSwitchToTabKeyChord()
|
||||
{
|
||||
const auto keyChord = _actionMap ? _actionMap.GetKeyBindingForAction(ShortcutAction::SwitchToTab, SwitchToTabArgs{ _TabViewIndex }) : nullptr;
|
||||
const auto id = fmt::format(FMT_COMPILE(L"Terminal.SwitchToTab{}"), _TabViewIndex);
|
||||
const auto keyChord{ _actionMap.GetKeyBindingForAction(id) };
|
||||
const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L"";
|
||||
|
||||
if (_keyChord == keyChordText)
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
<Page Include="SuggestionsControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="SnippetsPaneContent.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
@@ -161,6 +164,9 @@
|
||||
<ClInclude Include="ScratchpadContent.h">
|
||||
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SnippetsPaneContent.h">
|
||||
<DependentUpon>SnippetsPaneContent.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SettingsPaneContent.h">
|
||||
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -274,6 +280,9 @@
|
||||
<ClCompile Include="ScratchpadContent.cpp">
|
||||
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SnippetsPaneContent.cpp">
|
||||
<DependentUpon>SnippetsPaneContent.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SettingsPaneContent.cpp">
|
||||
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -352,12 +361,17 @@
|
||||
</Midl>
|
||||
<Midl Include="FilteredCommand.idl" />
|
||||
<Midl Include="IPaneContent.idl" />
|
||||
<Midl Include="TerminalPaneContent.idl" />
|
||||
<Midl Include="TerminalPaneContent.idl" >
|
||||
<DependentUpon>TaskPaneContent.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="TerminalSettingsCache.idl" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources\en-US\Resources.resw" />
|
||||
<PRIResource Include="Resources\en-US\Resources.resw">
|
||||
<SubType>Designer</SubType>
|
||||
</PRIResource>
|
||||
<PRIResource Include="Resources\en-US\ContextMenu.resw" />
|
||||
<OCResourceDirectory Include="Resources" />
|
||||
</ItemGroup>
|
||||
@@ -466,10 +480,8 @@
|
||||
</ItemDefinitionGroup>
|
||||
<!-- ========================= Globals ======================== -->
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
|
||||
|
||||
<!--
|
||||
By default, the PRI file will contain resource paths beginning with the
|
||||
project name. Since we enabled XBF embedding, this *also* includes App.xbf.
|
||||
@@ -490,4 +502,4 @@
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -4,18 +4,11 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "TerminalPage.h"
|
||||
#include "TerminalPage.g.cpp"
|
||||
#include "RenameWindowRequestedArgs.g.cpp"
|
||||
#include "RequestMoveContentArgs.g.cpp"
|
||||
#include "RequestReceiveContentArgs.g.cpp"
|
||||
#include "LaunchPositionRequest.g.cpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <inc/WindowingBehavior.h>
|
||||
#include <LibraryResources.h>
|
||||
#include <TerminalCore/ControlKeyStates.hpp>
|
||||
#include <til/latch.h>
|
||||
#include <Utils.h>
|
||||
|
||||
#include "../../types/inc/utils.hpp"
|
||||
#include "App.h"
|
||||
@@ -23,8 +16,14 @@
|
||||
#include "DebugTapConnection.h"
|
||||
#include "SettingsPaneContent.h"
|
||||
#include "ScratchpadContent.h"
|
||||
#include "SnippetsPaneContent.h"
|
||||
#include "TabRowControl.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "TerminalPage.g.cpp"
|
||||
#include "RenameWindowRequestedArgs.g.cpp"
|
||||
#include "RequestMoveContentArgs.g.cpp"
|
||||
#include "RequestReceiveContentArgs.g.cpp"
|
||||
#include "LaunchPositionRequest.g.cpp"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
@@ -123,7 +122,6 @@ namespace winrt::TerminalApp::implementation
|
||||
// to happen before the Settings UI is reloaded and tries to re-read those values.
|
||||
if (const auto p = CommandPaletteElement())
|
||||
{
|
||||
p.SetCommands(_settings.GlobalSettings().ActionMap().ExpandedCommands());
|
||||
p.SetActionMap(_settings.ActionMap());
|
||||
}
|
||||
|
||||
@@ -454,10 +452,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// - command - command to dispatch
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_OnDispatchCommandRequested(const IInspectable& /*sender*/, const Microsoft::Terminal::Settings::Model::Command& command)
|
||||
void TerminalPage::_OnDispatchCommandRequested(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::Command& command)
|
||||
{
|
||||
const auto& actionAndArgs = command.ActionAndArgs();
|
||||
_actionDispatch->DoAction(actionAndArgs);
|
||||
_actionDispatch->DoAction(sender, actionAndArgs);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -826,7 +824,7 @@ namespace winrt::TerminalApp::implementation
|
||||
newTabFlyout.Items().Append(settingsItem);
|
||||
|
||||
auto actionMap = _settings.ActionMap();
|
||||
const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) };
|
||||
const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(L"Terminal.OpenSettingsUI") };
|
||||
if (settingsKeyChord)
|
||||
{
|
||||
_SetAcceleratorForMenuItem(settingsItem, settingsKeyChord);
|
||||
@@ -848,7 +846,7 @@ namespace winrt::TerminalApp::implementation
|
||||
commandPaletteFlyout.Click({ this, &TerminalPage::_CommandPaletteButtonOnClick });
|
||||
newTabFlyout.Items().Append(commandPaletteFlyout);
|
||||
|
||||
const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
|
||||
const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction(L"Terminal.ToggleCommandPalette") };
|
||||
if (commandPaletteKeyChord)
|
||||
{
|
||||
_SetAcceleratorForMenuItem(commandPaletteFlyout, commandPaletteKeyChord);
|
||||
@@ -1005,6 +1003,18 @@ namespace winrt::TerminalApp::implementation
|
||||
items.push_back(profileItem);
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Action:
|
||||
{
|
||||
const auto actionEntry = entry.as<ActionEntry>();
|
||||
const auto actionId = actionEntry.ActionId();
|
||||
if (_settings.ActionMap().GetActionById(actionId))
|
||||
{
|
||||
auto actionItem = _CreateNewTabFlyoutAction(actionId);
|
||||
items.push_back(actionItem);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1023,7 +1033,8 @@ namespace winrt::TerminalApp::implementation
|
||||
// NewTab(ProfileIndex=N) action
|
||||
NewTerminalArgs newTerminalArgs{ profileIndex };
|
||||
NewTabArgs newTabArgs{ newTerminalArgs };
|
||||
auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) };
|
||||
const auto id = fmt::format(FMT_COMPILE(L"Terminal.OpenNewTabProfile{}"), profileIndex);
|
||||
const auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(id) };
|
||||
|
||||
// make sure we find one to display
|
||||
if (profileKeyChord)
|
||||
@@ -1094,6 +1105,42 @@ namespace winrt::TerminalApp::implementation
|
||||
return profileMenuItem;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method creates a flyout menu item for a given action
|
||||
// It makes sure to set the correct icon, keybinding, and click-action.
|
||||
WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutAction(const winrt::hstring& actionId)
|
||||
{
|
||||
auto actionMenuItem = WUX::Controls::MenuFlyoutItem{};
|
||||
const auto action{ _settings.ActionMap().GetActionById(actionId) };
|
||||
const auto actionKeyChord{ _settings.ActionMap().GetKeyBindingForAction(actionId) };
|
||||
|
||||
if (actionKeyChord)
|
||||
{
|
||||
_SetAcceleratorForMenuItem(actionMenuItem, actionKeyChord);
|
||||
}
|
||||
|
||||
const auto actionName = action.Name();
|
||||
actionMenuItem.Text(actionName);
|
||||
|
||||
// If there's an icon set for this action, set it as the icon for
|
||||
// this flyout item
|
||||
const auto& iconPath = action.IconPath();
|
||||
if (!iconPath.empty())
|
||||
{
|
||||
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
|
||||
actionMenuItem.Icon(icon);
|
||||
}
|
||||
|
||||
actionMenuItem.Click([action, weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->_actionDispatch->DoAction(action.ActionAndArgs());
|
||||
}
|
||||
});
|
||||
|
||||
return actionMenuItem;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper method to create an IconElement that can be passed to MenuFlyoutItems and
|
||||
// MenuFlyoutSubItems
|
||||
@@ -1211,6 +1258,26 @@ namespace winrt::TerminalApp::implementation
|
||||
TerminalSettings settings,
|
||||
const bool inheritCursor)
|
||||
{
|
||||
// The only way to create string references to literals in WinRT is through std::optional. Fun!
|
||||
static std::optional<winrt::param::hstring> textMeasurement;
|
||||
static const auto textMeasurementInit = [&]() {
|
||||
switch (_settings.GlobalSettings().TextMeasurement())
|
||||
{
|
||||
case TextMeasurement::Graphemes:
|
||||
textMeasurement.emplace(L"graphemes");
|
||||
break;
|
||||
case TextMeasurement::Wcswidth:
|
||||
textMeasurement.emplace(L"wcswidth");
|
||||
break;
|
||||
case TextMeasurement::Console:
|
||||
textMeasurement.emplace(L"console");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
|
||||
TerminalConnection::ITerminalConnection connection{ nullptr };
|
||||
|
||||
auto connectionType = profile.ConnectionType();
|
||||
@@ -1282,6 +1349,11 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
if (textMeasurement)
|
||||
{
|
||||
valueSet.Insert(L"textMeasurement", Windows::Foundation::PropertyValue::CreateString(*textMeasurement));
|
||||
}
|
||||
|
||||
if (const auto id = settings.SessionId(); id != winrt::guid{})
|
||||
{
|
||||
valueSet.Insert(L"sessionId", Windows::Foundation::PropertyValue::CreateGuid(id));
|
||||
@@ -1671,6 +1743,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
|
||||
|
||||
term.SearchMissingCommand({ get_weak(), &TerminalPage::_SearchMissingCommandHandler });
|
||||
|
||||
// Don't even register for the event if the feature is compiled off.
|
||||
if constexpr (Feature_ShellCompletions::IsEnabled())
|
||||
{
|
||||
@@ -1689,6 +1763,12 @@ namespace winrt::TerminalApp::implementation
|
||||
page->_PopulateContextMenu(weakTerm.get(), sender.try_as<MUX::Controls::CommandBarFlyout>(), true);
|
||||
}
|
||||
});
|
||||
term.QuickFixMenu().Opening([weak = get_weak(), weakTerm](auto&& sender, auto&& /*args*/) {
|
||||
if (const auto& page{ weak.get() })
|
||||
{
|
||||
page->_PopulateQuickFixMenu(weakTerm.get(), sender.try_as<Controls::MenuFlyout>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1827,7 +1907,6 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
const auto p = FindName(L"CommandPaletteElement").as<CommandPalette>();
|
||||
|
||||
p.SetCommands(_settings.GlobalSettings().ActionMap().ExpandedCommands());
|
||||
p.SetActionMap(_settings.ActionMap());
|
||||
|
||||
// When the visibility of the command palette changes to "collapsed",
|
||||
@@ -2922,6 +3001,30 @@ namespace winrt::TerminalApp::implementation
|
||||
ShowWindowChanged.raise(*this, args);
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalPage::_SearchMissingCommandHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::SearchMissingCommandEventArgs args)
|
||||
{
|
||||
assert(!Dispatcher().HasThreadAccess());
|
||||
|
||||
if (!Feature_QuickFix::IsEnabled())
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
std::vector<hstring> suggestions;
|
||||
suggestions.reserve(1);
|
||||
suggestions.emplace_back(fmt::format(L"winget install {}", args.MissingCommand()));
|
||||
|
||||
co_await wil::resume_foreground(Dispatcher());
|
||||
|
||||
auto term = _GetActiveControl();
|
||||
if (!term)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
term.UpdateWinGetSuggestions(single_threaded_vector<hstring>(std::move(suggestions)));
|
||||
term.ShowQuickFixMenu();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Paste text from the Windows Clipboard to the focused terminal
|
||||
void TerminalPage::_PasteText()
|
||||
@@ -3205,6 +3308,15 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
content = _makeSettingsContent();
|
||||
}
|
||||
else if (paneType == L"snippets")
|
||||
{
|
||||
const auto& tasksContent{ winrt::make_self<SnippetsPaneContent>() };
|
||||
tasksContent->UpdateSettings(_settings);
|
||||
tasksContent->GetRoot().KeyDown({ this, &TerminalPage::_KeyDownHandler });
|
||||
tasksContent->DispatchCommandRequested({ this, &TerminalPage::_OnDispatchCommandRequested });
|
||||
|
||||
content = *tasksContent;
|
||||
}
|
||||
|
||||
assert(content);
|
||||
|
||||
@@ -4138,6 +4250,66 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalPage::ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord)
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
co_await wil::resume_foreground(Dispatcher());
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
// If we haven't ever loaded the TeachingTip, then do so now and
|
||||
// create the toast for it.
|
||||
if (page->_actionSavedToast == nullptr)
|
||||
{
|
||||
if (auto tip{ page->FindName(L"ActionSavedToast").try_as<MUX::Controls::TeachingTip>() })
|
||||
{
|
||||
page->_actionSavedToast = std::make_shared<Toast>(tip);
|
||||
// Make sure to use the weak ref when setting up this
|
||||
// callback.
|
||||
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
|
||||
}
|
||||
}
|
||||
_UpdateTeachingTipTheme(ActionSavedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
|
||||
|
||||
SavedActionName(name);
|
||||
SavedActionKeyChord(keyChord);
|
||||
SavedActionCommandLine(input);
|
||||
|
||||
if (page->_actionSavedToast != nullptr)
|
||||
{
|
||||
page->_actionSavedToast->Open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalPage::ActionSaveFailed(winrt::hstring message)
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
co_await wil::resume_foreground(Dispatcher());
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
// If we haven't ever loaded the TeachingTip, then do so now and
|
||||
// create the toast for it.
|
||||
if (page->_actionSaveFailedToast == nullptr)
|
||||
{
|
||||
if (auto tip{ page->FindName(L"ActionSaveFailedToast").try_as<MUX::Controls::TeachingTip>() })
|
||||
{
|
||||
page->_actionSaveFailedToast = std::make_shared<Toast>(tip);
|
||||
// Make sure to use the weak ref when setting up this
|
||||
// callback.
|
||||
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
|
||||
}
|
||||
}
|
||||
_UpdateTeachingTipTheme(ActionSaveFailedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
|
||||
|
||||
ActionSaveFailedMessage().Text(message);
|
||||
|
||||
if (page->_actionSaveFailedToast != nullptr)
|
||||
{
|
||||
page->_actionSaveFailedToast->Open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when an attempt to rename the window has failed. This will open
|
||||
// the toast displaying a message to the user that the attempt to rename
|
||||
@@ -4842,6 +5014,62 @@ namespace winrt::TerminalApp::implementation
|
||||
makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } });
|
||||
}
|
||||
|
||||
void TerminalPage::_PopulateQuickFixMenu(const TermControl& control,
|
||||
const Controls::MenuFlyout& menu)
|
||||
{
|
||||
if (!control || !menu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper lambda for dispatching a SendInput ActionAndArgs onto the
|
||||
// ShortcutActionDispatch. Used below to wire up each menu entry to the
|
||||
// respective action. Then clear the quick fix menu.
|
||||
auto weak = get_weak();
|
||||
auto makeCallback = [weak](const hstring& suggestion) {
|
||||
return [weak, suggestion](auto&&, auto&&) {
|
||||
if (auto page{ weak.get() })
|
||||
{
|
||||
const auto actionAndArgs = ActionAndArgs{ ShortcutAction::SendInput, SendInputArgs{ hstring{ L"\u0003" } + suggestion } };
|
||||
page->_actionDispatch->DoAction(actionAndArgs);
|
||||
if (auto ctrl = page->_GetActiveControl())
|
||||
{
|
||||
ctrl.ClearQuickFix();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
auto makeItem = [&menu, &makeCallback](const winrt::hstring& label,
|
||||
const winrt::hstring& icon,
|
||||
const winrt::hstring& suggestion) {
|
||||
MenuFlyoutItem item{};
|
||||
|
||||
if (!icon.empty())
|
||||
{
|
||||
auto iconElement = UI::IconPathConverter::IconWUX(icon);
|
||||
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
|
||||
item.Icon(iconElement);
|
||||
}
|
||||
|
||||
item.Text(label);
|
||||
item.Click(makeCallback(suggestion));
|
||||
menu.Items().Append(item);
|
||||
};
|
||||
|
||||
// Wire up each item to the action that should be performed. By actually
|
||||
// connecting these to actions, we ensure the implementation is
|
||||
// consistent. This also leaves room for customizing this menu with
|
||||
// actions in the future.
|
||||
|
||||
menu.Items().Clear();
|
||||
const auto quickFixes = control.CommandHistory().QuickFixes();
|
||||
for (const auto& qf : quickFixes)
|
||||
{
|
||||
makeItem(qf, L"\ue74c", qf);
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for our WindowProperties's PropertyChanged event. We'll use this
|
||||
// to pop the "Identify Window" toast when the user renames our window.
|
||||
winrt::fire_and_forget TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/,
|
||||
|
||||
@@ -146,6 +146,8 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::hstring KeyboardServiceDisabledText();
|
||||
|
||||
winrt::fire_and_forget IdentifyWindow();
|
||||
winrt::fire_and_forget ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord);
|
||||
winrt::fire_and_forget ActionSaveFailed(winrt::hstring message);
|
||||
winrt::fire_and_forget RenameFailed();
|
||||
winrt::fire_and_forget ShowTerminalWorkingDirectory();
|
||||
|
||||
@@ -199,6 +201,10 @@ namespace winrt::TerminalApp::implementation
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr);
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, PropertyChanged.raise, nullptr);
|
||||
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionName, PropertyChanged.raise, L"");
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionKeyChord, PropertyChanged.raise, L"");
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionCommandLine, PropertyChanged.raise, L"");
|
||||
|
||||
private:
|
||||
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
|
||||
std::optional<HWND> _hostingHwnd;
|
||||
@@ -258,6 +264,8 @@ namespace winrt::TerminalApp::implementation
|
||||
bool _isEmbeddingInboundListener{ false };
|
||||
|
||||
std::shared_ptr<Toast> _windowIdToast{ nullptr };
|
||||
std::shared_ptr<Toast> _actionSavedToast{ nullptr };
|
||||
std::shared_ptr<Toast> _actionSaveFailedToast{ nullptr };
|
||||
std::shared_ptr<Toast> _windowRenameFailedToast{ nullptr };
|
||||
std::shared_ptr<Toast> _windowCwdToast{ nullptr };
|
||||
|
||||
@@ -300,6 +308,7 @@ namespace winrt::TerminalApp::implementation
|
||||
std::vector<winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase> _CreateNewTabFlyoutItems(winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::NewTabMenuEntry> entries);
|
||||
winrt::Windows::UI::Xaml::Controls::IconElement _CreateNewTabFlyoutIcon(const winrt::hstring& icon);
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex);
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutAction(const winrt::hstring& actionId);
|
||||
|
||||
void _OpenNewTabDropdown();
|
||||
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs);
|
||||
@@ -520,6 +529,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void _OpenSuggestions(const Microsoft::Terminal::Control::TermControl& sender, Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::Command> commandsCollection, winrt::TerminalApp::SuggestionsMode mode, winrt::hstring filterText);
|
||||
|
||||
void _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
|
||||
winrt::fire_and_forget _SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args);
|
||||
|
||||
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
|
||||
@@ -537,6 +547,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void _sendDraggedTabToWindow(const winrt::hstring& windowId, const uint32_t tabIndex, std::optional<til::point> dragPoint);
|
||||
|
||||
void _PopulateContextMenu(const Microsoft::Terminal::Control::TermControl& control, const Microsoft::UI::Xaml::Controls::CommandBarFlyout& sender, const bool withSelection);
|
||||
void _PopulateQuickFixMenu(const Microsoft::Terminal::Control::TermControl& control, const Windows::UI::Xaml::Controls::MenuFlyout& sender);
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyout _CreateRunAsAdminFlyout(int profileIndex);
|
||||
|
||||
winrt::Microsoft::Terminal::Control::TermControl _senderOrActiveControl(const winrt::Windows::Foundation::IInspectable& sender);
|
||||
|
||||
@@ -72,6 +72,10 @@ namespace TerminalApp
|
||||
void IdentifyWindow();
|
||||
void RenameFailed();
|
||||
|
||||
String SavedActionName { get; };
|
||||
String SavedActionKeyChord { get; };
|
||||
String SavedActionCommandLine { get; };
|
||||
|
||||
// We cannot use the default XAML APIs because we want to make sure
|
||||
// that there's only one application-global dialog visible at a time,
|
||||
// and because of GH#5224.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d">
|
||||
@@ -204,5 +205,43 @@
|
||||
Title="{x:Bind WindowProperties.VirtualWorkingDirectory, Mode=OneWay}"
|
||||
x:Load="False"
|
||||
IsLightDismissEnabled="True" />
|
||||
|
||||
<mux:TeachingTip x:Name="ActionSavedToast"
|
||||
x:Uid="ActionSavedToast"
|
||||
Title="Action Saved"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:Load="False"
|
||||
IsLightDismissEnabled="True">
|
||||
<mux:TeachingTip.Content>
|
||||
<StackPanel HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<TextBlock x:Name="ActionSavedNameText"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionName), Mode=OneWay}">
|
||||
<Run Text="Name: " />
|
||||
<Run Text="{x:Bind SavedActionName, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="ActionSavedKeyChordText"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionKeyChord), Mode=OneWay}">
|
||||
<Run Text="Key Chord: " />
|
||||
<Run Text="{x:Bind SavedActionKeyChord, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="ActionSavedCommandLineText"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionCommandLine), Mode=OneWay}">
|
||||
<Run Text="Input: " />
|
||||
<Run Text="{x:Bind SavedActionCommandLine, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</mux:TeachingTip.Content>
|
||||
</mux:TeachingTip>
|
||||
<mux:TeachingTip x:Name="ActionSaveFailedToast"
|
||||
x:Uid="ActionSaveFailedToast"
|
||||
Title="Action Save Failed"
|
||||
x:Load="False"
|
||||
IsLightDismissEnabled="True">
|
||||
<mux:TeachingTip.Content>
|
||||
<TextBlock x:Name="ActionSaveFailedMessage"
|
||||
Text="" />
|
||||
</mux:TeachingTip.Content>
|
||||
</mux:TeachingTip>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -78,8 +78,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_bellPlayer = nullptr;
|
||||
_bellPlayerCreated = false;
|
||||
}
|
||||
|
||||
CloseRequested.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
winrt::hstring TerminalPaneContent::Icon() const
|
||||
@@ -239,19 +237,20 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (_profile)
|
||||
{
|
||||
if (_isDefTermSession && _profile.CloseOnExit() == CloseOnExitMode::Automatic)
|
||||
{
|
||||
// For 'automatic', we only care about the connection state if we were launched by Terminal
|
||||
// Since we were launched via defterm, ignore the connection state (i.e. we treat the
|
||||
// close on exit mode as 'always', see GH #13325 for discussion)
|
||||
Close();
|
||||
}
|
||||
|
||||
const auto mode = _profile.CloseOnExit();
|
||||
if ((mode == CloseOnExitMode::Always) ||
|
||||
((mode == CloseOnExitMode::Graceful || mode == CloseOnExitMode::Automatic) && newConnectionState == ConnectionState::Closed))
|
||||
|
||||
if (
|
||||
// This one is obvious: If the user asked for "always" we do just that.
|
||||
(mode == CloseOnExitMode::Always) ||
|
||||
// Otherwise, and unless the user asked for the opposite of "always",
|
||||
// close the pane when the connection closed gracefully (not failed).
|
||||
(mode != CloseOnExitMode::Never && newConnectionState == ConnectionState::Closed) ||
|
||||
// However, defterm handoff can result in Windows Terminal randomly opening which may be annoying,
|
||||
// so by default we should at least always close the pane, even if the command failed.
|
||||
// See GH #13325 for discussion.
|
||||
(mode == CloseOnExitMode::Automatic && _isDefTermSession))
|
||||
{
|
||||
Close();
|
||||
CloseRequested.raise(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,7 +330,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPaneContent::_closeTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
Close();
|
||||
CloseRequested.raise(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void TerminalPaneContent::_restartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace winrt::TerminalApp::implementation
|
||||
Windows::Foundation::Size GridUnitSize();
|
||||
|
||||
til::typed_event<TerminalApp::TerminalPaneContent, winrt::Windows::Foundation::IInspectable> RestartTerminalRequested;
|
||||
|
||||
til::typed_event<> ConnectionStateChanged;
|
||||
til::typed_event<IPaneContent> CloseRequested;
|
||||
til::typed_event<IPaneContent, winrt::TerminalApp::BellEventArgs> BellRequested;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import "IPaneContent.idl";
|
||||
import "TerminalSettingsCache.idl";
|
||||
import "FilteredCommand.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
@@ -16,4 +17,21 @@ namespace TerminalApp
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<TerminalPaneContent, Object> RestartTerminalRequested;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FilteredTask : TerminalApp.FilteredCommand
|
||||
{
|
||||
String Input{ get; };
|
||||
Windows.Foundation.Collections.IObservableVector<FilteredTask> Children { get; };
|
||||
Boolean HasChildren { get; };
|
||||
Windows.UI.Xaml.Visibility Visibility { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass SnippetsPaneContent : Windows.UI.Xaml.Controls.UserControl, IPaneContent
|
||||
{
|
||||
SnippetsPaneContent();
|
||||
void SetLastActiveControl(Microsoft.Terminal.Control.TermControl control);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Settings.Model.Command> DispatchCommandRequested;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -947,26 +947,6 @@ namespace winrt::TerminalApp::implementation
|
||||
auto dispatcher = TabViewItem().Dispatcher();
|
||||
ContentEventTokens events{};
|
||||
|
||||
events.CloseRequested = content.CloseRequested(
|
||||
winrt::auto_revoke,
|
||||
[this](auto&& sender, auto&&) {
|
||||
if (const auto content{ sender.try_as<TerminalApp::IPaneContent>() })
|
||||
{
|
||||
// Calling Close() while walking the tree is not safe, because Close() mutates the tree.
|
||||
const auto pane = _rootPane->_FindPane([&](const auto& p) -> std::shared_ptr<Pane> {
|
||||
if (p->GetContent() == content)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
return {};
|
||||
});
|
||||
if (pane)
|
||||
{
|
||||
pane->Close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
events.TitleChanged = content.TitleChanged(
|
||||
winrt::auto_revoke,
|
||||
[dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
|
||||
@@ -1243,6 +1223,18 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Raise our own ActivePaneChanged event.
|
||||
ActivePaneChanged.raise(*this, nullptr);
|
||||
|
||||
const auto content{ pane->GetContent() };
|
||||
if (const auto termContent{ content.try_as<winrt::TerminalApp::TerminalPaneContent>() })
|
||||
{
|
||||
const auto& termControl{ termContent.GetTermControl() };
|
||||
_rootPane->WalkTree([termControl](const auto& p) {
|
||||
if (const auto& taskPane{ p->GetContent().try_as<SnippetsPaneContent>() })
|
||||
{
|
||||
taskPane.SetLastActiveControl(termControl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -134,7 +134,6 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::TerminalApp::IPaneContent::ConnectionStateChanged_revoker ConnectionStateChanged;
|
||||
winrt::TerminalApp::IPaneContent::ReadOnlyChanged_revoker ReadOnlyChanged;
|
||||
winrt::TerminalApp::IPaneContent::FocusRequested_revoker FocusRequested;
|
||||
winrt::TerminalApp::IPaneContent::CloseRequested_revoker CloseRequested;
|
||||
|
||||
// These events literally only apply if the content is a TermControl.
|
||||
winrt::Microsoft::Terminal::Control::TermControl::KeySent_revoker KeySent;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "ConptyConnection.h"
|
||||
|
||||
#include <conpty-static.h>
|
||||
#include <til/string.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include "CTerminalHandoff.h"
|
||||
@@ -259,11 +258,39 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_cols = unbox_prop_or<uint32_t>(settings, L"initialCols", _cols);
|
||||
_sessionId = unbox_prop_or<winrt::guid>(settings, L"sessionId", _sessionId);
|
||||
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
|
||||
_inheritCursor = unbox_prop_or<bool>(settings, L"inheritCursor", _inheritCursor);
|
||||
_profileGuid = unbox_prop_or<winrt::guid>(settings, L"profileGuid", _profileGuid);
|
||||
|
||||
const auto& initialEnvironment{ unbox_prop_or<winrt::hstring>(settings, L"initialEnvironment", L"") };
|
||||
_flags = PSEUDOCONSOLE_RESIZE_QUIRK;
|
||||
|
||||
// If we're using an existing buffer, we want the new connection
|
||||
// to reuse the existing cursor. When not setting this flag, the
|
||||
// PseudoConsole sends a clear screen VT code which our renderer
|
||||
// interprets into making all the previous lines be outside the
|
||||
// current viewport.
|
||||
const auto inheritCursor = unbox_prop_or<bool>(settings, L"inheritCursor", false);
|
||||
if (inheritCursor)
|
||||
{
|
||||
_flags |= PSEUDOCONSOLE_INHERIT_CURSOR;
|
||||
}
|
||||
|
||||
const auto textMeasurement = unbox_prop_or<winrt::hstring>(settings, L"textMeasurement", winrt::hstring{});
|
||||
if (!textMeasurement.empty())
|
||||
{
|
||||
if (textMeasurement == L"graphemes")
|
||||
{
|
||||
_flags |= PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES;
|
||||
}
|
||||
else if (textMeasurement == L"wcswidth")
|
||||
{
|
||||
_flags |= PSEUDOCONSOLE_GLYPH_WIDTH_WCSWIDTH;
|
||||
}
|
||||
else if (textMeasurement == L"console")
|
||||
{
|
||||
_flags |= PSEUDOCONSOLE_GLYPH_WIDTH_CONSOLE;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& initialEnvironment{ unbox_prop_or<winrt::hstring>(settings, L"initialEnvironment", L"") };
|
||||
const bool reloadEnvironmentVariables = unbox_prop_or<bool>(settings, L"reloadEnvironmentVariables", false);
|
||||
|
||||
if (reloadEnvironmentVariables)
|
||||
@@ -318,19 +345,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// handoff from an already-started PTY process.
|
||||
if (!_inPipe)
|
||||
{
|
||||
DWORD flags = PSEUDOCONSOLE_RESIZE_QUIRK;
|
||||
|
||||
// If we're using an existing buffer, we want the new connection
|
||||
// to reuse the existing cursor. When not setting this flag, the
|
||||
// PseudoConsole sends a clear screen VT code which our renderer
|
||||
// interprets into making all the previous lines be outside the
|
||||
// current viewport.
|
||||
if (_inheritCursor)
|
||||
{
|
||||
flags |= PSEUDOCONSOLE_INHERIT_CURSOR;
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(til::unwrap_coord_size(dimensions), flags, &_inPipe, &_outPipe, &_hPC));
|
||||
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(til::unwrap_coord_size(dimensions), _flags, &_inPipe, &_outPipe, &_hPC));
|
||||
|
||||
if (_initialParentHwnd != 0)
|
||||
{
|
||||
@@ -431,12 +446,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
try
|
||||
{
|
||||
// GH#11556 - make sure to format the error code to this string as an UNSIGNED int
|
||||
winrt::hstring exitText{ fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status)) };
|
||||
TerminalOutput.raise(L"\r\n");
|
||||
TerminalOutput.raise(exitText);
|
||||
TerminalOutput.raise(L"\r\n");
|
||||
TerminalOutput.raise(RS_(L"CtrlDToClose"));
|
||||
TerminalOutput.raise(L"\r\n");
|
||||
const auto msg1 = fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status));
|
||||
const auto msg2 = RS_(L"CtrlDToClose");
|
||||
const auto msg = fmt::format(FMT_COMPILE(L"\r\n{}\r\n{}\r\n"), msg1, msg2);
|
||||
TerminalOutput.raise(msg);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
til::u8state _u8State{};
|
||||
std::wstring _u16Str{};
|
||||
std::array<char, 4096> _buffer{};
|
||||
bool _inheritCursor{ false };
|
||||
DWORD _flags{ 0 };
|
||||
|
||||
til::env _initialEnv{};
|
||||
guid _profileGuid{};
|
||||
|
||||
@@ -13,15 +13,18 @@
|
||||
#include <unicode.hpp>
|
||||
#include <utils.hpp>
|
||||
#include <WinUser.h>
|
||||
//#include <winrt/Microsoft.Management.Deployment.h>
|
||||
|
||||
#include "EventArgs.h"
|
||||
#include "../../renderer/atlas/AtlasEngine.h"
|
||||
#include "../../renderer/base/renderer.hpp"
|
||||
#include "../../renderer/uia/UiaRenderer.hpp"
|
||||
#include "../../types/inc/CodepointWidthDetector.hpp"
|
||||
|
||||
#include "ControlCore.g.cpp"
|
||||
#include "SelectionColor.g.cpp"
|
||||
|
||||
//using namespace winrt::Microsoft::Management::Deployment;
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
using namespace ::Microsoft::Console::VirtualTerminal;
|
||||
using namespace ::Microsoft::Terminal::Core;
|
||||
@@ -71,6 +74,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_desiredFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, DEFAULT_FONT_SIZE, CP_UTF8 },
|
||||
_actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }
|
||||
{
|
||||
static const auto textMeasurementInit = [&]() {
|
||||
TextMeasurementMode mode = TextMeasurementMode::Graphemes;
|
||||
switch (settings.TextMeasurement())
|
||||
{
|
||||
case TextMeasurement::Wcswidth:
|
||||
mode = TextMeasurementMode::Wcswidth;
|
||||
break;
|
||||
case TextMeasurement::Console:
|
||||
mode = TextMeasurementMode::Console;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
CodepointWidthDetector::Singleton().Reset(mode);
|
||||
return true;
|
||||
}();
|
||||
|
||||
_settings = winrt::make_self<implementation::ControlSettings>(settings, unfocusedAppearance);
|
||||
_terminal = std::make_shared<::Microsoft::Terminal::Core::Terminal>();
|
||||
const auto lock = _terminal->LockForWriting();
|
||||
@@ -110,6 +130,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
auto pfnCompletionsChanged = [=](auto&& menuJson, auto&& replaceLength) { _terminalCompletionsChanged(menuJson, replaceLength); };
|
||||
_terminal->CompletionsChangedCallback(pfnCompletionsChanged);
|
||||
|
||||
auto pfnSearchMissingCommand = [this](auto&& PH1) { _terminalSearchMissingCommand(std::forward<decltype(PH1)>(PH1)); };
|
||||
_terminal->SetSearchMissingCommandCallback(pfnSearchMissingCommand);
|
||||
|
||||
auto pfnClearQuickFix = [this] { _terminalClearQuickFix(); };
|
||||
_terminal->SetClearQuickFixCallback(pfnClearQuickFix);
|
||||
|
||||
// MSFT 33353327: Initialize the renderer in the ctor instead of Initialize().
|
||||
// We need the renderer to be ready to accept new engines before the SwapChainPanel is ready to go.
|
||||
// If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach
|
||||
@@ -1609,6 +1635,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_midiAudio.PlayNote(reinterpret_cast<HWND>(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast<std::chrono::milliseconds>(duration));
|
||||
}
|
||||
|
||||
void ControlCore::_terminalSearchMissingCommand(std::wstring_view missingCommand)
|
||||
{
|
||||
SearchMissingCommand.raise(*this, make<implementation::SearchMissingCommandEventArgs>(hstring{ missingCommand }));
|
||||
}
|
||||
|
||||
void ControlCore::_terminalClearQuickFix()
|
||||
{
|
||||
ClearQuickFix.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
bool ControlCore::HasSelection() const
|
||||
{
|
||||
const auto lock = _terminal->LockForReading();
|
||||
@@ -2277,11 +2313,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
commands.pop_back();
|
||||
}
|
||||
|
||||
|
||||
auto context = winrt::make_self<CommandHistoryContext>(std::move(commands));
|
||||
context->CurrentCommandline(trimmedCurrentCommand);
|
||||
// TODO CARLOS: should we delete this after a new command is run? Or delete it after a suggestion is used? Or just after the next winget suggestion (current impl)?
|
||||
// No clue which we should do. Thoughts?
|
||||
context->QuickFixes(_cachedQuickFixes);
|
||||
return *context;
|
||||
}
|
||||
|
||||
void ControlCore::UpdateQuickFixes(const Windows::Foundation::Collections::IVector<hstring>& quickFixes)
|
||||
{
|
||||
_cachedQuickFixes = quickFixes;
|
||||
}
|
||||
|
||||
Core::Scheme ControlCore::ColorScheme() const noexcept
|
||||
{
|
||||
Core::Scheme s;
|
||||
@@ -2645,21 +2690,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Select the region of text between [s.start, s.end), in buffer space
|
||||
void ControlCore::_selectSpan(til::point_span s)
|
||||
{
|
||||
// s.end is an _exclusive_ point. We need an inclusive one. But
|
||||
// decrement in bounds wants an inclusive one. If you pass an exclusive
|
||||
// one, then it might assert at you for being out of bounds. So we also
|
||||
// take care of the case that the end point is outside the viewport
|
||||
// manually.
|
||||
// s.end is an _exclusive_ point. We need an inclusive one.
|
||||
const auto bufferSize{ _terminal->GetTextBuffer().GetSize() };
|
||||
til::point inclusiveEnd = s.end;
|
||||
if (s.end.x == bufferSize.Width())
|
||||
{
|
||||
inclusiveEnd = til::point{ std::max(0, s.end.x - 1), s.end.y };
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferSize.DecrementInBounds(inclusiveEnd);
|
||||
}
|
||||
bufferSize.DecrementInBounds(inclusiveEnd);
|
||||
|
||||
_terminal->SelectNewRegion(s.start, inclusiveEnd);
|
||||
_renderer->TriggerSelection();
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
til::property<Windows::Foundation::Collections::IVector<winrt::hstring>> History;
|
||||
til::property<winrt::hstring> CurrentCommandline;
|
||||
til::property<Windows::Foundation::Collections::IVector<winrt::hstring>> QuickFixes;
|
||||
|
||||
CommandHistoryContext(std::vector<winrt::hstring>&& history)
|
||||
{
|
||||
@@ -241,6 +242,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
hstring ReadEntireBuffer() const;
|
||||
Control::CommandHistoryContext CommandHistory() const;
|
||||
void UpdateQuickFixes(const Windows::Foundation::Collections::IVector<hstring>& quickFixes);
|
||||
|
||||
void AdjustOpacity(const float opacity, const bool relative);
|
||||
|
||||
@@ -283,6 +285,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
til::typed_event<IInspectable, Control::UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
|
||||
til::typed_event<IInspectable, Control::OpenHyperlinkEventArgs> OpenHyperlink;
|
||||
til::typed_event<IInspectable, Control::CompletionsChangedEventArgs> CompletionsChanged;
|
||||
til::typed_event<IInspectable, Control::SearchMissingCommandEventArgs> SearchMissingCommand;
|
||||
til::typed_event<> ClearQuickFix;
|
||||
|
||||
til::typed_event<> CloseTerminalRequested;
|
||||
til::typed_event<> RestartTerminalRequested;
|
||||
@@ -352,6 +356,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
til::point _contextMenuBufferPosition{ 0, 0 };
|
||||
|
||||
Windows::Foundation::Collections::IVector<int32_t> _cachedSearchResultRows{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<hstring> _cachedQuickFixes{ nullptr };
|
||||
|
||||
void _setupDispatcherAndCallbacks();
|
||||
|
||||
bool _setFontSizeUnderLock(float fontSize);
|
||||
@@ -375,6 +382,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _terminalPlayMidiNote(const int noteNumber,
|
||||
const int velocity,
|
||||
const std::chrono::microseconds duration);
|
||||
void _terminalSearchMissingCommand(std::wstring_view missingCommand);
|
||||
void _terminalClearQuickFix();
|
||||
|
||||
winrt::fire_and_forget _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength);
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace Microsoft.Terminal.Control
|
||||
{
|
||||
IVector<String> History { get; };
|
||||
String CurrentCommandline { get; };
|
||||
IVector<String> QuickFixes { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ControlCore : ICoreState
|
||||
@@ -174,6 +175,8 @@ namespace Microsoft.Terminal.Control
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, SearchMissingCommandEventArgs> SearchMissingCommand;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ClearQuickFix;
|
||||
|
||||
// These events are always called from the UI thread (bugs aside)
|
||||
event Windows.Foundation.TypedEventHandler<Object, FontSizeChangedArgs> FontSizeChanged;
|
||||
|
||||
@@ -18,3 +18,4 @@
|
||||
#include "KeySentEventArgs.g.cpp"
|
||||
#include "CharSentEventArgs.g.cpp"
|
||||
#include "StringSentEventArgs.g.cpp"
|
||||
#include "SearchMissingCommandEventArgs.g.cpp"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "KeySentEventArgs.g.h"
|
||||
#include "CharSentEventArgs.g.h"
|
||||
#include "StringSentEventArgs.g.h"
|
||||
#include "SearchMissingCommandEventArgs.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
@@ -211,6 +212,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, Text);
|
||||
};
|
||||
|
||||
struct SearchMissingCommandEventArgs : public SearchMissingCommandEventArgsT<SearchMissingCommandEventArgs>
|
||||
{
|
||||
public:
|
||||
SearchMissingCommandEventArgs(const winrt::hstring& missingCommand) :
|
||||
_MissingCommand(missingCommand) {}
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, MissingCommand);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::factory_implementation
|
||||
|
||||
@@ -18,6 +18,13 @@ namespace Microsoft.Terminal.Control
|
||||
Direct3D11,
|
||||
};
|
||||
|
||||
enum TextMeasurement
|
||||
{
|
||||
Graphemes,
|
||||
Wcswidth,
|
||||
Console,
|
||||
};
|
||||
|
||||
runtimeclass FontSizeChangedArgs
|
||||
{
|
||||
Int32 Width { get; };
|
||||
@@ -119,4 +126,9 @@ namespace Microsoft.Terminal.Control
|
||||
{
|
||||
String Text { get; };
|
||||
}
|
||||
|
||||
runtimeclass SearchMissingCommandEventArgs
|
||||
{
|
||||
String MissingCommand { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ HRESULT HwndTerminal::Refresh(const til::size windowSize, _Out_ til::size* dimen
|
||||
_renderer->TriggerRedrawAll();
|
||||
|
||||
// Convert our new dimensions to characters
|
||||
const auto viewInPixels = Viewport::FromDimensions(windowSize);
|
||||
const auto viewInPixels = Viewport::FromDimensions({}, windowSize);
|
||||
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
|
||||
|
||||
// Guard against resizing the window to 0 columns/rows, which the text buffer classes don't really support.
|
||||
@@ -464,7 +464,7 @@ try
|
||||
|
||||
Viewport viewInPixels;
|
||||
{
|
||||
const auto viewInCharacters = Viewport::FromDimensions(dimensionsInCharacters);
|
||||
const auto viewInCharacters = Viewport::FromDimensions({}, dimensionsInCharacters);
|
||||
const auto lock = publicTerminal->_terminal->LockForReading();
|
||||
viewInPixels = publicTerminal->_renderEngine->GetViewportInPixels(viewInCharacters);
|
||||
}
|
||||
@@ -491,7 +491,7 @@ try
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
|
||||
const auto viewInPixels = Viewport::FromDimensions({ width, height });
|
||||
const auto viewInPixels = Viewport::FromDimensions({}, { width, height });
|
||||
const auto lock = publicTerminal->_terminal->LockForReading();
|
||||
const auto viewInCharacters = publicTerminal->_renderEngine->GetViewportInCharacters(viewInPixels);
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace Microsoft.Terminal.Control
|
||||
Microsoft.Terminal.Control.GraphicsAPI GraphicsAPI { get; };
|
||||
Boolean DisablePartialInvalidation { get; };
|
||||
Boolean SoftwareRendering { get; };
|
||||
Microsoft.Terminal.Control.TextMeasurement TextMeasurement { get; };
|
||||
Boolean ShowMarks { get; };
|
||||
Boolean UseBackgroundImageForWindow { get; };
|
||||
Boolean RightClickContextMenu { get; };
|
||||
|
||||
Binary file not shown.
@@ -296,6 +296,12 @@ Please either install the missing font or choose another one.</value>
|
||||
<value>Select output</value>
|
||||
<comment>The tooltip for a button for selecting all of a command's output</comment>
|
||||
</data>
|
||||
<data name="QuickFixButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Quick fix</value>
|
||||
</data>
|
||||
<data name="QuickFixButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Quick fix</value>
|
||||
</data>
|
||||
<data name="SessionRestoreMessage" xml:space="preserve">
|
||||
<value>Restored</value>
|
||||
<comment>"Restored" as in "This content was restored"</comment>
|
||||
|
||||
@@ -155,12 +155,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// search box remains in Visible state (though not really *visible*) during the
|
||||
// first load. So, we only need to apply this check here (after checking that
|
||||
// we're done initializing).
|
||||
if (Visibility() == Visibility::Visible)
|
||||
if (IsOpen())
|
||||
{
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop ongoing close animation if any
|
||||
if (CloseAnimation().GetCurrentState() == Media::Animation::ClockState::Active)
|
||||
{
|
||||
CloseAnimation().Stop();
|
||||
}
|
||||
|
||||
VisualStateManager::GoToState(*this, L"Opened", false);
|
||||
|
||||
// Call the callback only after we're in Opened state. Setting focus
|
||||
@@ -196,6 +202,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
bool SearchBoxControl::IsOpen()
|
||||
{
|
||||
return Visibility() == Visibility::Visible && CloseAnimation().GetCurrentState() != Media::Animation::ClockState::Active;
|
||||
}
|
||||
|
||||
winrt::hstring SearchBoxControl::Text()
|
||||
{
|
||||
return TextBox().Text();
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void TextBoxKeyDown(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
|
||||
void Open(std::function<void()> callback);
|
||||
void Close();
|
||||
bool IsOpen();
|
||||
|
||||
winrt::hstring Text();
|
||||
bool GoForward();
|
||||
|
||||
@@ -40,6 +40,9 @@ constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(
|
||||
// The minimum delay between emitting warning bells
|
||||
constexpr const auto TerminalWarningBellInterval = std::chrono::milliseconds(1000);
|
||||
|
||||
constexpr std::wstring_view StateNormal{ L"Normal" };
|
||||
constexpr std::wstring_view StateCollapsed{ L"Collapsed" };
|
||||
|
||||
DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::Control::CopyFormat);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::Control::MouseButtonState);
|
||||
|
||||
@@ -220,9 +223,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_revokers.CloseTerminalRequested = _core.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCloseTerminalRequested });
|
||||
_revokers.CompletionsChanged = _core.CompletionsChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCompletionsChanged });
|
||||
_revokers.RestartTerminalRequested = _core.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleRestartTerminalRequested });
|
||||
_revokers.SearchMissingCommand = _core.SearchMissingCommand(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSearchMissingCommand });
|
||||
|
||||
_revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard });
|
||||
|
||||
_revokers.ClearQuickFix = _core.ClearQuickFix(winrt::auto_revoke, { get_weak(), &TermControl::_clearQuickFix });
|
||||
|
||||
// Initialize the terminal only once the swapchainpanel is loaded - that
|
||||
// way, we'll be able to query the real pixel size it got on layout
|
||||
_layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
|
||||
@@ -332,6 +338,29 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Feature_QuickFix::IsEnabled())
|
||||
{
|
||||
auto quickFixBtn{ QuickFixButton() };
|
||||
quickFixBtn.PointerEntered({ get_weak(), &TermControl::QuickFixButton_PointerEntered });
|
||||
quickFixBtn.PointerExited({ get_weak(), &TermControl::QuickFixButton_PointerExited });
|
||||
}
|
||||
}
|
||||
|
||||
void TermControl::QuickFixButton_PointerEntered(const IInspectable& /*sender*/, const PointerRoutedEventArgs& /*e*/)
|
||||
{
|
||||
if (!_IsClosing() && _quickFixButtonCollapsible)
|
||||
{
|
||||
VisualStateManager::GoToState(*this, StateNormal, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TermControl::QuickFixButton_PointerExited(const IInspectable& /*sender*/, const PointerRoutedEventArgs& /*e*/)
|
||||
{
|
||||
if (!_IsClosing() && _quickFixButtonCollapsible)
|
||||
{
|
||||
VisualStateManager::GoToState(*this, StateCollapsed, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
@@ -493,7 +522,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
if (_searchBox && _searchBox->Visibility() == Visibility::Visible)
|
||||
if (_searchBox && _searchBox->IsOpen())
|
||||
{
|
||||
const auto core = winrt::get_self<ControlCore>(_core);
|
||||
const auto& searchMatches = core->SearchResultRows();
|
||||
@@ -538,6 +567,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// but since code paths differ, extra work is required to ensure correctness.
|
||||
if (!_core.HasMultiLineSelection())
|
||||
{
|
||||
_core.SnapSearchResultToSelection(true);
|
||||
const auto selectedLine{ _core.SelectedText(true) };
|
||||
_searchBox->PopulateTextbox(selectedLine);
|
||||
}
|
||||
@@ -554,13 +584,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// This is called when a Find Next/Previous Match action is triggered.
|
||||
void TermControl::SearchMatch(const bool goForward)
|
||||
{
|
||||
if (_IsClosing())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!_searchBox || _searchBox->Visibility() != Visibility::Visible)
|
||||
if (!_searchBox || !_searchBox->IsOpen())
|
||||
{
|
||||
CreateSearchBoxControl();
|
||||
}
|
||||
@@ -602,7 +633,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - The handler for the "search criteria changed" event. Clears selection and initiates a new search.
|
||||
// - The handler for the "search criteria changed" event. Initiates a new search.
|
||||
// Arguments:
|
||||
// - text: the text to search
|
||||
// - goForward: indicates whether the search should be performed forward (if set to true) or backward
|
||||
@@ -616,7 +647,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
if (_searchBox && _searchBox->Visibility() == Visibility::Visible)
|
||||
{
|
||||
_handleSearchResults(_core.Search(text, goForward, caseSensitive, regularExpression, false));
|
||||
// We only want to update the search results based on the new text. Set
|
||||
// `resetOnly` to true so we don't accidentally update the current match index.
|
||||
const auto result = _core.Search(text, goForward, caseSensitive, regularExpression, true);
|
||||
_handleSearchResults(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,6 +668,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_searchBox->Close();
|
||||
_core.ClearSearch();
|
||||
|
||||
// Clear search highlights scroll marks (by triggering an update after closing the search box)
|
||||
if (_showMarksInScrollbar)
|
||||
{
|
||||
const auto scrollBar = ScrollBar();
|
||||
ScrollBarUpdate update{
|
||||
.newValue = scrollBar.Value(),
|
||||
.newMaximum = scrollBar.Maximum(),
|
||||
.newMinimum = scrollBar.Minimum(),
|
||||
.newViewportSize = scrollBar.ViewportSize(),
|
||||
};
|
||||
_updateScrollBar->Run(update);
|
||||
}
|
||||
|
||||
// Set focus back to terminal control
|
||||
this->Focus(FocusState::Programmatic);
|
||||
}
|
||||
@@ -808,6 +855,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// When we hot reload the settings, the core will send us a scrollbar
|
||||
// update. If we enabled scrollbar marks, then great, when we handle
|
||||
// that message, we'll redraw them.
|
||||
|
||||
if (Feature_QuickFix::IsEnabled())
|
||||
{
|
||||
// update the position of the quick fix menu (in case we changed the padding)
|
||||
ShowQuickFixMenu();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -2313,6 +2366,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
_updateSelectionMarkers(nullptr, winrt::make<UpdateSelectionMarkersEventArgs>(false));
|
||||
}
|
||||
|
||||
ShowQuickFixMenu();
|
||||
}
|
||||
|
||||
hstring TermControl::Title()
|
||||
@@ -3472,7 +3527,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const Control::FontSizeChangedArgs& args)
|
||||
{
|
||||
// scale the selection markers to be the size of a cell
|
||||
auto scaleMarker = [args, dpiScale{ SwapChainPanel().CompositionScaleX() }](const Windows::UI::Xaml::Shapes::Path& shape) {
|
||||
const auto dpiScale = SwapChainPanel().CompositionScaleX();
|
||||
auto scaleMarker = [args, &dpiScale](const Windows::UI::Xaml::Shapes::Path& shape) {
|
||||
// The selection markers were designed to be 5x14 in size,
|
||||
// so use those dimensions below for the scaling
|
||||
const auto scaleX = args.Width() / 5.0 / dpiScale;
|
||||
@@ -3488,6 +3544,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
};
|
||||
scaleMarker(SelectionStartMarker());
|
||||
scaleMarker(SelectionEndMarker());
|
||||
|
||||
if (Feature_QuickFix::IsEnabled())
|
||||
{
|
||||
auto quickFixBtn = QuickFixButton();
|
||||
quickFixBtn.Height(args.Height() / dpiScale);
|
||||
QuickFixIcon().FontSize(std::min(static_cast<double>(args.Width() / dpiScale), GetPadding().Left));
|
||||
ShowQuickFixMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void TermControl::_coreRaisedNotice(const IInspectable& /*sender*/,
|
||||
@@ -3556,6 +3620,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return _core.CommandHistory();
|
||||
}
|
||||
|
||||
void TermControl::UpdateWinGetSuggestions(Windows::Foundation::Collections::IVector<hstring> suggestions)
|
||||
{
|
||||
get_self<ControlCore>(_core)->UpdateQuickFixes(suggestions);
|
||||
}
|
||||
|
||||
Core::Scheme TermControl::ColorScheme() const noexcept
|
||||
{
|
||||
return _core.ColorScheme();
|
||||
@@ -3595,7 +3664,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
void TermControl::_refreshSearch()
|
||||
{
|
||||
if (!_searchBox || _searchBox->Visibility() != Visibility::Visible)
|
||||
if (!_searchBox || !_searchBox->IsOpen())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -3619,7 +3688,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
_searchBox->SetStatus(results.TotalMatches, results.CurrentMatch, results.SearchRegexInvalid);
|
||||
// Only show status when we have a search term
|
||||
if (_searchBox->Text().empty())
|
||||
{
|
||||
_searchBox->ClearStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox->SetStatus(results.TotalMatches, results.CurrentMatch, results.SearchRegexInvalid);
|
||||
}
|
||||
|
||||
if (results.SearchInvalidated)
|
||||
{
|
||||
@@ -3770,6 +3847,67 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_showContextMenuAt(_toControlOrigin(cursorPos));
|
||||
}
|
||||
|
||||
double TermControl::CalculateQuickFixButtonWidth()
|
||||
{
|
||||
return GetPadding().Left;
|
||||
}
|
||||
|
||||
double TermControl::CalculateQuickFixButtonCollapsedWidth()
|
||||
{
|
||||
return GetPadding().Left / 3.0;
|
||||
}
|
||||
|
||||
void TermControl::ShowQuickFixMenu()
|
||||
{
|
||||
if (!_quickFixesAvailable)
|
||||
{
|
||||
QuickFixButton().Visibility(Visibility::Collapsed);
|
||||
return;
|
||||
}
|
||||
|
||||
auto quickFixBtn{ QuickFixButton() };
|
||||
|
||||
// If the gutter is narrow, display the collapsed version
|
||||
const auto& termPadding = GetPadding();
|
||||
|
||||
_quickFixButtonCollapsible = termPadding.Left < CharacterDimensions().Width;
|
||||
VisualStateManager::GoToState(*this, !_quickFixButtonCollapsible ? StateNormal : StateCollapsed, false);
|
||||
|
||||
const auto rd = get_self<ControlCore>(_core)->GetRenderData();
|
||||
rd->LockConsole();
|
||||
const auto viewportBufferPosition = rd->GetViewport();
|
||||
const auto cursorBufferPosition = rd->GetCursorPosition();
|
||||
rd->UnlockConsole();
|
||||
if (cursorBufferPosition.y < viewportBufferPosition.Top() || cursorBufferPosition.y > viewportBufferPosition.BottomExclusive())
|
||||
{
|
||||
quickFixBtn.Visibility(Visibility::Collapsed);
|
||||
return;
|
||||
}
|
||||
|
||||
// draw the button in the gutter
|
||||
const auto& cursorPosInDips = CursorPositionInDips();
|
||||
Controls::Canvas::SetLeft(quickFixBtn, -termPadding.Left);
|
||||
Controls::Canvas::SetTop(quickFixBtn, cursorPosInDips.Y);
|
||||
quickFixBtn.Visibility(Visibility::Visible);
|
||||
}
|
||||
|
||||
void TermControl::_bubbleSearchMissingCommand(const IInspectable& /*sender*/, const Control::SearchMissingCommandEventArgs& args)
|
||||
{
|
||||
_quickFixesAvailable = true;
|
||||
SearchMissingCommand.raise(*this, args);
|
||||
}
|
||||
|
||||
void TermControl::_clearQuickFix(const IInspectable& /*sender*/, const IInspectable& /*args*/)
|
||||
{
|
||||
_quickFixesAvailable = false;
|
||||
ShowQuickFixMenu();
|
||||
}
|
||||
|
||||
void TermControl::ClearQuickFix()
|
||||
{
|
||||
_clearQuickFix(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void TermControl::_PasteCommandHandler(const IInspectable& /*sender*/,
|
||||
const IInspectable& /*args*/)
|
||||
{
|
||||
|
||||
@@ -75,6 +75,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension);
|
||||
|
||||
Windows::Foundation::Point CursorPositionInDips();
|
||||
double CalculateQuickFixButtonWidth();
|
||||
double CalculateQuickFixButtonCollapsedWidth();
|
||||
|
||||
void WindowVisibilityChanged(const bool showOrHide);
|
||||
|
||||
@@ -167,6 +169,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
hstring ReadEntireBuffer() const;
|
||||
Control::CommandHistoryContext CommandHistory() const;
|
||||
void UpdateWinGetSuggestions(Windows::Foundation::Collections::IVector<hstring> suggestions);
|
||||
|
||||
winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept;
|
||||
void ColorScheme(const winrt::Microsoft::Terminal::Core::Scheme& scheme) const noexcept;
|
||||
@@ -178,6 +181,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void RawWriteString(const winrt::hstring& text);
|
||||
|
||||
void ShowContextMenu();
|
||||
void ShowQuickFixMenu();
|
||||
void ClearQuickFix();
|
||||
|
||||
void Detach();
|
||||
|
||||
@@ -202,17 +207,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
til::typed_event<IInspectable, Control::KeySentEventArgs> KeySent;
|
||||
til::typed_event<IInspectable, Control::CharSentEventArgs> CharSent;
|
||||
til::typed_event<IInspectable, Control::StringSentEventArgs> StringSent;
|
||||
til::typed_event<IInspectable, Control::SearchMissingCommandEventArgs> SearchMissingCommand;
|
||||
|
||||
// UNDER NO CIRCUMSTANCES SHOULD YOU ADD A (PROJECTED_)FORWARDED_TYPED_EVENT HERE
|
||||
// Those attach the handler to the core directly, and will explode if
|
||||
// the core ever gets detached & reattached to another window.
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(CompletionsChanged, IInspectable, Control::CompletionsChangedEventArgs);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(CompletionsChanged, IInspectable, Control::CompletionsChangedEventArgs);
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(RestartTerminalRequested, IInspectable, IInspectable);
|
||||
|
||||
BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
|
||||
@@ -242,6 +248,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
bool _closing{ false };
|
||||
bool _focused{ false };
|
||||
bool _initializedTerminal{ false };
|
||||
bool _quickFixButtonCollapsible{ false };
|
||||
bool _quickFixesAvailable{ false };
|
||||
|
||||
std::shared_ptr<ThrottledFuncLeading> _playWarningBell;
|
||||
|
||||
@@ -332,6 +340,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _MouseWheelHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
|
||||
void _ScrollbarChangeHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs& e);
|
||||
|
||||
void QuickFixButton_PointerEntered(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
|
||||
void QuickFixButton_PointerExited(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
|
||||
|
||||
void _GotFocusHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void _LostFocusHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
|
||||
@@ -394,6 +405,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _contextMenuHandler(IInspectable sender, Control::ContextMenuRequestedEventArgs args);
|
||||
void _showContextMenuAt(const til::point& controlRelativePos);
|
||||
|
||||
void _bubbleSearchMissingCommand(const IInspectable& sender, const Control::SearchMissingCommandEventArgs& args);
|
||||
void _clearQuickFix(const IInspectable& sender, const IInspectable& args);
|
||||
|
||||
void _PasteCommandHandler(const IInspectable& sender, const IInspectable& args);
|
||||
void _CopyCommandHandler(const IInspectable& sender, const IInspectable& args);
|
||||
void _SearchCommandHandler(const IInspectable& sender, const IInspectable& args);
|
||||
@@ -423,6 +437,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
Control::ControlCore::CloseTerminalRequested_revoker CloseTerminalRequested;
|
||||
Control::ControlCore::CompletionsChanged_revoker CompletionsChanged;
|
||||
Control::ControlCore::RestartTerminalRequested_revoker RestartTerminalRequested;
|
||||
Control::ControlCore::SearchMissingCommand_revoker SearchMissingCommand;
|
||||
Control::ControlCore::ClearQuickFix_revoker ClearQuickFix;
|
||||
|
||||
// These are set up in _InitializeTerminal
|
||||
Control::ControlCore::RendererWarning_revoker RendererWarning;
|
||||
|
||||
@@ -69,10 +69,12 @@ namespace Microsoft.Terminal.Control
|
||||
event Windows.Foundation.TypedEventHandler<Object, KeySentEventArgs> KeySent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, CharSentEventArgs> CharSent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, StringSentEventArgs> StringSent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, SearchMissingCommandEventArgs> SearchMissingCommand;
|
||||
|
||||
|
||||
Microsoft.UI.Xaml.Controls.CommandBarFlyout ContextMenu { get; };
|
||||
Microsoft.UI.Xaml.Controls.CommandBarFlyout SelectionContextMenu { get; };
|
||||
Windows.UI.Xaml.Controls.MenuFlyout QuickFixMenu { get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<TermControl, Windows.UI.Xaml.RoutedEventArgs> Initialized;
|
||||
// This is an event handler forwarder for the underlying connection.
|
||||
@@ -125,6 +127,7 @@ namespace Microsoft.Terminal.Control
|
||||
|
||||
String ReadEntireBuffer();
|
||||
CommandHistoryContext CommandHistory();
|
||||
void UpdateWinGetSuggestions(Windows.Foundation.Collections.IVector<String> suggestions);
|
||||
|
||||
void AdjustOpacity(Single Opacity, Boolean relative);
|
||||
|
||||
@@ -141,8 +144,12 @@ namespace Microsoft.Terminal.Control
|
||||
void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode);
|
||||
|
||||
Windows.Foundation.Point CursorPositionInDips { get; };
|
||||
Double CalculateQuickFixButtonWidth { get; };
|
||||
Double CalculateQuickFixButtonCollapsedWidth { get; };
|
||||
|
||||
void ShowContextMenu();
|
||||
void ShowQuickFixMenu();
|
||||
void ClearQuickFix();
|
||||
|
||||
void Detach();
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,28 @@
|
||||
</ToolTip>
|
||||
</ToolTipService.ToolTip>
|
||||
</Border>
|
||||
|
||||
<Button x:Name="QuickFixButton"
|
||||
x:Uid="QuickFixButton"
|
||||
Width="{x:Bind CalculateQuickFixButtonWidth}"
|
||||
Padding="0"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
AllowFocusOnInteraction="False"
|
||||
CornerRadius="0,5,5,0"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
Visibility="Collapsed">
|
||||
<Button.Content>
|
||||
<FontIcon x:Name="QuickFixIcon"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Glyph="" />
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout x:Name="QuickFixMenu" />
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Canvas>
|
||||
|
||||
<Canvas x:Name="SelectionCanvas"
|
||||
@@ -1353,6 +1375,18 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="QuickFixButtonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Collapsed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="QuickFixButton.Width" Value="{x:Bind CalculateQuickFixButtonCollapsedWidth}" />
|
||||
<Setter Target="QuickFixButton.Background" Value="{ThemeResource SystemAccentColor}" />
|
||||
<Setter Target="QuickFixIcon.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -171,6 +171,13 @@
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\Microsoft.Management.Deployment.winmd">
|
||||
<IsWinMDFile>true</IsWinMDFile>
|
||||
<Private>false</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<!-- ====================== Compiler & Linker Flags ===================== -->
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
|
||||
@@ -699,34 +699,44 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s
|
||||
vkey = _VirtualKeyFromCharacter(ch);
|
||||
}
|
||||
|
||||
// GH#1527: When the user has auto mark prompts enabled, we're going to try
|
||||
// and heuristically detect if this was the line the prompt was on.
|
||||
// * If the key was an Enter keypress (Terminal.app also marks ^C keypresses
|
||||
// as prompts. That's omitted for now.)
|
||||
// * AND we're not in the alt buffer
|
||||
//
|
||||
// Then treat this line like it's a prompt mark.
|
||||
if (_autoMarkPrompts && vkey == VK_RETURN && !_inAltBuffer())
|
||||
if (vkey == VK_RETURN && !_inAltBuffer())
|
||||
{
|
||||
// We need to be a little tricky here, to try and support folks that are
|
||||
// auto-marking prompts, but don't necessarily have the rest of shell
|
||||
// integration enabled.
|
||||
//
|
||||
// We'll set the current attributes to Output, so that the output after
|
||||
// here is marked as the command output. But we also need to make sure
|
||||
// that a mark was started.
|
||||
// We can't just check if the current row has a mark - there could be a
|
||||
// multiline prompt.
|
||||
//
|
||||
// (TextBuffer::_createPromptMarkIfNeeded does that work for us)
|
||||
|
||||
const bool createdMark = _activeBuffer().StartOutput();
|
||||
if (createdMark)
|
||||
// Treat VK_RETURN as a new prompt,
|
||||
// so we should clear the quick fix UI if it's visible.
|
||||
if (_pfnClearQuickFix)
|
||||
{
|
||||
_activeBuffer().ManuallyMarkRowAsPrompt(_activeBuffer().GetCursor().GetPosition().y);
|
||||
_pfnClearQuickFix();
|
||||
}
|
||||
|
||||
// This changed the scrollbar marks - raise a notification to update them
|
||||
_NotifyScrollEvent();
|
||||
// GH#1527: When the user has auto mark prompts enabled, we're going to try
|
||||
// and heuristically detect if this was the line the prompt was on.
|
||||
// * If the key was an Enter keypress (Terminal.app also marks ^C keypresses
|
||||
// as prompts. That's omitted for now.)
|
||||
// * AND we're not in the alt buffer
|
||||
//
|
||||
// Then treat this line like it's a prompt mark.
|
||||
if (_autoMarkPrompts)
|
||||
{
|
||||
// We need to be a little tricky here, to try and support folks that are
|
||||
// auto-marking prompts, but don't necessarily have the rest of shell
|
||||
// integration enabled.
|
||||
//
|
||||
// We'll set the current attributes to Output, so that the output after
|
||||
// here is marked as the command output. But we also need to make sure
|
||||
// that a mark was started.
|
||||
// We can't just check if the current row has a mark - there could be a
|
||||
// multiline prompt.
|
||||
//
|
||||
// (TextBuffer::_createPromptMarkIfNeeded does that work for us)
|
||||
|
||||
const bool createdMark = _activeBuffer().StartOutput();
|
||||
if (createdMark)
|
||||
{
|
||||
_activeBuffer().ManuallyMarkRowAsPrompt(_activeBuffer().GetCursor().GetPosition().y);
|
||||
|
||||
// This changed the scrollbar marks - raise a notification to update them
|
||||
_NotifyScrollEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,7 +971,7 @@ Viewport Terminal::_GetMutableViewport() const noexcept
|
||||
// GH#3493: if we're in the alt buffer, then it's possible that the mutable
|
||||
// viewport's size hasn't been updated yet. In that case, use the
|
||||
// temporarily stashed _altBufferSize instead.
|
||||
return _inAltBuffer() ? Viewport::FromDimensions(_altBufferSize) :
|
||||
return _inAltBuffer() ? Viewport::FromDimensions({}, _altBufferSize) :
|
||||
_mutableViewport;
|
||||
}
|
||||
|
||||
@@ -1227,6 +1237,16 @@ void Microsoft::Terminal::Core::Terminal::CompletionsChangedCallback(std::functi
|
||||
_pfnCompletionsChanged.swap(pfn);
|
||||
}
|
||||
|
||||
void Microsoft::Terminal::Core::Terminal::SetSearchMissingCommandCallback(std::function<void(std::wstring_view)> pfn) noexcept
|
||||
{
|
||||
_pfnSearchMissingCommand.swap(pfn);
|
||||
}
|
||||
|
||||
void Microsoft::Terminal::Core::Terminal::SetClearQuickFixCallback(std::function<void()> pfn) noexcept
|
||||
{
|
||||
_pfnClearQuickFix.swap(pfn);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Stores the search highlighted regions in the terminal
|
||||
void Terminal::SetSearchHighlights(const std::vector<til::point_span>& highlights) noexcept
|
||||
|
||||
@@ -157,6 +157,8 @@ public:
|
||||
|
||||
void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override;
|
||||
|
||||
void SearchMissingCommand(const std::wstring_view command) override;
|
||||
|
||||
#pragma endregion
|
||||
|
||||
void ClearMark();
|
||||
@@ -229,6 +231,8 @@ public:
|
||||
void SetShowWindowCallback(std::function<void(bool)> pfn) noexcept;
|
||||
void SetPlayMidiNoteCallback(std::function<void(const int, const int, const std::chrono::microseconds)> pfn) noexcept;
|
||||
void CompletionsChangedCallback(std::function<void(std::wstring_view, unsigned int)> pfn) noexcept;
|
||||
void SetSearchMissingCommandCallback(std::function<void(std::wstring_view)> pfn) noexcept;
|
||||
void SetClearQuickFixCallback(std::function<void()> pfn) noexcept;
|
||||
void SetSearchHighlights(const std::vector<til::point_span>& highlights) noexcept;
|
||||
void SetSearchHighlightFocused(const size_t focusedIdx);
|
||||
|
||||
@@ -338,6 +342,8 @@ private:
|
||||
std::function<void(bool)> _pfnShowWindowChanged;
|
||||
std::function<void(const int, const int, const std::chrono::microseconds)> _pfnPlayMidiNote;
|
||||
std::function<void(std::wstring_view, unsigned int)> _pfnCompletionsChanged;
|
||||
std::function<void(std::wstring_view)> _pfnSearchMissingCommand;
|
||||
std::function<void()> _pfnClearQuickFix;
|
||||
|
||||
RenderSettings _renderSettings;
|
||||
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
|
||||
|
||||
@@ -333,6 +333,14 @@ void Terminal::InvokeCompletions(std::wstring_view menuJson, unsigned int replac
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::SearchMissingCommand(const std::wstring_view command)
|
||||
{
|
||||
if (_pfnSearchMissingCommand)
|
||||
{
|
||||
_pfnSearchMissingCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::NotifyBufferRotation(const int delta)
|
||||
{
|
||||
// Update our selection, so it doesn't move as the buffer is cycled
|
||||
|
||||
@@ -495,7 +495,7 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
|
||||
searchEnd = { bufferSize.RightInclusive(), searchStart.y - 1 };
|
||||
searchStart = { bufferSize.Left(), std::max(searchStart.y - viewportHeight, bufferSize.Top()) };
|
||||
}
|
||||
searchArea = Viewport::FromDimensions(searchStart, searchEnd.x + 1, searchEnd.y + 1);
|
||||
searchArea = Viewport::FromDimensions(searchStart, { searchEnd.x + 1, searchEnd.y + 1 });
|
||||
|
||||
const til::point bufferStart{ bufferSize.Origin() };
|
||||
const til::point bufferEnd{ bufferSize.RightInclusive(), ViewEndIndex() };
|
||||
@@ -516,7 +516,7 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
|
||||
searchEnd.y -= 1;
|
||||
searchStart.y = std::max(searchEnd.y - viewportHeight, bufferSize.Top());
|
||||
}
|
||||
searchArea = Viewport::FromDimensions(searchStart, searchEnd.x + 1, searchEnd.y + 1);
|
||||
searchArea = Viewport::FromDimensions(searchStart, { searchEnd.x + 1, searchEnd.y + 1 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -699,17 +699,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMicaAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& theme = _settingsSource.GlobalSettings().CurrentTheme();
|
||||
const auto& requestedTheme = _settingsSource.GlobalSettings().CurrentTheme().RequestedTheme();
|
||||
const bool hasThemeForSettings{ theme.Settings() != nullptr };
|
||||
const auto& appTheme = theme.RequestedTheme();
|
||||
const auto& requestedTheme = (hasThemeForSettings) ? theme.Settings().RequestedTheme() : appTheme;
|
||||
|
||||
RequestedTheme(requestedTheme);
|
||||
|
||||
const auto bgKey = (theme.Window() != nullptr && theme.Window().UseMica()) ?
|
||||
// Mica gets it's appearance from the app's theme, not necessarily the
|
||||
// Page's theme. In the case of dark app, light settings, mica will be a
|
||||
// dark color, and the text will also be dark, making the UI _very_ hard
|
||||
// to read. (and similarly in the inverse situation).
|
||||
//
|
||||
// To mitigate this, don't set the transparent background in the case
|
||||
// that our theme is different than the app's.
|
||||
const bool actuallyUseMica = isMicaAvailable && (appTheme == requestedTheme);
|
||||
|
||||
const auto bgKey = (theme.Window() != nullptr && theme.Window().UseMica()) && actuallyUseMica ?
|
||||
L"SettingsPageMicaBackground" :
|
||||
L"SettingsPageBackground";
|
||||
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.SoftwareRendering, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<local:SettingContainer x:Uid="Globals_TextMeasurement">
|
||||
<ComboBox AutomationProperties.AccessibilityView="Content"
|
||||
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.TextMeasurementList}"
|
||||
SelectedItem="{x:Bind ViewModel.CurrentTextMeasurement, Mode=TwoWay}"
|
||||
Style="{StaticResource ComboBoxSettingStyle}" />
|
||||
</local:SettingContainer>
|
||||
</StackPanel>
|
||||
</Page>
|
||||
|
||||
@@ -17,5 +17,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_settings{ std::move(settings) }
|
||||
{
|
||||
INITIALIZE_BINDABLE_ENUM_SETTING(GraphicsAPI, GraphicsAPI, winrt::Microsoft::Terminal::Control::GraphicsAPI, L"Globals_GraphicsAPI_", L"Text");
|
||||
INITIALIZE_BINDABLE_ENUM_SETTING(TextMeasurement, TextMeasurement, winrt::Microsoft::Terminal::Control::TextMeasurement, L"Globals_TextMeasurement_", L"Text");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
GETSET_BINDABLE_ENUM_SETTING(GraphicsAPI, winrt::Microsoft::Terminal::Control::GraphicsAPI, _settings.GlobalSettings().GraphicsAPI);
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), DisablePartialInvalidation);
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), SoftwareRendering);
|
||||
GETSET_BINDABLE_ENUM_SETTING(TextMeasurement, winrt::Microsoft::Terminal::Control::TextMeasurement, _settings.GlobalSettings().TextMeasurement);
|
||||
|
||||
private:
|
||||
Model::CascadiaSettings _settings{ nullptr };
|
||||
|
||||
@@ -15,5 +15,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> GraphicsAPIList { get; };
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, DisablePartialInvalidation);
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, SoftwareRendering);
|
||||
IInspectable CurrentTextMeasurement;
|
||||
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> TextMeasurementList { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +342,24 @@
|
||||
<value>When enabled, the terminal will use a software rasterizer (WARP). This setting should be left disabled under almost all circumstances.</value>
|
||||
<comment>{Locked="WARP"} WARP is the "Windows Advanced Rasterization Platform".</comment>
|
||||
</data>
|
||||
<data name="Globals_TextMeasurement.Header" xml:space="preserve">
|
||||
<value>Text measurement mode</value>
|
||||
<comment>This text is shown next to a list of choices.</comment>
|
||||
</data>
|
||||
<data name="Globals_TextMeasurement.HelpText" xml:space="preserve">
|
||||
<value>This changes the way incoming text is grouped into cells. The "Grapheme clusters" option is the most modern and Unicode-correct way to do so, while "wcswidth" is a common approach on UNIX, and "Windows Console" replicates the way it used to work on Windows. Changing this setting requires a restart of Windows Terminal and it only applies to applications launched from within it.</value>
|
||||
</data>
|
||||
<data name="Globals_TextMeasurement_Graphemes.Text" xml:space="preserve">
|
||||
<value>Grapheme clusters</value>
|
||||
<comment>The default choice between multiple graphics APIs.</comment>
|
||||
</data>
|
||||
<data name="Globals_TextMeasurement_Wcswidth.Text" xml:space="preserve">
|
||||
<value>wcswidth</value>
|
||||
<comment>{Locked="wcswidth"}</comment>
|
||||
</data>
|
||||
<data name="Globals_TextMeasurement_Console.Text" xml:space="preserve">
|
||||
<value>Windows Console</value>
|
||||
</data>
|
||||
<data name="Globals_InitialCols.Text" xml:space="preserve">
|
||||
<value>Columns</value>
|
||||
<comment>Header for a control to choose the number of columns in the terminal's text grid.</comment>
|
||||
|
||||
@@ -52,6 +52,7 @@ static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
|
||||
static constexpr std::string_view TabSearchKey{ "tabSearch" };
|
||||
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
|
||||
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
|
||||
static constexpr std::string_view SaveTaskKey{ "experimental.saveTask" };
|
||||
static constexpr std::string_view SuggestionsKey{ "showSuggestions" };
|
||||
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
|
||||
static constexpr std::string_view SetFocusModeKey{ "setFocusMode" };
|
||||
@@ -389,6 +390,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{ ShortcutAction::TabSearch, RS_(L"TabSearchCommandKey") },
|
||||
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
|
||||
{ ShortcutAction::ToggleCommandPalette, MustGenerate },
|
||||
{ ShortcutAction::SaveTask, MustGenerate },
|
||||
{ ShortcutAction::Suggestions, MustGenerate },
|
||||
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
|
||||
{ ShortcutAction::SetFocusMode, MustGenerate },
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "ScrollToMarkArgs.g.cpp"
|
||||
#include "AddMarkArgs.g.cpp"
|
||||
#include "FindMatchArgs.g.cpp"
|
||||
#include "SaveTaskArgs.g.cpp"
|
||||
#include "ToggleCommandPaletteArgs.g.cpp"
|
||||
#include "SuggestionsArgs.g.cpp"
|
||||
#include "NewWindowArgs.g.cpp"
|
||||
@@ -947,6 +948,29 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring SaveTaskArgs::GenerateName() const
|
||||
{
|
||||
if (Feature_SaveTask::IsEnabled())
|
||||
{
|
||||
std::wstringstream ss;
|
||||
|
||||
ss << RS_(L"SaveActionNamePrefix").c_str() << L" commandline: " << Commandline().c_str();
|
||||
|
||||
if (!Name().empty())
|
||||
{
|
||||
ss << L", name: " << Name().c_str();
|
||||
}
|
||||
|
||||
if (!KeyChord().empty())
|
||||
{
|
||||
ss << L", keyChord " << KeyChord().c_str();
|
||||
}
|
||||
|
||||
return winrt::hstring{ ss.str() };
|
||||
}
|
||||
return L"";
|
||||
}
|
||||
|
||||
static winrt::hstring _FormatColorString(const Control::SelectionColor& selectionColor)
|
||||
{
|
||||
if (!selectionColor)
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "ScrollToMarkArgs.g.h"
|
||||
#include "AddMarkArgs.g.h"
|
||||
#include "MoveTabArgs.g.h"
|
||||
#include "SaveTaskArgs.g.h"
|
||||
#include "ToggleCommandPaletteArgs.g.h"
|
||||
#include "SuggestionsArgs.g.h"
|
||||
#include "FindMatchArgs.g.h"
|
||||
@@ -215,6 +216,12 @@ protected: \
|
||||
#define TOGGLE_COMMAND_PALETTE_ARGS(X) \
|
||||
X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, 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 SUGGESTIONS_ARGS(X) \
|
||||
X(SuggestionsSource, Source, "source", false, SuggestionsSource::Tasks) \
|
||||
@@ -819,6 +826,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
ACTION_ARGS_STRUCT(ToggleCommandPaletteArgs, TOGGLE_COMMAND_PALETTE_ARGS);
|
||||
|
||||
ACTION_ARGS_STRUCT(SaveTaskArgs, SAVE_TASK_ARGS);
|
||||
|
||||
ACTION_ARGS_STRUCT(SuggestionsArgs, SUGGESTIONS_ARGS);
|
||||
|
||||
ACTION_ARGS_STRUCT(FindMatchArgs, FIND_MATCH_ARGS);
|
||||
@@ -928,6 +937,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
BASIC_FACTORY(SetTabColorArgs);
|
||||
BASIC_FACTORY(RenameTabArgs);
|
||||
BASIC_FACTORY(SwapPaneArgs);
|
||||
BASIC_FACTORY(SendInputArgs);
|
||||
BASIC_FACTORY(SplitPaneArgs);
|
||||
BASIC_FACTORY(SetFocusModeArgs);
|
||||
BASIC_FACTORY(SetFullScreenArgs);
|
||||
@@ -940,6 +950,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
BASIC_FACTORY(CloseTabArgs);
|
||||
BASIC_FACTORY(MoveTabArgs);
|
||||
BASIC_FACTORY(OpenSettingsArgs);
|
||||
BASIC_FACTORY(SaveTaskArgs);
|
||||
BASIC_FACTORY(FindMatchArgs);
|
||||
BASIC_FACTORY(NewWindowArgs);
|
||||
BASIC_FACTORY(FocusPaneArgs);
|
||||
|
||||
@@ -119,6 +119,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Tasks = 0x1,
|
||||
CommandHistory = 0x2,
|
||||
DirectoryHistory = 0x4,
|
||||
QuickFixes = 0x8,
|
||||
All = 0xffffffff,
|
||||
};
|
||||
|
||||
@@ -225,6 +226,8 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
|
||||
[default_interface] runtimeclass SendInputArgs : IActionArgs
|
||||
{
|
||||
SendInputArgs(String input);
|
||||
|
||||
String Input { get; };
|
||||
};
|
||||
|
||||
@@ -354,6 +357,15 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
FindMatchDirection Direction { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass SaveTaskArgs : IActionArgs
|
||||
{
|
||||
SaveTaskArgs();
|
||||
SaveTaskArgs(String Name, String Commandline, String KeyChord);
|
||||
String Name;
|
||||
String Commandline;
|
||||
String KeyChord;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass NewWindowArgs : IActionArgs
|
||||
{
|
||||
NewWindowArgs(INewContentArgs contentArgs);
|
||||
|
||||
36
src/cascadia/TerminalSettingsModel/ActionEntry.cpp
Normal file
36
src/cascadia/TerminalSettingsModel/ActionEntry.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ActionEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include "ActionEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
static constexpr std::string_view ActionIdKey{ "actionId" };
|
||||
|
||||
ActionEntry::ActionEntry() noexcept :
|
||||
ActionEntryT<ActionEntry, NewTabMenuEntry>(NewTabMenuEntryType::Action)
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value ActionEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> ActionEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<ActionEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId);
|
||||
|
||||
return entry;
|
||||
}
|
||||
37
src/cascadia/TerminalSettingsModel/ActionEntry.h
Normal file
37
src/cascadia/TerminalSettingsModel/ActionEntry.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- ActionEntry.h
|
||||
|
||||
Abstract:
|
||||
- An action entry in the "new tab" dropdown menu
|
||||
|
||||
Author(s):
|
||||
- Pankaj Bhojwani - May 2024
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "ActionEntry.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct ActionEntry : ActionEntryT<ActionEntry, NewTabMenuEntry>
|
||||
{
|
||||
public:
|
||||
ActionEntry() noexcept;
|
||||
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, ActionId);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ActionEntry);
|
||||
}
|
||||
@@ -60,31 +60,84 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the Command in the current layer, if it's valid
|
||||
// - We internally store invalid commands as full commands.
|
||||
// This helper function returns nullptr when we get an invalid
|
||||
// command. This allows us to simply check for null when we
|
||||
// want a valid command.
|
||||
// - Detects if any of the user's actions are identical to the inbox actions,
|
||||
// and if so, deletes them and redirects their keybindings to the inbox actions
|
||||
// - We have to do this here instead of when loading since we don't actually have
|
||||
// any parents while loading the user settings, the parents are added after
|
||||
void ActionMap::_FinalizeInheritance()
|
||||
{
|
||||
// first, gather the inbox actions from the relevant parent
|
||||
std::unordered_map<InternalActionID, Model::Command> inboxActions;
|
||||
winrt::com_ptr<implementation::ActionMap> foundParent{ nullptr };
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
const auto parentMap = parent->_ActionMap;
|
||||
if (parentMap.begin() != parentMap.end() && parentMap.begin()->second.Origin() == OriginTag::InBox)
|
||||
{
|
||||
// only one parent contains all the inbox actions and that parent contains only inbox actions,
|
||||
// so if we found an inbox action we know this is the parent we are looking for
|
||||
foundParent = parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundParent)
|
||||
{
|
||||
for (const auto& [_, cmd] : foundParent->_ActionMap)
|
||||
{
|
||||
inboxActions.emplace(Hash(cmd.ActionAndArgs()), cmd);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<KeyChord, winrt::hstring, KeyChordHash, KeyChordEquality> keysToReassign;
|
||||
|
||||
// now, look through our _ActionMap for commands that
|
||||
// - had an ID generated for them
|
||||
// - do not have a name/icon path
|
||||
// - have a hash that matches a command in the inbox actions
|
||||
std::erase_if(_ActionMap, [&](const auto& pair) {
|
||||
const auto userCmdImpl{ get_self<Command>(pair.second) };
|
||||
if (userCmdImpl->IDWasGenerated() && !userCmdImpl->HasName() && userCmdImpl->IconPath().empty())
|
||||
{
|
||||
const auto userActionHash = Hash(userCmdImpl->ActionAndArgs());
|
||||
if (const auto inboxCmd = inboxActions.find(userActionHash); inboxCmd != inboxActions.end())
|
||||
{
|
||||
for (const auto& [key, cmdID] : _KeyMap)
|
||||
{
|
||||
// for any of our keys that point to the user action, point them to the inbox action instead
|
||||
if (cmdID == pair.first)
|
||||
{
|
||||
keysToReassign.insert_or_assign(key, inboxCmd->second.ID());
|
||||
}
|
||||
}
|
||||
|
||||
// remove this pair
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
for (const auto [key, cmdID] : keysToReassign)
|
||||
{
|
||||
_KeyMap.insert_or_assign(key, cmdID);
|
||||
}
|
||||
}
|
||||
|
||||
bool ActionMap::FixupsAppliedDuringLoad() const
|
||||
{
|
||||
return _fixupsAppliedDuringLoad;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the Command referred to be the given ID
|
||||
// - Will recurse through parents if we don't find it in this layer
|
||||
// Arguments:
|
||||
// - actionID: the internal ID associated with a Command
|
||||
// Return Value:
|
||||
// - If the command is valid, the command itself.
|
||||
// - If the command is explicitly unbound, nullptr.
|
||||
// - If the command cannot be found in this layer, nullopt.
|
||||
std::optional<Model::Command> ActionMap::_GetActionByID(const InternalActionID actionID) const
|
||||
// - The command if it exists in this layer, otherwise nullptr
|
||||
Model::Command ActionMap::_GetActionByID(const winrt::hstring& actionID) const
|
||||
{
|
||||
// Check the masking actions
|
||||
const auto maskingPair{ _MaskingActions.find(actionID) };
|
||||
if (maskingPair != _MaskingActions.end())
|
||||
{
|
||||
// ActionMap should never point to nullptr
|
||||
FAIL_FAST_IF_NULL(maskingPair->second);
|
||||
|
||||
// masking actions cannot contain nested or invalid commands,
|
||||
// so we can just return it directly.
|
||||
return maskingPair->second;
|
||||
}
|
||||
|
||||
// Check current layer
|
||||
const auto actionMapPair{ _ActionMap.find(actionID) };
|
||||
if (actionMapPair != _ActionMap.end())
|
||||
@@ -94,13 +147,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// ActionMap should never point to nullptr
|
||||
FAIL_FAST_IF_NULL(cmd);
|
||||
|
||||
return !cmd.HasNestedCommands() && cmd.ActionAndArgs().Action() == ShortcutAction::Invalid ?
|
||||
nullptr : // explicitly unbound
|
||||
cmd;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
if (const auto inheritedCmd = parent->_GetActionByID(actionID))
|
||||
{
|
||||
return inheritedCmd;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have an answer
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void RegisterShortcutAction(ShortcutAction shortcutAction, std::unordered_map<hstring, Model::ActionAndArgs>& list, std::unordered_set<InternalActionID>& visited)
|
||||
@@ -143,24 +202,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void ActionMap::_PopulateAvailableActionsWithStandardCommands(std::unordered_map<hstring, Model::ActionAndArgs>& availableActions, std::unordered_set<InternalActionID>& visitedActionIDs) const
|
||||
{
|
||||
// Update AvailableActions and visitedActionIDs with our current layer
|
||||
for (const auto& [actionID, cmd] : _ActionMap)
|
||||
for (const auto& [_, cmd] : _ActionMap)
|
||||
{
|
||||
if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid)
|
||||
// Only populate AvailableActions with actions that haven't been visited already.
|
||||
const auto actionID = Hash(cmd.ActionAndArgs());
|
||||
if (!visitedActionIDs.contains(actionID))
|
||||
{
|
||||
// Only populate AvailableActions with actions that haven't been visited already.
|
||||
if (visitedActionIDs.find(actionID) == visitedActionIDs.end())
|
||||
const auto name{ cmd.Name() };
|
||||
if (!name.empty())
|
||||
{
|
||||
const auto& name{ cmd.Name() };
|
||||
if (!name.empty())
|
||||
{
|
||||
// Update AvailableActions.
|
||||
const auto actionAndArgsImpl{ get_self<ActionAndArgs>(cmd.ActionAndArgs()) };
|
||||
availableActions.insert_or_assign(name, *actionAndArgsImpl->Copy());
|
||||
}
|
||||
|
||||
// Record that we already handled adding this action to the NameMap.
|
||||
visitedActionIDs.insert(actionID);
|
||||
// Update AvailableActions.
|
||||
const auto actionAndArgsImpl{ get_self<ActionAndArgs>(cmd.ActionAndArgs()) };
|
||||
availableActions.insert_or_assign(name, *actionAndArgsImpl->Copy());
|
||||
}
|
||||
|
||||
// Record that we already handled adding this action to the NameMap.
|
||||
visitedActionIDs.insert(actionID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +236,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
if (!_NameMapCache)
|
||||
{
|
||||
if (!_CumulativeActionMapCache)
|
||||
{
|
||||
_RefreshKeyBindingCaches();
|
||||
}
|
||||
// populate _NameMapCache
|
||||
std::unordered_map<hstring, Model::Command> nameMap{};
|
||||
_PopulateNameMapWithSpecialCommands(nameMap);
|
||||
@@ -231,59 +292,66 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// - Populates the provided nameMap with all of our actions and our parents actions
|
||||
// while omitting the actions that were already added before
|
||||
// Arguments:
|
||||
// - nameMap: the nameMap we're populating. This maps the name (hstring) of a command to the command itself.
|
||||
// There should only ever by one of each command (identified by the actionID) in the nameMap.
|
||||
// - nameMap: the nameMap we're populating, this maps the name (hstring) of a command to the command itself
|
||||
void ActionMap::_PopulateNameMapWithStandardCommands(std::unordered_map<hstring, Model::Command>& nameMap) const
|
||||
{
|
||||
std::unordered_set<InternalActionID> visitedActionIDs;
|
||||
for (const auto& cmd : _GetCumulativeActions())
|
||||
for (const auto& [_, cmd] : _CumulativeActionMapCache)
|
||||
{
|
||||
// only populate with valid commands
|
||||
if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid)
|
||||
const auto& name{ cmd.Name() };
|
||||
if (!name.empty())
|
||||
{
|
||||
// Only populate NameMap with actions that haven't been visited already.
|
||||
const auto actionID{ Hash(cmd.ActionAndArgs()) };
|
||||
if (visitedActionIDs.find(actionID) == visitedActionIDs.end())
|
||||
// there might be a collision here, where there could be 2 different commands with the same name
|
||||
// in this case, prioritize the user's action
|
||||
// TODO GH #17166: we should no longer use Command.Name to identify commands anywhere
|
||||
if (!nameMap.contains(name) || cmd.Origin() == OriginTag::User)
|
||||
{
|
||||
const auto& name{ cmd.Name() };
|
||||
if (!name.empty())
|
||||
{
|
||||
// Update NameMap.
|
||||
nameMap.insert_or_assign(name, cmd);
|
||||
}
|
||||
|
||||
// Record that we already handled adding this action to the NameMap.
|
||||
visitedActionIDs.emplace(actionID);
|
||||
// either a command with this name does not exist, or this is a user-defined command with a name
|
||||
// in either case, update the name map with the command (if this is a user-defined command with
|
||||
// the same name as an existing command, the existing one will get overwritten)
|
||||
nameMap.insert_or_assign(name, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Provides an accumulated list of actions that are exposed. The accumulated list includes actions added in this layer, followed by actions added by our parents.
|
||||
std::vector<Model::Command> ActionMap::_GetCumulativeActions() const noexcept
|
||||
// - Recursively populate keyBindingsMap with ours and our parents' key -> id pairs
|
||||
// - This is a bottom-up approach
|
||||
// - Keybindings of the parents are overridden by the children
|
||||
void ActionMap::_PopulateCumulativeKeyMap(std::unordered_map<Control::KeyChord, winrt::hstring, KeyChordHash, KeyChordEquality>& keyBindingsMap)
|
||||
{
|
||||
// First, add actions from our current layer
|
||||
std::vector<Model::Command> cumulativeActions;
|
||||
cumulativeActions.reserve(_MaskingActions.size() + _ActionMap.size());
|
||||
|
||||
// masking actions have priority. Actions here are constructed from consolidating an inherited action with changes we've found when populating this layer.
|
||||
std::transform(_MaskingActions.begin(), _MaskingActions.end(), std::back_inserter(cumulativeActions), [](std::pair<InternalActionID, Model::Command> actionPair) {
|
||||
return actionPair.second;
|
||||
});
|
||||
std::transform(_ActionMap.begin(), _ActionMap.end(), std::back_inserter(cumulativeActions), [](std::pair<InternalActionID, Model::Command> actionPair) {
|
||||
return actionPair.second;
|
||||
});
|
||||
|
||||
// Now, add the accumulated actions from our parents
|
||||
for (const auto& parent : _parents)
|
||||
for (const auto& [keys, cmdID] : _KeyMap)
|
||||
{
|
||||
const auto parentActions{ parent->_GetCumulativeActions() };
|
||||
cumulativeActions.reserve(cumulativeActions.size() + parentActions.size());
|
||||
cumulativeActions.insert(cumulativeActions.end(), parentActions.begin(), parentActions.end());
|
||||
if (!keyBindingsMap.contains(keys))
|
||||
{
|
||||
keyBindingsMap.emplace(keys, cmdID);
|
||||
}
|
||||
}
|
||||
|
||||
return cumulativeActions;
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
parent->_PopulateCumulativeKeyMap(keyBindingsMap);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Recursively populate actionMap with ours and our parents' id -> command pairs
|
||||
// - This is a bottom-up approach
|
||||
// - Actions of the parents are overridden by the children
|
||||
void ActionMap::_PopulateCumulativeActionMap(std::unordered_map<hstring, Model::Command>& actionMap)
|
||||
{
|
||||
for (const auto& [cmdID, cmd] : _ActionMap)
|
||||
{
|
||||
if (!actionMap.contains(cmdID))
|
||||
{
|
||||
actionMap.emplace(cmdID, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
parent->_PopulateCumulativeActionMap(actionMap);
|
||||
}
|
||||
}
|
||||
|
||||
IMapView<Control::KeyChord, Model::Command> ActionMap::GlobalHotkeys()
|
||||
@@ -297,81 +365,42 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
IMapView<Control::KeyChord, Model::Command> ActionMap::KeyBindings()
|
||||
{
|
||||
if (!_KeyBindingMapCache)
|
||||
if (!_ResolvedKeyActionMapCache)
|
||||
{
|
||||
_RefreshKeyBindingCaches();
|
||||
}
|
||||
return _KeyBindingMapCache.GetView();
|
||||
return _ResolvedKeyActionMapCache.GetView();
|
||||
}
|
||||
|
||||
void ActionMap::_RefreshKeyBindingCaches()
|
||||
{
|
||||
std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality> keyBindingsMap;
|
||||
std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality> globalHotkeys;
|
||||
std::unordered_set<Control::KeyChord, KeyChordHash, KeyChordEquality> unboundKeys;
|
||||
std::unordered_map<KeyChord, winrt::hstring, KeyChordHash, KeyChordEquality> accumulatedKeybindingsMap;
|
||||
std::unordered_map<winrt::hstring, Model::Command> accumulatedActionsMap;
|
||||
std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality> resolvedKeyActionMap;
|
||||
|
||||
_PopulateKeyBindingMapWithStandardCommands(keyBindingsMap, unboundKeys);
|
||||
_PopulateCumulativeKeyMap(accumulatedKeybindingsMap);
|
||||
_PopulateCumulativeActionMap(accumulatedActionsMap);
|
||||
|
||||
for (const auto& [keys, cmd] : keyBindingsMap)
|
||||
for (const auto& [keys, cmdID] : accumulatedKeybindingsMap)
|
||||
{
|
||||
// Only populate GlobalHotkeys with actions whose
|
||||
// ShortcutAction is GlobalSummon or QuakeMode
|
||||
if (cmd.ActionAndArgs().Action() == ShortcutAction::GlobalSummon || cmd.ActionAndArgs().Action() == ShortcutAction::QuakeMode)
|
||||
if (const auto idCmdPair = accumulatedActionsMap.find(cmdID); idCmdPair != accumulatedActionsMap.end())
|
||||
{
|
||||
globalHotkeys.emplace(keys, cmd);
|
||||
}
|
||||
}
|
||||
resolvedKeyActionMap.emplace(keys, idCmdPair->second);
|
||||
|
||||
_KeyBindingMapCache = single_threaded_map(std::move(keyBindingsMap));
|
||||
_GlobalHotkeysCache = single_threaded_map(std::move(globalHotkeys));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Populates the provided keyBindingsMap with all of our actions and our parents actions
|
||||
// while omitting the key bindings that were already added before.
|
||||
// - This needs to be a bottom up approach to ensure that we only add each key chord once.
|
||||
// Arguments:
|
||||
// - keyBindingsMap: the keyBindingsMap we're populating. This maps the key chord of a command to the command itself.
|
||||
// - unboundKeys: a set of keys that are explicitly unbound
|
||||
void ActionMap::_PopulateKeyBindingMapWithStandardCommands(std::unordered_map<Control::KeyChord, Model::Command, KeyChordHash, KeyChordEquality>& keyBindingsMap, std::unordered_set<Control::KeyChord, KeyChordHash, KeyChordEquality>& unboundKeys) const
|
||||
{
|
||||
// Update KeyBindingsMap with our current layer
|
||||
for (const auto& [keys, actionID] : _KeyMap)
|
||||
{
|
||||
// Get the action our KeyMap maps to.
|
||||
// This _cannot_ be nullopt because KeyMap can only map to
|
||||
// actions in this layer.
|
||||
// This _can_ be nullptr because nullptr means it was
|
||||
// explicitly unbound ( "command": "unbound", "keys": "ctrl+c" ).
|
||||
const auto cmd{ _GetActionByID(actionID).value() };
|
||||
if (cmd)
|
||||
{
|
||||
// iterate over all of the action's bound keys
|
||||
const auto cmdImpl{ get_self<Command>(cmd) };
|
||||
for (const auto& keys : cmdImpl->KeyMappings())
|
||||
// Only populate GlobalHotkeys with actions whose
|
||||
// ShortcutAction is GlobalSummon or QuakeMode
|
||||
if (idCmdPair->second.ActionAndArgs().Action() == ShortcutAction::GlobalSummon || idCmdPair->second.ActionAndArgs().Action() == ShortcutAction::QuakeMode)
|
||||
{
|
||||
// Only populate KeyBindingsMap with actions that...
|
||||
// (1) haven't been visited already
|
||||
// (2) aren't explicitly unbound
|
||||
if (keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end())
|
||||
{
|
||||
keyBindingsMap.emplace(keys, cmd);
|
||||
}
|
||||
globalHotkeys.emplace(keys, idCmdPair->second);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// record any keys that are explicitly unbound,
|
||||
// but don't add them to the list of key bindings
|
||||
unboundKeys.emplace(keys);
|
||||
}
|
||||
}
|
||||
|
||||
// Update keyBindingsMap and unboundKeys with our parents
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
parent->_PopulateKeyBindingMapWithStandardCommands(keyBindingsMap, unboundKeys);
|
||||
}
|
||||
_CumulativeKeyMapCache = single_threaded_map(std::move(accumulatedKeybindingsMap));
|
||||
_CumulativeActionMapCache = single_threaded_map(std::move(accumulatedActionsMap));
|
||||
_ResolvedKeyActionMapCache = single_threaded_map(std::move(resolvedKeyActionMap));
|
||||
_GlobalHotkeysCache = single_threaded_map(std::move(globalHotkeys));
|
||||
}
|
||||
|
||||
com_ptr<ActionMap> ActionMap::Copy() const
|
||||
@@ -388,13 +417,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
actionMap->_ActionMap.emplace(actionID, *winrt::get_self<Command>(cmd)->Copy());
|
||||
}
|
||||
|
||||
// ID --> Command
|
||||
actionMap->_MaskingActions.reserve(_MaskingActions.size());
|
||||
for (const auto& [actionID, cmd] : _MaskingActions)
|
||||
{
|
||||
actionMap->_MaskingActions.emplace(actionID, *winrt::get_self<Command>(cmd)->Copy());
|
||||
}
|
||||
|
||||
// Name --> Command
|
||||
actionMap->_NestedCommands.reserve(_NestedCommands.size());
|
||||
for (const auto& [name, cmd] : _NestedCommands)
|
||||
@@ -421,7 +443,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// - Adds a command to the ActionMap
|
||||
// Arguments:
|
||||
// - cmd: the command we're adding
|
||||
void ActionMap::AddAction(const Model::Command& cmd)
|
||||
void ActionMap::AddAction(const Model::Command& cmd, const Control::KeyChord& keys)
|
||||
{
|
||||
// _Never_ add null to the ActionMap
|
||||
if (!cmd)
|
||||
@@ -432,7 +454,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// invalidate caches
|
||||
_NameMapCache = nullptr;
|
||||
_GlobalHotkeysCache = nullptr;
|
||||
_KeyBindingMapCache = nullptr;
|
||||
_CumulativeKeyMapCache = nullptr;
|
||||
_CumulativeActionMapCache = nullptr;
|
||||
_ResolvedKeyActionMapCache = nullptr;
|
||||
|
||||
// Handle nested commands
|
||||
const auto cmdImpl{ get_self<Command>(cmd) };
|
||||
@@ -455,236 +479,89 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
|
||||
// General Case:
|
||||
// Add the new command to the KeyMap.
|
||||
// This map directs you to an entry in the ActionMap.
|
||||
// Add the new command to the _ActionMap
|
||||
// Add the new keybinding to the _KeyMap
|
||||
|
||||
// Removing Actions from the Command Palette:
|
||||
// cmd.Name and cmd.Action have a one-to-one relationship.
|
||||
// If cmd.Name is empty, we must retrieve the old name and remove it.
|
||||
|
||||
// Removing Key Bindings:
|
||||
// cmd.Keys and cmd.Action have a many-to-one relationship.
|
||||
// If cmd.Keys is empty, we don't care.
|
||||
// If action is "unbound"/"invalid", you're explicitly unbinding the provided cmd.keys.
|
||||
// NOTE: If we're unbinding a command from a different layer, we must use maskingActions
|
||||
// to keep track of what key mappings are still valid.
|
||||
|
||||
// _TryUpdateActionMap may update oldCmd and maskingCmd
|
||||
Model::Command oldCmd{ nullptr };
|
||||
Model::Command maskingCmd{ nullptr };
|
||||
_TryUpdateActionMap(cmd, oldCmd, maskingCmd);
|
||||
|
||||
_TryUpdateName(cmd, oldCmd, maskingCmd);
|
||||
_TryUpdateKeyChord(cmd, oldCmd, maskingCmd);
|
||||
_TryUpdateActionMap(cmd);
|
||||
_TryUpdateKeyChord(cmd, keys);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Try to add the new command to _ActionMap.
|
||||
// - If the command was added previously in this layer, populate oldCmd.
|
||||
// - If the command was added previously in another layer, populate maskingCmd.
|
||||
// - Try to add the new command to _ActionMap
|
||||
// Arguments:
|
||||
// - cmd: the action we're trying to register
|
||||
// - oldCmd: the action found in _ActionMap, if one already exists
|
||||
// - maskingAction: the action found in a parent layer, if one already exists
|
||||
void ActionMap::_TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& maskingCmd)
|
||||
void ActionMap::_TryUpdateActionMap(const Model::Command& cmd)
|
||||
{
|
||||
// Example:
|
||||
// { "command": "copy", "keys": "ctrl+c" } --> add the action in for the first time
|
||||
// { "command": "copy", "keys": "ctrl+shift+c" } --> update oldCmd
|
||||
const auto actionID{ Hash(cmd.ActionAndArgs()) };
|
||||
const auto& actionPair{ _ActionMap.find(actionID) };
|
||||
if (actionPair == _ActionMap.end())
|
||||
// if the shortcut action is invalid, then this is for unbinding and _TryUpdateKeyChord will handle that
|
||||
if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid)
|
||||
{
|
||||
// add this action in for the first time
|
||||
_ActionMap.emplace(actionID, cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're adding an action that already exists in our layer.
|
||||
// Record it so that we update it with any new information.
|
||||
oldCmd = actionPair->second;
|
||||
}
|
||||
|
||||
// Masking Actions
|
||||
//
|
||||
// Example:
|
||||
// parent: { "command": "copy", "keys": "ctrl+c" } --> add the action to parent._ActionMap
|
||||
// current: { "command": "copy", "keys": "ctrl+shift+c" } --> look through parents for the "ctrl+c" binding, add it to _MaskingActions
|
||||
// { "command": "copy", "keys": "ctrl+ins" } --> this should already be in _MaskingActions
|
||||
|
||||
// Now check if this action was introduced in another layer.
|
||||
const auto& maskingActionPair{ _MaskingActions.find(actionID) };
|
||||
if (maskingActionPair == _MaskingActions.end())
|
||||
{
|
||||
// Check if we need to add this to our list of masking commands.
|
||||
for (const auto& parent : _parents)
|
||||
const auto cmdImpl{ get_self<implementation::Command>(cmd) };
|
||||
if (cmd.Origin() == OriginTag::User && cmd.ID().empty())
|
||||
{
|
||||
// NOTE: This only checks the layer above us, but that's ok.
|
||||
// If we had to find one from a layer above that, parent->_MaskingActions
|
||||
// would have found it, so we inherit it for free!
|
||||
const auto& inheritedCmd{ parent->_GetActionByID(actionID) };
|
||||
if (inheritedCmd && *inheritedCmd)
|
||||
// the user did not define an ID for their non-nested, non-iterable, valid command - generate one for them
|
||||
cmdImpl->GenerateID();
|
||||
}
|
||||
|
||||
// only add to the _ActionMap if there is an ID
|
||||
if (auto cmdID = cmd.ID(); !cmdID.empty())
|
||||
{
|
||||
// in the legacy scenario, a user might have several of the same action but only one of them has defined an icon or a name
|
||||
// eg. { "command": "paste", "name": "myPaste", "keys":"ctrl+a" }
|
||||
// { "command": "paste", "keys": "ctrl+b" }
|
||||
// once they port over to the new implementation, we will reduce it to just one Command object with a generated ID
|
||||
// but several key binding entries, like so
|
||||
// { "command": "newTab", "id": "User.paste" } -> in the actions map
|
||||
// { "keys": "ctrl+a", "id": "User.paste" } -> in the keybindings map
|
||||
// { "keys": "ctrl+b", "id": "User.paste" } -> in the keybindings map
|
||||
// however, we have to make sure that we preserve the icon/name that might have been there in one of the command objects
|
||||
// to do that, we check if this command we're adding had an ID that was generated
|
||||
// if so, we check if there already exists a command with that generated ID, and if there is we port over any name/icon there might be
|
||||
// (this may cause us to overwrite in scenarios where the user has an existing command that has the same generated ID but
|
||||
// performs a different action or has different args, but that falls under "play stupid games")
|
||||
if (cmdImpl->IDWasGenerated())
|
||||
{
|
||||
const auto& inheritedCmdImpl{ get_self<Command>(*inheritedCmd) };
|
||||
maskingCmd = *inheritedCmdImpl->Copy();
|
||||
_MaskingActions.emplace(actionID, maskingCmd);
|
||||
if (const auto foundCmd{ _GetActionByID(cmdID) })
|
||||
{
|
||||
const auto foundCmdImpl{ get_self<implementation::Command>(foundCmd) };
|
||||
if (foundCmdImpl->HasName() && !cmdImpl->HasName())
|
||||
{
|
||||
cmdImpl->Name(foundCmdImpl->Name());
|
||||
}
|
||||
if (!foundCmdImpl->IconPath().empty() && cmdImpl->IconPath().empty())
|
||||
{
|
||||
cmdImpl->IconPath(foundCmdImpl->IconPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ActionMap.insert_or_assign(cmdID, cmd);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is an action that we already have a mutable "masking" record for.
|
||||
// Record it so that we update it with any new information.
|
||||
maskingCmd = maskingActionPair->second;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update our internal state with the name of the newly registered action
|
||||
// Arguments:
|
||||
// - cmd: the action we're trying to register
|
||||
// - oldCmd: the action that already exists in our internal state. May be null.
|
||||
// - maskingCmd: the masking action that already exists in our internal state. May be null.
|
||||
void ActionMap::_TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& maskingCmd)
|
||||
{
|
||||
// Example:
|
||||
// { "name": "foo", "command": "copy" } --> we are setting a name, update oldCmd and maskingCmd
|
||||
// { "command": "copy" } --> no change to name, exit early
|
||||
const auto cmdImpl{ get_self<Command>(cmd) };
|
||||
if (!cmdImpl->HasName())
|
||||
{
|
||||
// the user is not trying to update the name.
|
||||
return;
|
||||
}
|
||||
|
||||
// Update oldCmd:
|
||||
// If we have a Command in our _ActionMap that we're trying to update,
|
||||
// update it.
|
||||
const auto newName{ cmd.Name() };
|
||||
if (oldCmd)
|
||||
{
|
||||
// This command has a name, check if it's new.
|
||||
if (newName != oldCmd.Name())
|
||||
{
|
||||
// The new name differs from the old name,
|
||||
// update our name.
|
||||
auto oldCmdImpl{ get_self<Command>(oldCmd) };
|
||||
oldCmdImpl->Name(newName);
|
||||
}
|
||||
}
|
||||
|
||||
// Update maskingCmd:
|
||||
// We have a Command that is masking one from a parent layer.
|
||||
// We need to ensure that this has the correct name. That way,
|
||||
// we can return an accumulated view of a Command at this layer.
|
||||
// This differs from oldCmd which is mainly used for serialization
|
||||
// by recording the delta of the Command in this layer.
|
||||
if (maskingCmd)
|
||||
{
|
||||
// This command has a name, check if it's new.
|
||||
if (newName != maskingCmd.Name())
|
||||
{
|
||||
// The new name differs from the old name,
|
||||
// update our name.
|
||||
auto maskingCmdImpl{ get_self<Command>(maskingCmd) };
|
||||
maskingCmdImpl->Name(newName);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a collision with NestedCommands
|
||||
_NestedCommands.erase(newName);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update our internal state with the key chord of the newly registered action
|
||||
// Arguments:
|
||||
// - cmd: the action we're trying to register
|
||||
// - oldCmd: the action that already exists in our internal state. May be null.
|
||||
// - maskingCmd: the masking action that already exists in our internal state. May be null.
|
||||
void ActionMap::_TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& maskingCmd)
|
||||
void ActionMap::_TryUpdateKeyChord(const Model::Command& cmd, const Control::KeyChord& keys)
|
||||
{
|
||||
// Example:
|
||||
// { "command": "copy", "keys": "ctrl+c" } --> we are registering a new key chord, update oldCmd and maskingCmd
|
||||
// Example (this is a legacy case, where the keys are provided in the same block as the command):
|
||||
// { "command": "copy", "keys": "ctrl+c" } --> we are registering a new key chord
|
||||
// { "name": "foo", "command": "copy" } --> no change to keys, exit early
|
||||
const auto keys{ cmd.Keys() };
|
||||
if (!keys)
|
||||
{
|
||||
// the user is not trying to update the keys.
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle collisions
|
||||
const auto oldKeyPair{ _KeyMap.find(keys) };
|
||||
if (oldKeyPair != _KeyMap.end())
|
||||
{
|
||||
// Collision: The key chord was already in use.
|
||||
//
|
||||
// Example:
|
||||
// { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" (different branch)
|
||||
// { "command": "paste", "keys": "ctrl+c" } --> Collision! (this branch)
|
||||
//
|
||||
// Remove the old one. (unbind "copy" in the example above)
|
||||
const auto actionPair{ _ActionMap.find(oldKeyPair->second) };
|
||||
const auto conflictingCmd{ actionPair->second };
|
||||
const auto conflictingCmdImpl{ get_self<implementation::Command>(conflictingCmd) };
|
||||
conflictingCmdImpl->EraseKey(keys);
|
||||
}
|
||||
else if (const auto& conflictingCmd{ GetActionByKeyChord(keys) })
|
||||
{
|
||||
// Collision with ancestor: The key chord was already in use, but by an action in another layer
|
||||
//
|
||||
// Example:
|
||||
// parent: { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" (different branch)
|
||||
// current: { "command": "paste", "keys": "ctrl+c" } --> Collision with ancestor! (this branch, sub-branch 1)
|
||||
// { "command": "unbound", "keys": "ctrl+c" } --> Collision with masking action! (this branch, sub-branch 2)
|
||||
const auto conflictingActionID{ Hash(conflictingCmd.ActionAndArgs()) };
|
||||
const auto maskingCmdPair{ _MaskingActions.find(conflictingActionID) };
|
||||
if (maskingCmdPair == _MaskingActions.end())
|
||||
{
|
||||
// This is the first time we're colliding with an action from a different layer,
|
||||
// so let's add this action to _MaskingActions and update it appropriately.
|
||||
// Create a copy of the conflicting action,
|
||||
// and erase the conflicting key chord from the copy.
|
||||
const auto conflictingCmdImpl{ get_self<implementation::Command>(conflictingCmd) };
|
||||
const auto conflictingCmdCopy{ conflictingCmdImpl->Copy() };
|
||||
conflictingCmdCopy->EraseKey(keys);
|
||||
_MaskingActions.emplace(conflictingActionID, *conflictingCmdCopy);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've collided with this action before. Let's resolve a collision with a masking action.
|
||||
const auto maskingCmdImpl{ get_self<implementation::Command>(maskingCmdPair->second) };
|
||||
maskingCmdImpl->EraseKey(keys);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign the new action in the _KeyMap.
|
||||
const auto actionID{ Hash(cmd.ActionAndArgs()) };
|
||||
_KeyMap.insert_or_assign(keys, actionID);
|
||||
|
||||
// Additive operation:
|
||||
// Register the new key chord with oldCmd (an existing _ActionMap entry)
|
||||
// Example:
|
||||
// { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" (section above)
|
||||
// { "command": "copy", "keys": "ctrl+shift+c" } --> also register "ctrl+shift+c" to the same Command (oldCmd)
|
||||
if (oldCmd)
|
||||
{
|
||||
// Update inner Command with new key chord
|
||||
auto oldCmdImpl{ get_self<Command>(oldCmd) };
|
||||
oldCmdImpl->RegisterKey(keys);
|
||||
}
|
||||
|
||||
// Additive operation:
|
||||
// Register the new key chord with maskingCmd (an existing _maskingAction entry)
|
||||
// Example:
|
||||
// parent: { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" to parent._ActionMap (different branch in a different layer)
|
||||
// current: { "command": "copy", "keys": "ctrl+shift+c" } --> also register "ctrl+shift+c" to the same Command (maskingCmd)
|
||||
if (maskingCmd)
|
||||
{
|
||||
// Update inner Command with new key chord
|
||||
auto maskingCmdImpl{ get_self<Command>(maskingCmd) };
|
||||
maskingCmdImpl->RegisterKey(keys);
|
||||
}
|
||||
// Assign the new action in the _KeyMap
|
||||
// However, there's a strange edge case here - since we're parsing a legacy or modern block,
|
||||
// the user might have { "command": null, "id": "someID", "keys": "ctrl+c" }
|
||||
// i.e. they provided an ID for a null command (which they really shouldn't, there's no purpose)
|
||||
// in this case, we do _not_ want to use the id they provided, we want to use an empty id
|
||||
// (empty id in the _KeyMap indicates the keychord was explicitly unbound)
|
||||
const auto action = cmd.ActionAndArgs().Action();
|
||||
const auto id = action == ShortcutAction::Invalid ? hstring{} : cmd.ID();
|
||||
_KeyMap.insert_or_assign(keys, id);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -714,6 +591,41 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return _GetActionByKeyChordInternal(keys).value_or(nullptr);
|
||||
}
|
||||
|
||||
Model::Command ActionMap::GetActionById(const winrt::hstring& cmdID) const
|
||||
{
|
||||
return _GetActionByID(cmdID);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the assigned command ID with the given key chord.
|
||||
// - Can return nullopt to differentiate explicit unbinding vs lack of binding.
|
||||
// Arguments:
|
||||
// - keys: the key chord of the command to search for
|
||||
// Return Value:
|
||||
// - the command ID with the given key chord
|
||||
// - an empty string if the key chord is explicitly unbound
|
||||
// - nullopt if it is not bound
|
||||
std::optional<winrt::hstring> ActionMap::_GetActionIdByKeyChordInternal(const Control::KeyChord& keys) const
|
||||
{
|
||||
if (const auto keyIDPair = _KeyMap.find(keys); keyIDPair != _KeyMap.end())
|
||||
{
|
||||
// the keychord is defined in this layer, return the ID
|
||||
return keyIDPair->second;
|
||||
}
|
||||
|
||||
// search through our parents
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
if (const auto foundCmdID = parent->_GetActionIdByKeyChordInternal(keys))
|
||||
{
|
||||
return foundCmdID;
|
||||
}
|
||||
}
|
||||
|
||||
// we did not find the keychord anywhere, it's not bound and not explicitly unbound either
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the assigned command with the given key chord.
|
||||
// - Can return nullopt to differentiate explicit unbinding vs lack of binding.
|
||||
@@ -722,73 +634,49 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// Return Value:
|
||||
// - the command with the given key chord
|
||||
// - nullptr if the key chord is explicitly unbound
|
||||
// - nullopt if it was not bound in this layer
|
||||
// - nullopt if it is not bound
|
||||
std::optional<Model::Command> ActionMap::_GetActionByKeyChordInternal(const Control::KeyChord& keys) const
|
||||
{
|
||||
// Check the current layer
|
||||
if (const auto actionIDPair = _KeyMap.find(keys); actionIDPair != _KeyMap.end())
|
||||
if (const auto actionIDOptional = _GetActionIdByKeyChordInternal(keys))
|
||||
{
|
||||
// the command was explicitly bound,
|
||||
// return what we found (invalid commands exposed as nullptr)
|
||||
return _GetActionByID(actionIDPair->second);
|
||||
}
|
||||
|
||||
// the command was not bound in this layer,
|
||||
// ask my parents
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal(keys) };
|
||||
if (inheritedCmd)
|
||||
if (!actionIDOptional->empty())
|
||||
{
|
||||
return *inheritedCmd;
|
||||
// there is an ID associated with these keys, find the command
|
||||
if (const auto foundCmd = _GetActionByID(*actionIDOptional))
|
||||
{
|
||||
return foundCmd;
|
||||
}
|
||||
}
|
||||
// the ID is an empty string, these keys are explicitly unbound
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This action is not explicitly bound
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the key chord for the provided action
|
||||
// Arguments:
|
||||
// - action: the shortcut action (an action type) we're looking for
|
||||
// - cmdID: the ID of the command we're looking for
|
||||
// Return Value:
|
||||
// - the key chord that executes the given action
|
||||
// - nullptr if the action is not bound to a key chord
|
||||
Control::KeyChord ActionMap::GetKeyBindingForAction(const ShortcutAction& action) const
|
||||
Control::KeyChord ActionMap::GetKeyBindingForAction(const winrt::hstring& cmdID)
|
||||
{
|
||||
return GetKeyBindingForAction(action, nullptr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the key chord for the provided action
|
||||
// Arguments:
|
||||
// - action: the shortcut action (an action type) we're looking for
|
||||
// - myArgs: the action args for the action we're looking for
|
||||
// Return Value:
|
||||
// - the key chord that executes the given action
|
||||
// - nullptr if the action is not bound to a key chord
|
||||
Control::KeyChord ActionMap::GetKeyBindingForAction(const ShortcutAction& myAction, const IActionArgs& myArgs) const
|
||||
{
|
||||
if (myAction == ShortcutAction::Invalid)
|
||||
if (!_ResolvedKeyActionMapCache)
|
||||
{
|
||||
return nullptr;
|
||||
_RefreshKeyBindingCaches();
|
||||
}
|
||||
|
||||
// Check our internal state.
|
||||
const auto actionAndArgs = winrt::make<ActionAndArgs>(myAction, myArgs);
|
||||
const auto hash{ Hash(actionAndArgs) };
|
||||
if (const auto& cmd{ _GetActionByID(hash) })
|
||||
// I dislike that we have to do an O(n) lookup every time we want to get the keybinding for an action -
|
||||
// an alternative is having the key->action map be a bi-map (would require a dependency), or store another map that is just
|
||||
// the reverse direction (action->key) which would be mean storing the same data twice but getting faster lookup
|
||||
for (const auto [key, action] : _ResolvedKeyActionMapCache)
|
||||
{
|
||||
return cmd->Keys();
|
||||
}
|
||||
|
||||
// Check our parents
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
if (const auto& keys{ parent->GetKeyBindingForAction(myAction, myArgs) })
|
||||
if (action.ID() == cmdID)
|
||||
{
|
||||
return keys;
|
||||
// if there are multiple keys bound to this action, we will just return the first one we find
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,25 +684,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ActionMap::GenerateIDsForActions()
|
||||
{
|
||||
bool fixedUp{ false };
|
||||
for (auto actionPair : _ActionMap)
|
||||
{
|
||||
auto cmdImpl{ winrt::get_self<Command>(actionPair.second) };
|
||||
|
||||
// Note: this function should ONLY be called for the action map in the user's settings file
|
||||
// this debug assert should verify that for debug builds
|
||||
assert(cmdImpl->Origin() == OriginTag::User);
|
||||
|
||||
if (cmdImpl->ID().empty())
|
||||
{
|
||||
fixedUp = cmdImpl->GenerateID() || fixedUp;
|
||||
}
|
||||
}
|
||||
return fixedUp;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Rebinds a key binding to a new key chord
|
||||
// Arguments:
|
||||
@@ -824,24 +693,26 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// - true, if successful. False, otherwise.
|
||||
bool ActionMap::RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys)
|
||||
{
|
||||
const auto& cmd{ GetActionByKeyChord(oldKeys) };
|
||||
const auto cmd{ GetActionByKeyChord(oldKeys) };
|
||||
if (!cmd)
|
||||
{
|
||||
// oldKeys must be bound. Otherwise, we don't know what action to bind.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newKeys)
|
||||
if (auto oldKeyPair = _KeyMap.find(oldKeys); oldKeyPair != _KeyMap.end())
|
||||
{
|
||||
// Bind newKeys
|
||||
const auto newCmd{ make_self<Command>() };
|
||||
newCmd->ActionAndArgs(cmd.ActionAndArgs());
|
||||
newCmd->RegisterKey(newKeys);
|
||||
AddAction(*newCmd);
|
||||
// oldKeys is bound in our layer, replace it with newKeys
|
||||
_KeyMap.insert_or_assign(newKeys, cmd.ID());
|
||||
_KeyMap.erase(oldKeyPair);
|
||||
}
|
||||
else
|
||||
{
|
||||
// oldKeys is bound in some other layer, set newKeys to cmd in this layer, and oldKeys to unbound in this layer
|
||||
_KeyMap.insert_or_assign(newKeys, cmd.ID());
|
||||
_KeyMap.insert_or_assign(oldKeys, L"");
|
||||
}
|
||||
|
||||
// unbind oldKeys
|
||||
DeleteKeyBinding(oldKeys);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -853,12 +724,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// - <none>
|
||||
void ActionMap::DeleteKeyBinding(const KeyChord& keys)
|
||||
{
|
||||
// create an "unbound" command
|
||||
// { "command": "unbound", "keys": <keys> }
|
||||
const auto cmd{ make_self<Command>() };
|
||||
cmd->ActionAndArgs(make<ActionAndArgs>());
|
||||
cmd->RegisterKey(keys);
|
||||
AddAction(*cmd);
|
||||
if (auto keyPair = _KeyMap.find(keys); keyPair != _KeyMap.end())
|
||||
{
|
||||
// this keychord is bound in our layer, delete it
|
||||
_KeyMap.erase(keyPair);
|
||||
}
|
||||
|
||||
// either the keychord was never in this layer or we just deleted it above,
|
||||
// if GetActionByKeyChord still returns a command that means the keychord is bound in another layer
|
||||
if (GetActionByKeyChord(keys))
|
||||
{
|
||||
// set to unbound in this layer
|
||||
_KeyMap.emplace(keys, L"");
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -872,32 +750,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void ActionMap::RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action)
|
||||
{
|
||||
auto cmd{ make_self<Command>() };
|
||||
cmd->RegisterKey(keys);
|
||||
cmd->ActionAndArgs(action);
|
||||
AddAction(*cmd);
|
||||
cmd->GenerateID();
|
||||
AddAction(*cmd, keys);
|
||||
}
|
||||
|
||||
void ActionMap::_recursiveUpdateCommandKeybindingLabels()
|
||||
void ActionMap::AddSendInputAction(winrt::hstring name, winrt::hstring input, const Control::KeyChord keys)
|
||||
{
|
||||
const auto& commands{ _ExpandedCommandsCache };
|
||||
|
||||
for (const auto& command : commands)
|
||||
auto newAction = winrt::make<ActionAndArgs>();
|
||||
newAction.Action(ShortcutAction::SendInput);
|
||||
auto sendInputArgs = winrt::make<SendInputArgs>(input);
|
||||
newAction.Args(sendInputArgs);
|
||||
auto cmd{ make_self<Command>() };
|
||||
cmd->ActionAndArgs(newAction);
|
||||
if (!name.empty())
|
||||
{
|
||||
if (command.HasNestedCommands())
|
||||
{
|
||||
_recursiveUpdateCommandKeybindingLabels();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's a keybinding that's bound to exactly this command,
|
||||
// then get the keychord and display it as a
|
||||
// part of the command in the UI.
|
||||
// We specifically need to do this for nested commands.
|
||||
const auto keyChord{ GetKeyBindingForAction(command.ActionAndArgs().Action(),
|
||||
command.ActionAndArgs().Args()) };
|
||||
command.RegisterKey(keyChord);
|
||||
}
|
||||
cmd->Name(name);
|
||||
}
|
||||
cmd->GenerateID();
|
||||
AddAction(*cmd, keys);
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
@@ -965,6 +836,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
cmdImpl.copy_from(winrt::get_self<implementation::Command>(command));
|
||||
|
||||
const auto inArgs{ command.ActionAndArgs().Args().try_as<Model::SendInputArgs>() };
|
||||
|
||||
const auto inputString{ inArgs ? inArgs.Input() : L"" };
|
||||
auto args = winrt::make_self<SendInputArgs>(
|
||||
winrt::hstring{ fmt::format(FMT_COMPILE(L"{:\x7f^{}}{}"),
|
||||
|
||||
@@ -49,6 +49,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
struct ActionMap : ActionMapT<ActionMap>, IInheritable<ActionMap>
|
||||
{
|
||||
void _FinalizeInheritance() override;
|
||||
|
||||
// views
|
||||
Windows::Foundation::Collections::IMapView<hstring, Model::ActionAndArgs> AvailableActions();
|
||||
Windows::Foundation::Collections::IMapView<hstring, Model::Command> NameMap();
|
||||
@@ -58,23 +60,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
// queries
|
||||
Model::Command GetActionByKeyChord(const Control::KeyChord& keys) const;
|
||||
Model::Command GetActionById(const winrt::hstring& cmdID) const;
|
||||
bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const;
|
||||
Control::KeyChord GetKeyBindingForAction(const ShortcutAction& action) const;
|
||||
Control::KeyChord GetKeyBindingForAction(const ShortcutAction& action, const IActionArgs& actionArgs) const;
|
||||
Control::KeyChord GetKeyBindingForAction(const winrt::hstring& cmdID);
|
||||
|
||||
// population
|
||||
void AddAction(const Model::Command& cmd);
|
||||
void AddAction(const Model::Command& cmd, const Control::KeyChord& keys);
|
||||
|
||||
// JSON
|
||||
static com_ptr<ActionMap> FromJson(const Json::Value& json, const OriginTag origin = OriginTag::None);
|
||||
std::vector<SettingsLoadWarnings> LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true);
|
||||
Json::Value ToJson() const;
|
||||
Json::Value KeyBindingsToJson() const;
|
||||
bool FixupsAppliedDuringLoad() const;
|
||||
|
||||
// modification
|
||||
bool GenerateIDsForActions();
|
||||
bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys);
|
||||
void DeleteKeyBinding(const Control::KeyChord& keys);
|
||||
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
|
||||
void AddSendInputAction(winrt::hstring name, winrt::hstring input, const Control::KeyChord keys);
|
||||
|
||||
Windows::Foundation::Collections::IVector<Model::Command> ExpandedCommands();
|
||||
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
|
||||
@@ -83,46 +87,47 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::Command> FilterToSendInput(winrt::hstring currentCommandline);
|
||||
|
||||
private:
|
||||
std::optional<Model::Command> _GetActionByID(const InternalActionID actionID) const;
|
||||
Model::Command _GetActionByID(const winrt::hstring& actionID) const;
|
||||
std::optional<winrt::hstring> _GetActionIdByKeyChordInternal(const Control::KeyChord& keys) const;
|
||||
std::optional<Model::Command> _GetActionByKeyChordInternal(const Control::KeyChord& keys) const;
|
||||
|
||||
void _RefreshKeyBindingCaches();
|
||||
void _PopulateAvailableActionsWithStandardCommands(std::unordered_map<hstring, Model::ActionAndArgs>& availableActions, std::unordered_set<InternalActionID>& visitedActionIDs) const;
|
||||
void _PopulateNameMapWithSpecialCommands(std::unordered_map<hstring, Model::Command>& nameMap) const;
|
||||
void _PopulateNameMapWithStandardCommands(std::unordered_map<hstring, Model::Command>& nameMap) const;
|
||||
void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map<Control::KeyChord, Model::Command, KeyChordHash, KeyChordEquality>& keyBindingsMap, std::unordered_set<Control::KeyChord, KeyChordHash, KeyChordEquality>& unboundKeys) const;
|
||||
std::vector<Model::Command> _GetCumulativeActions() const noexcept;
|
||||
|
||||
void _TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& consolidatedCmd);
|
||||
void _TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd);
|
||||
void _TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd);
|
||||
void _PopulateCumulativeKeyMap(std::unordered_map<Control::KeyChord, winrt::hstring, KeyChordHash, KeyChordEquality>& keyBindingsMap);
|
||||
void _PopulateCumulativeActionMap(std::unordered_map<hstring, Model::Command>& actionMap);
|
||||
|
||||
void _recursiveUpdateCommandKeybindingLabels();
|
||||
void _TryUpdateActionMap(const Model::Command& cmd);
|
||||
void _TryUpdateKeyChord(const Model::Command& cmd, const Control::KeyChord& keys);
|
||||
|
||||
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 };
|
||||
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _KeyBindingMapCache{ nullptr };
|
||||
|
||||
Windows::Foundation::Collections::IVector<Model::Command> _ExpandedCommandsCache{ nullptr };
|
||||
|
||||
std::unordered_map<winrt::hstring, Model::Command> _NestedCommands;
|
||||
std::vector<Model::Command> _IterableCommands;
|
||||
std::unordered_map<Control::KeyChord, InternalActionID, KeyChordHash, KeyChordEquality> _KeyMap;
|
||||
std::unordered_map<InternalActionID, Model::Command> _ActionMap;
|
||||
|
||||
// Masking Actions:
|
||||
// These are actions that were introduced in an ancestor,
|
||||
// but were edited (or unbound) in the current layer.
|
||||
// _ActionMap shows a Command with keys that were added in this layer,
|
||||
// whereas _MaskingActions provides a view that encompasses all of
|
||||
// the valid associated key chords.
|
||||
// Maintaining this map allows us to return a valid Command
|
||||
// in GetKeyBindingForAction.
|
||||
// Additionally, these commands to not need to be serialized,
|
||||
// whereas those in _ActionMap do. These actions provide more data
|
||||
// than is necessary to be serialized.
|
||||
std::unordered_map<InternalActionID, Model::Command> _MaskingActions;
|
||||
bool _fixupsAppliedDuringLoad{ false };
|
||||
|
||||
// _KeyMap is the map of key chords -> action IDs defined in this layer
|
||||
// _ActionMap is the map of action IDs -> commands defined in this layer
|
||||
// These maps are the ones that we deserialize into when parsing the user json and vice-versa
|
||||
std::unordered_map<Control::KeyChord, winrt::hstring, KeyChordHash, KeyChordEquality> _KeyMap;
|
||||
std::unordered_map<winrt::hstring, Model::Command> _ActionMap;
|
||||
|
||||
// _CumulativeKeyMapCache is the map of key chords -> action IDs defined in all layers, with child layers overriding parent layers
|
||||
Windows::Foundation::Collections::IMap<Control::KeyChord, winrt::hstring> _CumulativeKeyMapCache{ nullptr };
|
||||
// _CumulativeActionMapCache is the map of action IDs -> commands defined in all layers, with child layers overriding parent layers
|
||||
Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command> _CumulativeActionMapCache{ nullptr };
|
||||
|
||||
// _ResolvedKeyActionMapCache is the map of key chords -> commands defined in all layers, with child layers overriding parent layers
|
||||
// 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> _ResolvedKeyActionMapCache{ nullptr };
|
||||
|
||||
friend class SettingsModelUnitTests::KeyBindingsTests;
|
||||
friend class SettingsModelUnitTests::DeserializationTests;
|
||||
|
||||
@@ -11,9 +11,8 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Boolean IsKeyChordExplicitlyUnbound(Microsoft.Terminal.Control.KeyChord keys);
|
||||
|
||||
Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys);
|
||||
|
||||
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action);
|
||||
[method_name("GetKeyBindingForActionWithArgs")] Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs);
|
||||
Command GetActionById(String cmdID);
|
||||
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID);
|
||||
|
||||
Windows.Foundation.Collections.IMapView<String, ActionAndArgs> AvailableActions { get; };
|
||||
|
||||
@@ -32,5 +31,6 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
void DeleteKeyBinding(Microsoft.Terminal.Control.KeyChord keys);
|
||||
|
||||
void RegisterKeyBinding(Microsoft.Terminal.Control.KeyChord keys, ActionAndArgs action);
|
||||
void AddSendInputAction(String name, String input, Microsoft.Terminal.Control.KeyChord keys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Deserialize an ActionMap from the array `json`. The json array should contain
|
||||
// an array of serialized `Command` objects.
|
||||
// - These actions are added to the `ActionMap`, where we automatically handle
|
||||
// overwriting and unbinding actions.
|
||||
// - Deserialize an ActionMap from the array `json`
|
||||
// - The json array either contains an array of serialized `Command` objects,
|
||||
// or an array of keybindings
|
||||
// - The actions are added to _ActionMap and the keybindings are added to _KeyMap
|
||||
// Arguments:
|
||||
// - json: an array of Json::Value's to deserialize into our ActionMap.
|
||||
// - json: an array of Json::Value's to deserialize into our _ActionMap and _KeyMap
|
||||
// Return value:
|
||||
// - a list of warnings encountered while deserializing the json
|
||||
std::vector<SettingsLoadWarnings> ActionMap::LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings)
|
||||
@@ -43,14 +43,69 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// settings phase, so we'll collect them now.
|
||||
std::vector<SettingsLoadWarnings> warnings;
|
||||
|
||||
for (const auto& cmdJson : json)
|
||||
for (const auto& jsonBlock : json)
|
||||
{
|
||||
if (!cmdJson.isObject())
|
||||
if (!jsonBlock.isObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddAction(*Command::FromJson(cmdJson, warnings, origin, withKeybindings));
|
||||
// the json block may be 1 of 3 things:
|
||||
// - the legacy style command block, that has the action, args and keys in it
|
||||
// - the modern style command block, that has the action, args and an ID
|
||||
// - the modern style keys block, that has the keys and an ID
|
||||
|
||||
// if the block contains a "command" field, it is either a legacy or modern style command block
|
||||
// and we can call Command::FromJson on it (Command::FromJson can handle parsing both legacy or modern)
|
||||
|
||||
// if there is no "command" field, then it is a modern style keys block
|
||||
|
||||
// if there are keys, extract them first
|
||||
Control::KeyChord keys{ nullptr };
|
||||
if (withKeybindings && jsonBlock.isMember(JsonKey(KeysKey)))
|
||||
{
|
||||
const auto keysJson{ jsonBlock[JsonKey(KeysKey)] };
|
||||
if (keysJson.isArray() && keysJson.size() > 1)
|
||||
{
|
||||
warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils::GetValueForKey(jsonBlock, KeysKey, keys);
|
||||
}
|
||||
}
|
||||
|
||||
// Now check if this is a command block
|
||||
if (jsonBlock.isMember(JsonKey(CommandsKey)) || jsonBlock.isMember(JsonKey(ActionKey)))
|
||||
{
|
||||
AddAction(*Command::FromJson(jsonBlock, warnings, origin), keys);
|
||||
|
||||
if (jsonBlock.isMember(JsonKey(KeysKey)))
|
||||
{
|
||||
// there are keys in this command block meaning this is the legacy style -
|
||||
// inform the loader that fixups are needed
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
}
|
||||
|
||||
if (jsonBlock.isMember(JsonKey(ActionKey)) && !jsonBlock.isMember(JsonKey(IterateOnKey)) && origin == OriginTag::User && !jsonBlock.isMember(JsonKey(IDKey)))
|
||||
{
|
||||
// for non-nested non-iterable commands,
|
||||
// if there's no ID in the command block we will generate one for the user -
|
||||
// inform the loader that the ID needs to be written into the json
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
}
|
||||
}
|
||||
else if (keys)
|
||||
{
|
||||
// this is not a command block, so it is a keybinding block
|
||||
|
||||
// if the "id" field doesn't exist in the json, then idJson will be an empty string which is fine
|
||||
winrt::hstring idJson;
|
||||
JsonUtils::GetValueForKey(jsonBlock, IDKey, idJson);
|
||||
|
||||
// any existing keybinding with the same keychord in this layer will get overwritten
|
||||
_KeyMap.insert_or_assign(keys, idJson);
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
@@ -60,23 +115,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
Json::Value actionList{ Json::ValueType::arrayValue };
|
||||
|
||||
// Command serializes to an array of JSON objects.
|
||||
// This is because a Command may have multiple key chords associated with it.
|
||||
// The name and icon are only serialized in the first object.
|
||||
// Example:
|
||||
// { "name": "Custom Copy", "command": "copy", "keys": "ctrl+c" }
|
||||
// { "command": "copy", "keys": "ctrl+shift+c" }
|
||||
// { "command": "copy", "keys": "ctrl+ins" }
|
||||
auto toJson = [&actionList](const Model::Command& cmd) {
|
||||
const auto cmdImpl{ winrt::get_self<implementation::Command>(cmd) };
|
||||
const auto& cmdJsonArray{ cmdImpl->ToJson() };
|
||||
for (const auto& cmdJson : cmdJsonArray)
|
||||
{
|
||||
actionList.append(cmdJson);
|
||||
}
|
||||
const auto& cmdJson{ cmdImpl->ToJson() };
|
||||
actionList.append(cmdJson);
|
||||
};
|
||||
|
||||
// Serialize all standard Command objects in the current layer
|
||||
for (const auto& [_, cmd] : _ActionMap)
|
||||
{
|
||||
toJson(cmd);
|
||||
@@ -96,4 +140,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
return actionList;
|
||||
}
|
||||
|
||||
Json::Value ActionMap::KeyBindingsToJson() const
|
||||
{
|
||||
Json::Value keybindingsList{ Json::ValueType::arrayValue };
|
||||
|
||||
// Serialize all standard keybinding objects in the current layer
|
||||
for (const auto& [keys, cmdID] : _KeyMap)
|
||||
{
|
||||
Json::Value keyIDPair{ Json::ValueType::objectValue };
|
||||
JsonUtils::SetValueForKey(keyIDPair, KeysKey, keys);
|
||||
JsonUtils::SetValueForKey(keyIDPair, IDKey, cmdID);
|
||||
keybindingsList.append(keyIDPair);
|
||||
}
|
||||
|
||||
return keybindingsList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
ON_ALL_ACTIONS(CloseTabsAfter) \
|
||||
ON_ALL_ACTIONS(TabSearch) \
|
||||
ON_ALL_ACTIONS(MoveTab) \
|
||||
ON_ALL_ACTIONS(SaveTask) \
|
||||
ON_ALL_ACTIONS(BreakIntoDebugger) \
|
||||
ON_ALL_ACTIONS(TogglePaneReadOnly) \
|
||||
ON_ALL_ACTIONS(EnablePaneReadOnly) \
|
||||
@@ -148,6 +149,7 @@
|
||||
ON_ALL_ACTIONS_WITH_ARGS(SplitPane) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(SwitchToTab) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(ToggleCommandPalette) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(SaveTask) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(FocusPane) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(ExportBuffer) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer) \
|
||||
|
||||
@@ -461,6 +461,7 @@ bool SettingsLoader::FixupUserSettings()
|
||||
};
|
||||
|
||||
auto fixedUp = userSettings.fixupsAppliedDuringLoad;
|
||||
fixedUp = userSettings.globals->FixupsAppliedDuringLoad() || fixedUp;
|
||||
|
||||
fixedUp = RemapColorSchemeForProfile(userSettings.baseLayerProfile) || fixedUp;
|
||||
for (const auto& profile : userSettings.profiles)
|
||||
@@ -504,10 +505,6 @@ bool SettingsLoader::FixupUserSettings()
|
||||
fixedUp = true;
|
||||
}
|
||||
|
||||
// we need to generate an ID for a command in the user settings if it doesn't already have one
|
||||
auto actionMap{ winrt::get_self<ActionMap>(userSettings.globals->ActionMap()) };
|
||||
actionMap->GenerateIDsForActions();
|
||||
|
||||
return fixedUp;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,6 @@ namespace winrt
|
||||
namespace WUX = Windows::UI::Xaml;
|
||||
}
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view IDKey{ "id" };
|
||||
static constexpr std::string_view IconKey{ "icon" };
|
||||
static constexpr std::string_view ActionKey{ "command" };
|
||||
static constexpr std::string_view IterateOnKey{ "iterateOn" };
|
||||
static constexpr std::string_view CommandsKey{ "commands" };
|
||||
static constexpr std::string_view KeysKey{ "keys" };
|
||||
|
||||
static constexpr std::string_view ProfileNameToken{ "${profile.name}" };
|
||||
static constexpr std::string_view ProfileIconToken{ "${profile.icon}" };
|
||||
static constexpr std::string_view SchemeNameToken{ "${scheme.name}" };
|
||||
@@ -43,7 +35,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
command->_Origin = _Origin;
|
||||
command->_ID = _ID;
|
||||
command->_ActionAndArgs = *get_self<implementation::ActionAndArgs>(_ActionAndArgs)->Copy();
|
||||
command->_keyMappings = _keyMappings;
|
||||
command->_iconPath = _iconPath;
|
||||
command->_IterateOn = _IterateOn;
|
||||
|
||||
@@ -121,7 +112,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return hstring{ _ID };
|
||||
}
|
||||
|
||||
bool Command::GenerateID()
|
||||
void Command::GenerateID()
|
||||
{
|
||||
if (_ActionAndArgs)
|
||||
{
|
||||
@@ -130,10 +121,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
_ID = generatedID;
|
||||
_IDWasGenerated = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Command::IDWasGenerated()
|
||||
{
|
||||
return _IDWasGenerated;
|
||||
}
|
||||
|
||||
void Command::Name(const hstring& value)
|
||||
@@ -144,70 +138,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Control::KeyChord> Command::KeyMappings() const noexcept
|
||||
{
|
||||
return _keyMappings;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Add the key chord to the command's list of key mappings.
|
||||
// - If the key chord was already registered, move it to the back
|
||||
// of the line, and dispatch a notification that Command::Keys changed.
|
||||
// Arguments:
|
||||
// - keys: the new key chord that we are registering this command to
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Command::RegisterKey(const Control::KeyChord& keys)
|
||||
{
|
||||
if (!keys)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the KeyChord and add it to the back of the line.
|
||||
// This makes it so that the main key chord associated with this
|
||||
// command is updated.
|
||||
EraseKey(keys);
|
||||
_keyMappings.push_back(keys);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Remove the key chord from the command's list of key mappings.
|
||||
// Arguments:
|
||||
// - keys: the key chord that we are unregistering
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Command::EraseKey(const Control::KeyChord& keys)
|
||||
{
|
||||
_keyMappings.erase(std::remove_if(_keyMappings.begin(), _keyMappings.end(), [&keys](const Control::KeyChord& iterKey) {
|
||||
return keys.Modifiers() == iterKey.Modifiers() && keys.Vkey() == iterKey.Vkey();
|
||||
}),
|
||||
_keyMappings.end());
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Keys is the Command's identifying KeyChord. The command may have multiple keys associated
|
||||
// with it, but we'll only ever display the most recently added one externally. To do this,
|
||||
// _keyMappings stores all of the associated key chords, but ensures that the last entry
|
||||
// is the most recently added one.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the primary key chord associated with this Command
|
||||
Control::KeyChord Command::Keys() const noexcept
|
||||
{
|
||||
if (_keyMappings.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return _keyMappings.back();
|
||||
}
|
||||
|
||||
hstring Command::KeyChordText() const noexcept
|
||||
{
|
||||
return KeyChordSerialization::ToString(Keys());
|
||||
}
|
||||
|
||||
hstring Command::IconPath() const noexcept
|
||||
{
|
||||
if (_iconPath.has_value())
|
||||
@@ -281,8 +211,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// - the newly constructed Command object.
|
||||
winrt::com_ptr<Command> Command::FromJson(const Json::Value& json,
|
||||
std::vector<SettingsLoadWarnings>& warnings,
|
||||
const OriginTag origin,
|
||||
const bool parseKeys)
|
||||
const OriginTag origin)
|
||||
{
|
||||
auto result = winrt::make_self<Command>();
|
||||
result->_Origin = origin;
|
||||
@@ -338,26 +267,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// create an "invalid" ActionAndArgs
|
||||
result->_ActionAndArgs = make<implementation::ActionAndArgs>();
|
||||
}
|
||||
|
||||
if (parseKeys)
|
||||
{
|
||||
// GH#4239 - If the user provided more than one key
|
||||
// chord to a "keys" array, warn the user here.
|
||||
// TODO: GH#1334 - remove this check.
|
||||
const auto keysJson{ json[JsonKey(KeysKey)] };
|
||||
if (keysJson.isArray() && keysJson.size() > 1)
|
||||
{
|
||||
warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord);
|
||||
}
|
||||
else
|
||||
{
|
||||
Control::KeyChord keys{ nullptr };
|
||||
if (JsonUtils::GetValueForKey(json, KeysKey, keys))
|
||||
{
|
||||
result->RegisterKey(keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If an iterable command doesn't have a name set, we'll still just
|
||||
@@ -423,14 +332,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Serialize the Command into an array of json actions
|
||||
// - Serialize the Command into a json value
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - an array of serialized actions
|
||||
// - a serialized command
|
||||
Json::Value Command::ToJson() const
|
||||
{
|
||||
Json::Value cmdList{ Json::ValueType::arrayValue };
|
||||
Json::Value cmdJson{ Json::ValueType::objectValue };
|
||||
|
||||
if (_nestedCommand || _IterateOn != ExpandCommandType::None)
|
||||
{
|
||||
@@ -438,15 +347,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// For these, we can trust _originalJson to be correct.
|
||||
// In fact, we _need_ to use it here because we don't actually deserialize `iterateOn`
|
||||
// until we expand the command.
|
||||
cmdList.append(_originalJson);
|
||||
cmdJson = _originalJson;
|
||||
}
|
||||
else if (_keyMappings.empty())
|
||||
else
|
||||
{
|
||||
// only write out one command
|
||||
Json::Value cmdJson{ Json::ValueType::objectValue };
|
||||
JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath);
|
||||
JsonUtils::SetValueForKey(cmdJson, NameKey, _name);
|
||||
if (!_ID.empty() && !_IDWasGenerated)
|
||||
if (!_ID.empty())
|
||||
{
|
||||
JsonUtils::SetValueForKey(cmdJson, IDKey, _ID);
|
||||
}
|
||||
@@ -455,38 +362,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs);
|
||||
}
|
||||
|
||||
cmdList.append(cmdJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we'll write out one command per key mapping
|
||||
for (auto keys{ _keyMappings.begin() }; keys != _keyMappings.end(); ++keys)
|
||||
{
|
||||
Json::Value cmdJson{ Json::ValueType::objectValue };
|
||||
|
||||
if (keys == _keyMappings.begin())
|
||||
{
|
||||
// First iteration also writes icon and name
|
||||
JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath);
|
||||
JsonUtils::SetValueForKey(cmdJson, NameKey, _name);
|
||||
if (!_ID.empty())
|
||||
{
|
||||
JsonUtils::SetValueForKey(cmdJson, IDKey, _ID);
|
||||
}
|
||||
}
|
||||
|
||||
if (_ActionAndArgs)
|
||||
{
|
||||
cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs);
|
||||
}
|
||||
|
||||
JsonUtils::SetValueForKey(cmdJson, KeysKey, *keys);
|
||||
cmdList.append(cmdJson);
|
||||
}
|
||||
}
|
||||
|
||||
return cmdList;
|
||||
return cmdJson;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
@@ -787,7 +665,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// the command will be run as a directory change instead.
|
||||
IVector<Model::Command> Command::HistoryToCommands(IVector<winrt::hstring> history,
|
||||
winrt::hstring currentCommandline,
|
||||
bool directories)
|
||||
bool directories,
|
||||
hstring iconPath)
|
||||
{
|
||||
std::wstring cdText = directories ? L"cd " : L"";
|
||||
auto result = std::vector<Model::Command>();
|
||||
@@ -819,9 +698,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto command = winrt::make_self<Command>();
|
||||
command->_ActionAndArgs = actionAndArgs;
|
||||
command->_name = winrt::hstring{ line };
|
||||
command->_iconPath = directories ?
|
||||
L"\ue8da" : // OpenLocal (a folder with an arrow pointing up)
|
||||
L"\ue81c"; // History icon
|
||||
command->_iconPath = iconPath;
|
||||
result.push_back(*command);
|
||||
foundCommands[line] = true;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ namespace SettingsModelUnitTests
|
||||
class CommandTests;
|
||||
};
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view IDKey{ "id" };
|
||||
static constexpr std::string_view IconKey{ "icon" };
|
||||
static constexpr std::string_view ActionKey{ "command" };
|
||||
static constexpr std::string_view IterateOnKey{ "iterateOn" };
|
||||
static constexpr std::string_view CommandsKey{ "commands" };
|
||||
static constexpr std::string_view KeysKey{ "keys" };
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct Command : CommandT<Command>
|
||||
@@ -40,8 +48,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json,
|
||||
std::vector<SettingsLoadWarnings>& warnings,
|
||||
const OriginTag origin,
|
||||
const bool parseKeys = true);
|
||||
const OriginTag origin);
|
||||
|
||||
static void ExpandCommands(Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command>& commands,
|
||||
Windows::Foundation::Collections::IVectorView<Model::Profile> profiles,
|
||||
@@ -62,13 +69,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void Name(const hstring& name);
|
||||
|
||||
hstring ID() const noexcept;
|
||||
bool GenerateID();
|
||||
|
||||
Control::KeyChord Keys() const noexcept;
|
||||
hstring KeyChordText() const noexcept;
|
||||
std::vector<Control::KeyChord> KeyMappings() const noexcept;
|
||||
void RegisterKey(const Control::KeyChord& keys);
|
||||
void EraseKey(const Control::KeyChord& keys);
|
||||
void GenerateID();
|
||||
bool IDWasGenerated();
|
||||
|
||||
hstring IconPath() const noexcept;
|
||||
void IconPath(const hstring& val);
|
||||
@@ -76,7 +78,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static Windows::Foundation::Collections::IVector<Model::Command> ParsePowerShellMenuComplete(winrt::hstring json, int32_t replaceLength);
|
||||
static Windows::Foundation::Collections::IVector<Model::Command> HistoryToCommands(Windows::Foundation::Collections::IVector<winrt::hstring> history,
|
||||
winrt::hstring currentCommandline,
|
||||
bool directories);
|
||||
bool directories,
|
||||
hstring iconPath);
|
||||
|
||||
WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
|
||||
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);
|
||||
@@ -85,7 +88,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
private:
|
||||
Json::Value _originalJson;
|
||||
Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command> _subcommands{ nullptr };
|
||||
std::vector<Control::KeyChord> _keyMappings;
|
||||
std::optional<std::wstring> _name;
|
||||
std::wstring _ID;
|
||||
bool _IDWasGenerated{ false };
|
||||
|
||||
@@ -38,9 +38,6 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
String Name { get; };
|
||||
String ID { get; };
|
||||
ActionAndArgs ActionAndArgs { get; };
|
||||
Microsoft.Terminal.Control.KeyChord Keys { get; };
|
||||
void RegisterKey(Microsoft.Terminal.Control.KeyChord keys);
|
||||
String KeyChordText { get; };
|
||||
|
||||
String IconPath;
|
||||
|
||||
@@ -48,7 +45,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Windows.Foundation.Collections.IMapView<String, Command> NestedCommands { get; };
|
||||
|
||||
static IVector<Command> ParsePowerShellMenuComplete(String json, Int32 replaceLength);
|
||||
static IVector<Command> HistoryToCommands(IVector<String> commandHistory, String commandline, Boolean directories);
|
||||
static IVector<Command> HistoryToCommands(IVector<String> commandHistory, String commandline, Boolean directories, String iconPath);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
DEFINE_ENUM_MAP(Model::WindowingMode, WindowingMode);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Core::MatchMode, MatchMode);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI);
|
||||
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::TextMeasurement, TextMeasurement);
|
||||
|
||||
// Profile Settings
|
||||
DEFINE_ENUM_MAP(Model::CloseOnExitMode, CloseOnExitMode);
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, WindowingMode> WindowingMode();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Core::MatchMode> MatchMode();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::GraphicsAPI> GraphicsAPI();
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::TextMeasurement> TextMeasurement();
|
||||
|
||||
// Profile Settings
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, CloseOnExitMode> CloseOnExitMode();
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.WindowingMode> WindowingMode { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Core.MatchMode> MatchMode { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.GraphicsAPI> GraphicsAPI { get; };
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.TextMeasurement> TextMeasurement { get; };
|
||||
|
||||
// Profile Settings
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.CloseOnExitMode> CloseOnExitMode { get; };
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace winrt::Microsoft::UI::Xaml::Controls;
|
||||
|
||||
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view KeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view ActionsKey{ "actions" };
|
||||
static constexpr std::string_view ThemeKey{ "theme" };
|
||||
static constexpr std::string_view DefaultProfileKey{ "defaultProfile" };
|
||||
@@ -45,6 +45,7 @@ void GlobalAppSettings::_FinalizeInheritance()
|
||||
}
|
||||
}
|
||||
}
|
||||
_actionMap->_FinalizeInheritance();
|
||||
}
|
||||
|
||||
winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
@@ -155,7 +156,9 @@ void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origi
|
||||
|
||||
void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings)
|
||||
{
|
||||
static constexpr std::array bindingsKeys{ LegacyKeybindingsKey, ActionsKey };
|
||||
// we want to do the keybindings map after the actions map so that we overwrite any leftover keybindings
|
||||
// that might have existed in the first pass, in case the user did a partial update from legacy to modern
|
||||
static constexpr std::array bindingsKeys{ ActionsKey, KeybindingsKey };
|
||||
for (const auto& jsonKey : bindingsKeys)
|
||||
{
|
||||
if (auto bindings{ json[JsonKey(jsonKey)] })
|
||||
@@ -240,6 +243,11 @@ Json::Value GlobalAppSettings::ToJson()
|
||||
{
|
||||
_GraphicsAPI.reset();
|
||||
}
|
||||
if (_TextMeasurement == Control::TextMeasurement::Graphemes)
|
||||
{
|
||||
_TextMeasurement.reset();
|
||||
}
|
||||
|
||||
if (_DisablePartialInvalidation == false)
|
||||
{
|
||||
_DisablePartialInvalidation.reset();
|
||||
@@ -259,9 +267,16 @@ Json::Value GlobalAppSettings::ToJson()
|
||||
#undef GLOBAL_SETTINGS_TO_JSON
|
||||
|
||||
json[JsonKey(ActionsKey)] = _actionMap->ToJson();
|
||||
json[JsonKey(KeybindingsKey)] = _actionMap->KeyBindingsToJson();
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::FixupsAppliedDuringLoad()
|
||||
{
|
||||
return _actionMap->FixupsAppliedDuringLoad();
|
||||
}
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::Theme GlobalAppSettings::CurrentTheme() noexcept
|
||||
{
|
||||
auto requestedTheme = Model::Theme::IsSystemInDarkTheme() ?
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true);
|
||||
|
||||
Json::Value ToJson();
|
||||
bool FixupsAppliedDuringLoad();
|
||||
|
||||
const std::vector<SettingsLoadWarnings>& KeybindingsWarnings() const;
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
INHERITABLE_SETTING(Microsoft.Terminal.Control.GraphicsAPI, GraphicsAPI);
|
||||
INHERITABLE_SETTING(Boolean, DisablePartialInvalidation);
|
||||
INHERITABLE_SETTING(Boolean, SoftwareRendering);
|
||||
INHERITABLE_SETTING(Microsoft.Terminal.Control.TextMeasurement, TextMeasurement);
|
||||
INHERITABLE_SETTING(Boolean, UseBackgroundImageForWindow);
|
||||
INHERITABLE_SETTING(Boolean, ForceVTInput);
|
||||
INHERITABLE_SETTING(Boolean, DebugFeaturesEnabled);
|
||||
|
||||
@@ -27,6 +27,7 @@ Author(s):
|
||||
X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI, "rendering.graphicsAPI") \
|
||||
X(bool, DisablePartialInvalidation, "rendering.disablePartialInvalidation", false) \
|
||||
X(bool, SoftwareRendering, "rendering.software", false) \
|
||||
X(winrt::Microsoft::Terminal::Control::TextMeasurement, TextMeasurement, "compatibility.textMeasurement") \
|
||||
X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \
|
||||
X(bool, ForceVTInput, "experimental.input.forceVT", false) \
|
||||
X(bool, TrimBlockSelection, "trimBlockSelection", true) \
|
||||
@@ -136,9 +137,10 @@ Author(s):
|
||||
// * ForegroundKey, BackgroundKey, SelectionBackgroundKey, CursorColorKey: all optional colors
|
||||
// * Opacity: needs special parsing
|
||||
|
||||
#define MTSM_THEME_SETTINGS(X) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::WindowTheme, Window, "window", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::TabRowTheme, TabRow, "tabRow", nullptr) \
|
||||
#define MTSM_THEME_SETTINGS(X) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::WindowTheme, Window, "window", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::SettingsTheme, Settings, "settings", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::TabRowTheme, TabRow, "tabRow", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::TabTheme, Tab, "tab", nullptr)
|
||||
|
||||
#define MTSM_THEME_WINDOW_SETTINGS(X) \
|
||||
@@ -148,6 +150,9 @@ Author(s):
|
||||
X(bool, RainbowFrame, "experimental.rainbowFrame", false) \
|
||||
X(bool, UseMica, "useMica", false)
|
||||
|
||||
#define MTSM_THEME_SETTINGS_SETTINGS(X) \
|
||||
X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "theme", winrt::Windows::UI::Xaml::ElementTheme::Default)
|
||||
|
||||
#define MTSM_THEME_TABROW_SETTINGS(X) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Background, "background", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedBackground, "unfocusedBackground", nullptr)
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
<ClInclude Include="SeparatorEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ActionEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FolderEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -185,6 +188,9 @@
|
||||
<ClCompile Include="SeparatorEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ActionEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FolderEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "SeparatorEntry.h"
|
||||
#include "FolderEntry.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "ActionEntry.h"
|
||||
#include "RemainingProfilesEntry.h"
|
||||
#include "MatchProfilesEntry.h"
|
||||
|
||||
@@ -52,6 +53,8 @@ winrt::com_ptr<NewTabMenuEntry> NewTabMenuEntry::FromJson(const Json::Value& jso
|
||||
return RemainingProfilesEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
return MatchProfilesEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Action:
|
||||
return ActionEntry::FromJson(json);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Separator,
|
||||
Folder,
|
||||
RemainingProfiles,
|
||||
MatchProfiles
|
||||
MatchProfiles,
|
||||
Action
|
||||
};
|
||||
|
||||
[default_interface] unsealed runtimeclass NewTabMenuEntry
|
||||
@@ -34,6 +35,13 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Int32 ProfileIndex;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ActionEntry : NewTabMenuEntry
|
||||
{
|
||||
ActionEntry();
|
||||
|
||||
String ActionId;
|
||||
}
|
||||
|
||||
enum FolderEntryInlining
|
||||
{
|
||||
Never = 0,
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -126,6 +126,9 @@
|
||||
<data name="SplitPaneParentCommandName" xml:space="preserve">
|
||||
<value>Split pane</value>
|
||||
</data>
|
||||
<data name="SnippetsPaneCommandName" xml:space="preserve">
|
||||
<value>Open snippets pane</value>
|
||||
</data>
|
||||
<data name="ApplicationDisplayNamePortable" xml:space="preserve">
|
||||
<value>Terminal (Portable)</value>
|
||||
<comment>This display name is used when the Terminal application is running in a "portable" mode, where settings are not stored in a shared location.</comment>
|
||||
@@ -727,4 +730,11 @@
|
||||
<value>Open about dialog</value>
|
||||
<comment>This will open the "about" dialog, to display version info and other documentation</comment>
|
||||
</data>
|
||||
</root>
|
||||
<data name="SaveActionNamePrefix" xml:space="preserve">
|
||||
<value>Save Task</value>
|
||||
</data>
|
||||
<data name="OpenTasksPaneCommandKey" xml:space="preserve">
|
||||
<value>Open tasks pane</value>
|
||||
<comment>This will open a pane with a list of the users's saved commands in it</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -367,6 +367,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
_GraphicsAPI = globalSettings.GraphicsAPI();
|
||||
_DisablePartialInvalidation = globalSettings.DisablePartialInvalidation();
|
||||
_SoftwareRendering = globalSettings.SoftwareRendering();
|
||||
_TextMeasurement = globalSettings.TextMeasurement();
|
||||
_UseBackgroundImageForWindow = globalSettings.UseBackgroundImageForWindow();
|
||||
_ForceVTInput = globalSettings.ForceVTInput();
|
||||
_TrimBlockSelection = globalSettings.TrimBlockSelection();
|
||||
|
||||
@@ -158,6 +158,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, bool, DisablePartialInvalidation, false);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, bool, SoftwareRendering, false);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextMeasurement, TextMeasurement);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, bool, UseBackgroundImageForWindow, false);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, bool, ForceVTInput, false);
|
||||
|
||||
|
||||
@@ -501,11 +501,12 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FindMatchDirecti
|
||||
|
||||
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::SuggestionsSource)
|
||||
{
|
||||
static constexpr std::array<pair_type, 5> mappings = {
|
||||
static constexpr std::array<pair_type, 6> mappings = {
|
||||
pair_type{ "none", AllClear },
|
||||
pair_type{ "tasks", ValueType::Tasks },
|
||||
pair_type{ "commandHistory", ValueType::CommandHistory },
|
||||
pair_type{ "directoryHistory", ValueType::DirectoryHistory },
|
||||
pair_type{ "quickFix", ValueType::QuickFixes },
|
||||
pair_type{ "all", AllSet },
|
||||
};
|
||||
};
|
||||
@@ -678,8 +679,9 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ScrollToMarkDirection)
|
||||
// Possible NewTabMenuEntryType values
|
||||
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType)
|
||||
{
|
||||
JSON_MAPPINGS(5) = {
|
||||
JSON_MAPPINGS(6) = {
|
||||
pair_type{ "profile", ValueType::Profile },
|
||||
pair_type{ "action", ValueType::Action },
|
||||
pair_type{ "separator", ValueType::Separator },
|
||||
pair_type{ "folder", ValueType::Folder },
|
||||
pair_type{ "remainingProfiles", ValueType::RemainingProfiles },
|
||||
@@ -770,3 +772,12 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::GraphicsAPI)
|
||||
pair_type{ "direct3d11", ValueType::Direct3D11 },
|
||||
};
|
||||
};
|
||||
|
||||
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::TextMeasurement)
|
||||
{
|
||||
JSON_MAPPINGS(3) = {
|
||||
pair_type{ "graphemes", ValueType::Graphemes },
|
||||
pair_type{ "wcswidth", ValueType::Wcswidth },
|
||||
pair_type{ "console", ValueType::Console },
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
|
||||
#include "SettingsTheme.g.h"
|
||||
#include "ThemeColor.g.cpp"
|
||||
#include "WindowTheme.g.cpp"
|
||||
#include "TabRowTheme.g.cpp"
|
||||
@@ -56,6 +57,7 @@ static constexpr wchar_t RegKeyAccentColor[] = L"AccentColor";
|
||||
}
|
||||
|
||||
THEME_OBJECT(WindowTheme, MTSM_THEME_WINDOW_SETTINGS);
|
||||
THEME_OBJECT(SettingsTheme, MTSM_THEME_SETTINGS_SETTINGS);
|
||||
THEME_OBJECT(TabRowTheme, MTSM_THEME_TABROW_SETTINGS);
|
||||
THEME_OBJECT(TabTheme, MTSM_THEME_TAB_SETTINGS);
|
||||
|
||||
@@ -219,6 +221,7 @@ uint8_t ThemeColor::UnfocusedTabOpacity() const noexcept
|
||||
};
|
||||
|
||||
THEME_OBJECT_CONVERTER(winrt::Microsoft::Terminal::Settings::Model, WindowTheme, MTSM_THEME_WINDOW_SETTINGS);
|
||||
THEME_OBJECT_CONVERTER(winrt::Microsoft::Terminal::Settings::Model, SettingsTheme, MTSM_THEME_SETTINGS_SETTINGS);
|
||||
THEME_OBJECT_CONVERTER(winrt::Microsoft::Terminal::Settings::Model, TabRowTheme, MTSM_THEME_TABROW_SETTINGS);
|
||||
THEME_OBJECT_CONVERTER(winrt::Microsoft::Terminal::Settings::Model, TabTheme, MTSM_THEME_TAB_SETTINGS);
|
||||
|
||||
@@ -251,6 +254,10 @@ winrt::com_ptr<Theme> Theme::Copy() const
|
||||
{
|
||||
theme->_Tab = *winrt::get_self<implementation::TabTheme>(_Tab)->Copy();
|
||||
}
|
||||
if (_Settings)
|
||||
{
|
||||
theme->_Settings = *winrt::get_self<implementation::SettingsTheme>(_Settings)->Copy();
|
||||
}
|
||||
|
||||
return theme;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user