Compare commits
106 Commits
release-1.
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e1ce8de26 | ||
|
|
5e13c37217 | ||
|
|
6328b1adda | ||
|
|
177cf1caae | ||
|
|
75473c50fb | ||
|
|
76cb9654e1 | ||
|
|
e967801cdf | ||
|
|
50cfd84dd2 | ||
|
|
33860f7c1a | ||
|
|
860a3c7b23 | ||
|
|
eae88a9fdb | ||
|
|
137aea367a | ||
|
|
8a93b1dd5d | ||
|
|
a3a9df82b5 | ||
|
|
9215b5282d | ||
|
|
cffd4eb8e9 | ||
|
|
6ca7d0e054 | ||
|
|
6f1dc5c9fa | ||
|
|
4027ba37a6 | ||
|
|
b5be04c3ae | ||
|
|
d8810f2730 | ||
|
|
48d4e248b7 | ||
|
|
58f5d7c72e | ||
|
|
b24dbf7c77 | ||
|
|
264618ae88 | ||
|
|
f892e52077 | ||
|
|
ff23be04fb | ||
|
|
81eb13542a | ||
|
|
073e732301 | ||
|
|
e7d3dc5da2 | ||
|
|
b90cc38f0f | ||
|
|
92b3453119 | ||
|
|
e25da60775 | ||
|
|
26d6a37800 | ||
|
|
cee5fc9226 | ||
|
|
951f389210 | ||
|
|
15f2535752 | ||
|
|
10e91a823d | ||
|
|
b91430b64d | ||
|
|
d1a72613c7 | ||
|
|
3e5eb73597 | ||
|
|
dc5baab3fe | ||
|
|
cc2fd9b490 | ||
|
|
45243bbee6 | ||
|
|
52eb2e5faa | ||
|
|
8b094fdfde | ||
|
|
4f55568a17 | ||
|
|
acd3ba7bd9 | ||
|
|
e337faaaf8 | ||
|
|
1fdceb0ea9 | ||
|
|
4eaa0b83c7 | ||
|
|
78ca722028 | ||
|
|
10bc1a6532 | ||
|
|
6485a2b440 | ||
|
|
ffaba38fd4 | ||
|
|
7a94b8f725 | ||
|
|
e2f5f75471 | ||
|
|
7ac7fd1de9 | ||
|
|
dd684cbca1 | ||
|
|
edc8b557e1 | ||
|
|
82f968d8d5 | ||
|
|
67c7969879 | ||
|
|
b3d8f0e279 | ||
|
|
10ace041c3 | ||
|
|
4d23121a7e | ||
|
|
414007fdd8 | ||
|
|
72c0601d00 | ||
|
|
b3225db167 | ||
|
|
2517183b63 | ||
|
|
9fbcbcc9d8 | ||
|
|
73aefc18e8 | ||
|
|
a02585da35 | ||
|
|
2cf69338cd | ||
|
|
d71d8d7b32 | ||
|
|
9d411d405c | ||
|
|
58be8cd117 | ||
|
|
487f33ee89 | ||
|
|
829beda501 | ||
|
|
6e6979abe8 | ||
|
|
1fbe8e415d | ||
|
|
fa93fdc034 | ||
|
|
cf6e1f273a | ||
|
|
99059451d8 | ||
|
|
a309191461 | ||
|
|
e1be26b184 | ||
|
|
bc546dbdb0 | ||
|
|
00763fda9e | ||
|
|
b88be4534b | ||
|
|
207666e34d | ||
|
|
62b9a0d1c0 | ||
|
|
02f47f49c3 | ||
|
|
6905065fcf | ||
|
|
0416a944a2 | ||
|
|
3627d8abd5 | ||
|
|
33a9e32736 | ||
|
|
75af4cabbb | ||
|
|
43bd483962 | ||
|
|
0672812e6f | ||
|
|
f53553f4e4 | ||
|
|
11130a4895 | ||
|
|
0886e8f12d | ||
|
|
2e564368c8 | ||
|
|
ead76d7f16 | ||
|
|
614d1b21d6 | ||
|
|
fe640ff894 | ||
|
|
c56eb8fd93 |
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,7 +9,8 @@
|
||||
* [ ] Closes #xxx
|
||||
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
|
||||
* [ ] Tests added/passed
|
||||
* [ ] Requires documentation to be updated
|
||||
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
|
||||
* [ ] Schema updated.
|
||||
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx
|
||||
|
||||
<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
|
||||
|
||||
25
.github/actions/spell-check/advice.txt
vendored
@@ -1,25 +0,0 @@
|
||||
<details>
|
||||
<summary>
|
||||
:pencil2: Contributor please read this
|
||||
</summary>
|
||||
|
||||
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
|
||||
|
||||
:warning: The command is written for posix shells. You can copy the contents of each `perl` command excluding the outer `'` marks and dropping any `'"`/`"'` quotation mark pairs into a file and then run `perl file.pl` from the root of the repository to run the code. Alternatively, you can manually insert the items...
|
||||
|
||||
If the listed items are:
|
||||
* ... **misspelled**, then please *correct* them instead of using the command.
|
||||
* ... *names*, please add them to `.github/actions/spell-check/dictionary/names.txt`.
|
||||
* ... APIs, you can add them to a file in `.github/actions/spell-check/dictionary/`.
|
||||
* ... just things you're using, please add them to an appropriate file in `.github/actions/spell-check/expect/`.
|
||||
* ... tokens you only need in one place and shouldn't *generally be used*, you can add an item in an appropriate file in `.github/actions/spell-check/patterns/`.
|
||||
|
||||
See the `README.md` in each directory for more information.
|
||||
|
||||
:microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [:check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink:
|
||||
|
||||
</details>
|
||||
|
||||
#### :warning: Reviewers
|
||||
At present, the action that triggered this message will not show its :x: in this PR unless the branch is within this repository.
|
||||
Thus, you **should** make sure that this comment has been addressed before encouraging the merge bot to merge this PR.
|
||||
35
.github/actions/spell-check/dictionary/apis.txt
vendored
@@ -1,35 +0,0 @@
|
||||
ACCEPTFILES
|
||||
ACCESSDENIED
|
||||
bitfield
|
||||
bitfields
|
||||
CLASSNOTAVAILABLE
|
||||
EXPCMDFLAGS
|
||||
EXPCMDSTATE
|
||||
fullkbd
|
||||
href
|
||||
IAsync
|
||||
IBox
|
||||
IBind
|
||||
IClass
|
||||
IComparable
|
||||
ICustom
|
||||
IDirect
|
||||
IExplorer
|
||||
IMap
|
||||
IObject
|
||||
IStorage
|
||||
LCID
|
||||
LSHIFT
|
||||
NCHITTEST
|
||||
NCLBUTTONDBLCLK
|
||||
NCRBUTTONDBLCLK
|
||||
NOAGGREGATION
|
||||
NOREDIRECTIONBITMAP
|
||||
oaidl
|
||||
ocidl
|
||||
RETURNCMD
|
||||
rfind
|
||||
roundf
|
||||
RSHIFT
|
||||
SIZENS
|
||||
tmp
|
||||
479826
.github/actions/spell-check/dictionary/dictionary.txt
vendored
@@ -1,3 +0,0 @@
|
||||
powf
|
||||
sqrtf
|
||||
isnan
|
||||
@@ -1,14 +0,0 @@
|
||||
ACLs
|
||||
DACL
|
||||
DACLs
|
||||
LKG
|
||||
mfcribbon
|
||||
microsoft
|
||||
microsoftonline
|
||||
osgvsowi
|
||||
powerrename
|
||||
powershell
|
||||
SACLs
|
||||
tdbuildteamid
|
||||
vcruntime
|
||||
visualstudio
|
||||
63
.github/actions/spell-check/excludes.txt
vendored
@@ -1,63 +0,0 @@
|
||||
(?:^|/)dirs$
|
||||
(?:^|/)go\.mod$
|
||||
(?:^|/)go\.sum$
|
||||
(?:^|/)package-lock\.json$
|
||||
(?:^|/)sources(?:|\.dep)$
|
||||
SUMS$
|
||||
\.ai$
|
||||
\.bmp$
|
||||
\.cer$
|
||||
\.class$
|
||||
\.crl$
|
||||
\.crt$
|
||||
\.csr$
|
||||
\.dll$
|
||||
\.DS_Store$
|
||||
\.eot$
|
||||
\.eps$
|
||||
\.exe$
|
||||
\.gif$
|
||||
\.graffle$
|
||||
\.gz$
|
||||
\.icns$
|
||||
\.ico$
|
||||
\.jar$
|
||||
\.jpeg$
|
||||
\.jpg$
|
||||
\.key$
|
||||
\.lib$
|
||||
\.lock$
|
||||
\.map$
|
||||
\.min\..
|
||||
\.mp3$
|
||||
\.mp4$
|
||||
\.otf$
|
||||
\.pbxproj$
|
||||
\.pdf$
|
||||
\.pem$
|
||||
\.png$
|
||||
\.psd$
|
||||
\.runsettings$
|
||||
\.sig$
|
||||
\.so$
|
||||
\.svg$
|
||||
\.svgz$
|
||||
\.tar$
|
||||
\.tgz$
|
||||
\.ttf$
|
||||
\.woff
|
||||
\.xcf$
|
||||
\.xls
|
||||
\.xpm$
|
||||
\.yml$
|
||||
\.zip$
|
||||
^consolegit2gitfilters\.json$
|
||||
^dep/
|
||||
^oss/
|
||||
^doc/reference/UTF8-torture-test\.txt$
|
||||
^src/interactivity/onecore/BgfxEngine\.
|
||||
^src/renderer/wddmcon/WddmConRenderer\.
|
||||
^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$
|
||||
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
|
||||
^\.github/actions/spell-check/
|
||||
^\.gitignore$
|
||||
15
.github/actions/spell-check/expect/web.txt
vendored
@@ -1,15 +0,0 @@
|
||||
http
|
||||
td
|
||||
www
|
||||
ecma
|
||||
rapidtables
|
||||
WCAG
|
||||
freedesktop
|
||||
ycombinator
|
||||
robertelder
|
||||
kovidgoyal
|
||||
leonerd
|
||||
fixterms
|
||||
uk
|
||||
winui
|
||||
appshellintegration
|
||||
@@ -1,18 +0,0 @@
|
||||
https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_#\/.]*
|
||||
https://aka\.ms/[-a-zA-Z0-9?&=\/_]*
|
||||
https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf
|
||||
https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]*
|
||||
https://www.w3.org/[-a-zA-Z0-9?&=\/_#]*
|
||||
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
|
||||
https://[a-z-]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
|
||||
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
|
||||
(?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|</UniqueIdentifier)
|
||||
(?:0[Xx]|\\x|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]{0,2}\b
|
||||
microsoft/cascadia-code\@[0-9a-fA-F]{40}
|
||||
\d+x\d+Logo
|
||||
Scro\&ll
|
||||
# selectionInput.cpp
|
||||
:\\windows\\syste\b
|
||||
TestUtils::VerifyExpectedString\(tb, L"[^"]+"
|
||||
hostSm\.ProcessString\(L"[^"]+"
|
||||
\b([A-Za-z])\1{3,}\b
|
||||
15
.github/actions/spelling/README.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# check-spelling/check-spelling configuration
|
||||
|
||||
File | Purpose | Format | Info
|
||||
-|-|-|-
|
||||
[allow/*.txt](allow/) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow)
|
||||
[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject)
|
||||
[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes)
|
||||
[patterns/*.txt](patterns/) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
|
||||
[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns)
|
||||
[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
|
||||
[expect/*.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect)
|
||||
[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice)
|
||||
|
||||
Note: you can replace any of these files with a directory by the same name (minus the suffix)
|
||||
and then include multiple files inside that directory (with that suffix) to merge multiple files together.
|
||||
48
.github/actions/spelling/advice.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
|
||||
<details>
|
||||
<summary>
|
||||
:pencil2: Contributor please read this
|
||||
</summary>
|
||||
|
||||
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
|
||||
|
||||
:warning: The command is written for posix shells. If it doesn't work for you, you can manually _add_ (one word per line) / _remove_ items to `expect.txt` and the `excludes.txt` files.
|
||||
|
||||
If the listed items are:
|
||||
|
||||
* ... **misspelled**, then please *correct* them instead of using the command.
|
||||
* ... *names*, please add them to `.github/actions/spelling/allow/names.txt`.
|
||||
* ... APIs, you can add them to a file in `.github/actions/spelling/allow/`.
|
||||
* ... just things you're using, please add them to an appropriate file in `.github/actions/spelling/expect/`.
|
||||
* ... tokens you only need in one place and shouldn't *generally be used*, you can add an item in an appropriate file in `.github/actions/spelling/patterns/`.
|
||||
|
||||
See the `README.md` in each directory for more information.
|
||||
|
||||
:microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink:
|
||||
|
||||
|
||||
<details><summary>If the flagged items are :exploding_head: false positives</summary>
|
||||
|
||||
If items relate to a ...
|
||||
* binary file (or some other file you wouldn't want to check at all).
|
||||
|
||||
Please add a file path to the `excludes.txt` file matching the containing file.
|
||||
|
||||
File paths are Perl 5 Regular Expressions - you can [test](
|
||||
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
|
||||
|
||||
`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
|
||||
../tree/HEAD/README.md) (on whichever branch you're using).
|
||||
|
||||
* well-formed pattern.
|
||||
|
||||
If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
|
||||
try adding it to the `patterns.txt` file.
|
||||
|
||||
Patterns are Perl 5 Regular Expressions - you can [test](
|
||||
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
|
||||
|
||||
Note that patterns can't match multiline strings.
|
||||
</details>
|
||||
|
||||
</details>
|
||||
@@ -1,6 +1,6 @@
|
||||
# Dictionaries are lists of words to accept unconditionally
|
||||
# Allow files are lists of words to accept unconditionally
|
||||
|
||||
While check spelling will complain about a whitelisted word
|
||||
While check spelling will complain about an expected word
|
||||
which is no longer present, you can include things here even if
|
||||
they are not otherwise present in the repository.
|
||||
|
||||
@@ -8,13 +8,14 @@ E.g., you could include a list of system APIs here, or potential
|
||||
contributors (so that if a future commit includes their name,
|
||||
it'll be accepted).
|
||||
|
||||
### Files
|
||||
## Files
|
||||
|
||||
| File | Description |
|
||||
| ---- | ----------- |
|
||||
| [Dictionary](dictionary.txt) | Primary US English dictionary |
|
||||
| [Allow](allow.txt) | Supplements to the dictionary |
|
||||
| [Chinese](chinese.txt) | Chinese words |
|
||||
| [Japanese](japanese.txt) | Japanese words |
|
||||
| [Microsoft](microsoft.txt) | Microsoft brand items |
|
||||
| [Fonts](fonts.txt) | Font names |
|
||||
| [Names](names.txt) | Names of people |
|
||||
| [Colors](colors.txt) | Names of color |
|
||||
108
.github/actions/spelling/allow/allow.txt
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
admins
|
||||
allcolors
|
||||
Apc
|
||||
apc
|
||||
breadcrumb
|
||||
breadcrumbs
|
||||
bsd
|
||||
calt
|
||||
ccmp
|
||||
changelog
|
||||
clickable
|
||||
clig
|
||||
CMMI
|
||||
copyable
|
||||
cybersecurity
|
||||
dalet
|
||||
Dcs
|
||||
dcs
|
||||
dialytika
|
||||
dje
|
||||
downside
|
||||
downsides
|
||||
dze
|
||||
dzhe
|
||||
EDDB
|
||||
EDDC
|
||||
Enum'd
|
||||
Fitt
|
||||
formattings
|
||||
FTCS
|
||||
ftp
|
||||
fvar
|
||||
gantt
|
||||
gcc
|
||||
geeksforgeeks
|
||||
ghe
|
||||
github
|
||||
gje
|
||||
godbolt
|
||||
hostname
|
||||
hostnames
|
||||
https
|
||||
hyperlink
|
||||
hyperlinking
|
||||
hyperlinks
|
||||
iconify
|
||||
img
|
||||
inlined
|
||||
It'd
|
||||
kje
|
||||
libfuzzer
|
||||
libuv
|
||||
liga
|
||||
lje
|
||||
Llast
|
||||
llvm
|
||||
Lmid
|
||||
locl
|
||||
lol
|
||||
lorem
|
||||
Lorigin
|
||||
maxed
|
||||
minimalistic
|
||||
mkmk
|
||||
mnt
|
||||
mru
|
||||
nje
|
||||
noreply
|
||||
ogonek
|
||||
ok'd
|
||||
overlined
|
||||
pipeline
|
||||
postmodern
|
||||
ptys
|
||||
qof
|
||||
qps
|
||||
rclt
|
||||
reimplementation
|
||||
reserialization
|
||||
reserialize
|
||||
reserializes
|
||||
rlig
|
||||
runtimes
|
||||
shcha
|
||||
slnt
|
||||
Sos
|
||||
ssh
|
||||
timeline
|
||||
timelines
|
||||
timestamped
|
||||
TLDR
|
||||
tokenizes
|
||||
tonos
|
||||
toolset
|
||||
tshe
|
||||
ubuntu
|
||||
uiatextrange
|
||||
UIs
|
||||
und
|
||||
unregister
|
||||
versioned
|
||||
vsdevcmd
|
||||
We'd
|
||||
wildcards
|
||||
XBox
|
||||
YBox
|
||||
yeru
|
||||
zhe
|
||||
248
.github/actions/spelling/allow/apis.txt
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
ACCEPTFILES
|
||||
ACCESSDENIED
|
||||
acl
|
||||
aclapi
|
||||
alignas
|
||||
alignof
|
||||
APPLYTOSUBMENUS
|
||||
appxrecipe
|
||||
bitfield
|
||||
bitfields
|
||||
BUILDBRANCH
|
||||
BUILDMSG
|
||||
BUILDNUMBER
|
||||
BYCOMMAND
|
||||
BYPOSITION
|
||||
charconv
|
||||
CLASSNOTAVAILABLE
|
||||
CLOSEAPP
|
||||
cmdletbinding
|
||||
COLORPROPERTY
|
||||
colspan
|
||||
COMDLG
|
||||
commandlinetoargv
|
||||
comparand
|
||||
cstdint
|
||||
CXICON
|
||||
CYICON
|
||||
Dacl
|
||||
dataobject
|
||||
dcomp
|
||||
DERR
|
||||
dlldata
|
||||
DNE
|
||||
DONTADDTORECENT
|
||||
DWMSBT
|
||||
DWMWA
|
||||
DWMWA
|
||||
DWORDLONG
|
||||
endfor
|
||||
ENDSESSION
|
||||
enumset
|
||||
environstrings
|
||||
EXPCMDFLAGS
|
||||
EXPCMDSTATE
|
||||
filetime
|
||||
FILTERSPEC
|
||||
FORCEFILESYSTEM
|
||||
FORCEMINIMIZE
|
||||
frac
|
||||
fullkbd
|
||||
futex
|
||||
GETDESKWALLPAPER
|
||||
GETHIGHCONTRAST
|
||||
GETMOUSEHOVERTIME
|
||||
Hashtable
|
||||
HIGHCONTRASTON
|
||||
HIGHCONTRASTW
|
||||
hotkeys
|
||||
href
|
||||
hrgn
|
||||
HTCLOSE
|
||||
hwinsta
|
||||
HWINSTA
|
||||
IActivation
|
||||
IApp
|
||||
IAppearance
|
||||
IAsync
|
||||
IBind
|
||||
IBox
|
||||
IClass
|
||||
IComparable
|
||||
IComparer
|
||||
IConnection
|
||||
ICustom
|
||||
IDialog
|
||||
IDirect
|
||||
IExplorer
|
||||
IFACEMETHOD
|
||||
IFile
|
||||
IGraphics
|
||||
IInheritable
|
||||
IMap
|
||||
IMonarch
|
||||
IObject
|
||||
iosfwd
|
||||
IPackage
|
||||
IPeasant
|
||||
ISetup
|
||||
isspace
|
||||
IStorage
|
||||
istream
|
||||
IStringable
|
||||
ITab
|
||||
ITaskbar
|
||||
itow
|
||||
IUri
|
||||
IVirtual
|
||||
KEYSELECT
|
||||
LCID
|
||||
llabs
|
||||
llu
|
||||
localtime
|
||||
lround
|
||||
Lsa
|
||||
lsass
|
||||
LSHIFT
|
||||
LTGRAY
|
||||
MAINWINDOW
|
||||
memchr
|
||||
memicmp
|
||||
MENUCOMMAND
|
||||
MENUDATA
|
||||
MENUINFO
|
||||
MENUITEMINFOW
|
||||
mmeapi
|
||||
MOUSELEAVE
|
||||
mov
|
||||
mptt
|
||||
msappx
|
||||
MULTIPLEUSE
|
||||
NCHITTEST
|
||||
NCLBUTTONDBLCLK
|
||||
NCMOUSELEAVE
|
||||
NCMOUSEMOVE
|
||||
NCRBUTTONDBLCLK
|
||||
NIF
|
||||
NIN
|
||||
NOAGGREGATION
|
||||
NOASYNC
|
||||
NOCHANGEDIR
|
||||
NOPROGRESS
|
||||
NOREDIRECTIONBITMAP
|
||||
NOREPEAT
|
||||
NOTIFYBYPOS
|
||||
NOTIFYICON
|
||||
NOTIFYICONDATA
|
||||
ntprivapi
|
||||
oaidl
|
||||
ocidl
|
||||
ODR
|
||||
offsetof
|
||||
ofstream
|
||||
onefuzz
|
||||
osver
|
||||
OSVERSIONINFOEXW
|
||||
otms
|
||||
OUTLINETEXTMETRICW
|
||||
overridable
|
||||
PACL
|
||||
PAGESCROLL
|
||||
PATINVERT
|
||||
PEXPLICIT
|
||||
PICKFOLDERS
|
||||
pmr
|
||||
ptstr
|
||||
QUERYENDSESSION
|
||||
rcx
|
||||
REGCLS
|
||||
RETURNCMD
|
||||
rfind
|
||||
ROOTOWNER
|
||||
roundf
|
||||
RSHIFT
|
||||
SACL
|
||||
schandle
|
||||
semver
|
||||
serializer
|
||||
SETVERSION
|
||||
SHELLEXECUTEINFOW
|
||||
shobjidl
|
||||
SHOWHIDE
|
||||
SHOWMINIMIZED
|
||||
SHOWTIP
|
||||
SINGLEUSE
|
||||
SIZENS
|
||||
smoothstep
|
||||
snprintf
|
||||
spsc
|
||||
sregex
|
||||
SRWLOC
|
||||
SRWLOCK
|
||||
STDCPP
|
||||
STDMETHOD
|
||||
strchr
|
||||
strcpy
|
||||
streambuf
|
||||
strtoul
|
||||
Stubless
|
||||
Subheader
|
||||
Subpage
|
||||
syscall
|
||||
SYSTEMBACKDROP
|
||||
TABROW
|
||||
TASKBARCREATED
|
||||
TBPF
|
||||
THEMECHANGED
|
||||
tlg
|
||||
TME
|
||||
tmp
|
||||
tmpdir
|
||||
tolower
|
||||
toupper
|
||||
TRACKMOUSEEVENT
|
||||
TTask
|
||||
TVal
|
||||
UChar
|
||||
UFIELD
|
||||
ULARGE
|
||||
UOI
|
||||
UPDATEINIFILE
|
||||
userenv
|
||||
USEROBJECTFLAGS
|
||||
Viewbox
|
||||
virtualalloc
|
||||
wcsstr
|
||||
wcstoui
|
||||
winmain
|
||||
winsta
|
||||
winstamin
|
||||
wmemcmp
|
||||
wpc
|
||||
WSF
|
||||
wsregex
|
||||
wwinmain
|
||||
xchg
|
||||
XDocument
|
||||
XElement
|
||||
xfacet
|
||||
xhash
|
||||
XIcon
|
||||
xiosbase
|
||||
xlocale
|
||||
xlocbuf
|
||||
xlocinfo
|
||||
xlocmes
|
||||
xlocmon
|
||||
xlocnum
|
||||
xloctime
|
||||
XMax
|
||||
xmemory
|
||||
XParse
|
||||
xpath
|
||||
xstddef
|
||||
xstring
|
||||
xtree
|
||||
xutility
|
||||
YIcon
|
||||
YMax
|
||||
117
.github/actions/spelling/allow/colors.txt
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
alice
|
||||
aliceblue
|
||||
antiquewhite
|
||||
blanchedalmond
|
||||
blueviolet
|
||||
burlywood
|
||||
cadetblue
|
||||
cornflowerblue
|
||||
cornsilk
|
||||
cyan
|
||||
darkblue
|
||||
darkcyan
|
||||
darkgoldenrod
|
||||
darkgray
|
||||
darkgreen
|
||||
darkgrey
|
||||
darkkhaki
|
||||
darkmagenta
|
||||
darkolivegreen
|
||||
darkorange
|
||||
darkorchid
|
||||
darkred
|
||||
darksalmon
|
||||
darkseagreen
|
||||
darkslateblue
|
||||
darkslategray
|
||||
darkslategrey
|
||||
darkturquoise
|
||||
darkviolet
|
||||
deeppink
|
||||
deepskyblue
|
||||
dimgray
|
||||
dimgrey
|
||||
dodgerblue
|
||||
firebrick
|
||||
floralwhite
|
||||
forestgreen
|
||||
gainsboro
|
||||
ghostwhite
|
||||
greenyellow
|
||||
hotpink
|
||||
indian
|
||||
indianred
|
||||
lavenderblush
|
||||
lawngreen
|
||||
lemonchiffon
|
||||
lightblue
|
||||
lightcoral
|
||||
lightcyan
|
||||
lightgoldenrod
|
||||
lightgoldenrodyellow
|
||||
lightgray
|
||||
lightgreen
|
||||
lightgrey
|
||||
lightpink
|
||||
lightsalmon
|
||||
lightseagreen
|
||||
lightskyblue
|
||||
lightslateblue
|
||||
lightslategray
|
||||
lightslategrey
|
||||
lightsteelblue
|
||||
lightyellow
|
||||
limegreen
|
||||
mediumaquamarine
|
||||
mediumblue
|
||||
mediumorchid
|
||||
mediumpurple
|
||||
mediumseagreen
|
||||
mediumslateblue
|
||||
mediumspringgreen
|
||||
mediumturquoise
|
||||
mediumvioletred
|
||||
midnightblue
|
||||
mintcream
|
||||
mistyrose
|
||||
navajo
|
||||
navajowhite
|
||||
navyblue
|
||||
oldlace
|
||||
olivedrab
|
||||
orangered
|
||||
palegoldenrod
|
||||
palegreen
|
||||
paleturquoise
|
||||
palevioletred
|
||||
papayawhip
|
||||
peachpuff
|
||||
peru
|
||||
powderblue
|
||||
rebecca
|
||||
rebeccapurple
|
||||
rosybrown
|
||||
royalblue
|
||||
saddlebrown
|
||||
sandybrown
|
||||
seagreen
|
||||
sienna
|
||||
skyblue
|
||||
slateblue
|
||||
slategray
|
||||
slategrey
|
||||
springgreen
|
||||
steelblue
|
||||
violetred
|
||||
webgray
|
||||
webgreen
|
||||
webgrey
|
||||
webmaroon
|
||||
webpurple
|
||||
whitesmoke
|
||||
xaroon
|
||||
xray
|
||||
xreen
|
||||
xrey
|
||||
xurple
|
||||
yellowgreen
|
||||
@@ -1,8 +1,10 @@
|
||||
Consolas
|
||||
emoji
|
||||
emojis
|
||||
Extralight
|
||||
Gabriola
|
||||
Iosevka
|
||||
MDL
|
||||
Monofur
|
||||
Segoe
|
||||
wght
|
||||
11
.github/actions/spelling/allow/math.txt
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
atan
|
||||
CPrime
|
||||
HBar
|
||||
HPrime
|
||||
isnan
|
||||
LPrime
|
||||
LStep
|
||||
powf
|
||||
RSub
|
||||
sqrtf
|
||||
ULP
|
||||
85
.github/actions/spelling/allow/microsoft.txt
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
ACLs
|
||||
ADMINS
|
||||
advapi
|
||||
altform
|
||||
altforms
|
||||
appendwttlogging
|
||||
appx
|
||||
appxbundle
|
||||
appxerror
|
||||
appxmanifest
|
||||
ATL
|
||||
backplating
|
||||
bitmaps
|
||||
BOMs
|
||||
CPLs
|
||||
cpptools
|
||||
cppvsdbg
|
||||
CPRs
|
||||
cryptbase
|
||||
DACL
|
||||
DACLs
|
||||
defaultlib
|
||||
diffs
|
||||
disposables
|
||||
dotnetfeed
|
||||
DTDs
|
||||
DWINRT
|
||||
enablewttlogging
|
||||
Intelli
|
||||
IVisual
|
||||
libucrt
|
||||
libucrtd
|
||||
LKG
|
||||
LOCKFILE
|
||||
Lxss
|
||||
mfcribbon
|
||||
microsoft
|
||||
microsoftonline
|
||||
MSAA
|
||||
msixbundle
|
||||
MSVC
|
||||
MSVCP
|
||||
muxc
|
||||
netcore
|
||||
Onefuzz
|
||||
osgvsowi
|
||||
PFILETIME
|
||||
pgc
|
||||
pgo
|
||||
pgosweep
|
||||
powerrename
|
||||
powershell
|
||||
propkey
|
||||
pscustomobject
|
||||
QWORD
|
||||
regedit
|
||||
robocopy
|
||||
SACLs
|
||||
sdkddkver
|
||||
Shobjidl
|
||||
Skype
|
||||
SRW
|
||||
sxs
|
||||
Sysinternals
|
||||
sysnative
|
||||
systemroot
|
||||
taskkill
|
||||
tasklist
|
||||
tdbuildteamid
|
||||
ucrt
|
||||
ucrtd
|
||||
unvirtualized
|
||||
VCRT
|
||||
vcruntime
|
||||
Virtualization
|
||||
visualstudio
|
||||
vscode
|
||||
VSTHRD
|
||||
winsdkver
|
||||
wlk
|
||||
wslpath
|
||||
wtl
|
||||
wtt
|
||||
wttlog
|
||||
Xamarin
|
||||
@@ -1,43 +1,66 @@
|
||||
Anup
|
||||
austdi
|
||||
arkthur
|
||||
Ballmer
|
||||
bhoj
|
||||
Bhojwani
|
||||
Bluloco
|
||||
carlos
|
||||
dhowett
|
||||
Diviness
|
||||
dsafa
|
||||
duhowett
|
||||
DXP
|
||||
ekg
|
||||
eryksun
|
||||
ethanschoonover
|
||||
Firefox
|
||||
Gatta
|
||||
glsl
|
||||
Gravell
|
||||
Grie
|
||||
Griese
|
||||
Hernan
|
||||
Howett
|
||||
Illhardt
|
||||
iquilezles
|
||||
italo
|
||||
jantari
|
||||
jerrysh
|
||||
Kaiyu
|
||||
kimwalisch
|
||||
KMehrain
|
||||
KODELIFE
|
||||
Kodelife
|
||||
Kourosh
|
||||
kowalczyk
|
||||
leonmsft
|
||||
Lepilleur
|
||||
lhecker
|
||||
lukesampson
|
||||
Macbook
|
||||
Manandhar
|
||||
masserano
|
||||
mbadolato
|
||||
Mehrain
|
||||
menger
|
||||
mgravell
|
||||
michaelniksa
|
||||
michkap
|
||||
migrie
|
||||
mikegr
|
||||
mikemaccana
|
||||
miloush
|
||||
miniksa
|
||||
niksa
|
||||
nvaccess
|
||||
nvda
|
||||
oising
|
||||
oldnewthing
|
||||
opengl
|
||||
osgwiki
|
||||
pabhojwa
|
||||
panos
|
||||
paulcam
|
||||
pauldotknopf
|
||||
PGP
|
||||
@@ -45,11 +68,18 @@ Pham
|
||||
Rincewind
|
||||
rprichard
|
||||
Schoonover
|
||||
shadertoy
|
||||
Shomnipotence
|
||||
simioni
|
||||
Somuah
|
||||
sonph
|
||||
sonpham
|
||||
stakx
|
||||
talo
|
||||
thereses
|
||||
Walisch
|
||||
WDX
|
||||
Wellons
|
||||
Wirt
|
||||
Wojciech
|
||||
zadjii
|
||||
523
.github/actions/spelling/candidate.patterns
vendored
Normal file
@@ -0,0 +1,523 @@
|
||||
# marker to ignore all code on line
|
||||
^.*/\* #no-spell-check-line \*/.*$
|
||||
# marker for ignoring a comment to the end of the line
|
||||
// #no-spell-check.*$
|
||||
|
||||
# patch hunk comments
|
||||
^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .*
|
||||
# git index header
|
||||
index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
|
||||
|
||||
# cid urls
|
||||
(['"])cid:.*?\g{-1}
|
||||
|
||||
# data url in parens
|
||||
\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
|
||||
# data url in quotes
|
||||
([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
|
||||
# data url
|
||||
data:[-a-zA-Z=;:/0-9+]*,\S*
|
||||
|
||||
# mailto urls
|
||||
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
|
||||
|
||||
# magnet urls
|
||||
magnet:[?=:\w]+
|
||||
|
||||
# magnet urls
|
||||
"magnet:[^"]+"
|
||||
|
||||
# obs:
|
||||
"obs:[^"]*"
|
||||
|
||||
# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read
|
||||
# In this examples content, I'm using a number of different ways to match things to show various approaches
|
||||
# asciinema
|
||||
\basciinema\.org/a/[0-9a-zA-Z]+
|
||||
|
||||
# apple
|
||||
\bdeveloper\.apple\.com/[-\w?=/]+
|
||||
# Apple music
|
||||
\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+
|
||||
|
||||
# appveyor api
|
||||
\bci\.appveyor\.com/api/projects/status/[0-9a-z]+
|
||||
# appveyor project
|
||||
\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+
|
||||
|
||||
# Amazon
|
||||
|
||||
# Amazon
|
||||
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
|
||||
# AWS S3
|
||||
\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]*
|
||||
# AWS execute-api
|
||||
\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b
|
||||
# AWS ELB
|
||||
\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b
|
||||
# AWS SNS
|
||||
\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/&#%_?:=]*
|
||||
# AWS VPC
|
||||
vpc-\w+
|
||||
|
||||
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
|
||||
# YouTube url
|
||||
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
|
||||
# YouTube music
|
||||
\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*)
|
||||
# YouTube tag
|
||||
<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"]
|
||||
# YouTube image
|
||||
\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]*
|
||||
# Google Accounts
|
||||
\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]*
|
||||
# Google Analytics
|
||||
\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]*
|
||||
# Google APIs
|
||||
\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+
|
||||
# Google Storage
|
||||
\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|)
|
||||
# Google Calendar
|
||||
\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+
|
||||
\w+\@group\.calendar\.google\.com\b
|
||||
# Google DataStudio
|
||||
\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|)
|
||||
# The leading `/` here is as opposed to the `\b` above
|
||||
# ... a short way to match `https://` or `http://` since most urls have one of those prefixes
|
||||
# Google Docs
|
||||
/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|))
|
||||
# Google Drive
|
||||
\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
|
||||
# Google Groups
|
||||
\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)*
|
||||
# Google Maps
|
||||
\bmaps\.google\.com/maps\?[\w&;=]*
|
||||
# Google themes
|
||||
themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
|
||||
# Google CDN
|
||||
\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]*
|
||||
# Goo.gl
|
||||
/goo\.gl/[a-zA-Z0-9]+
|
||||
# Google Chrome Store
|
||||
\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|)
|
||||
# Google Books
|
||||
\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=&#.]*
|
||||
# Google Fonts
|
||||
\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]*
|
||||
# Google Forms
|
||||
\bforms\.gle/\w+
|
||||
# Google Scholar
|
||||
\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+
|
||||
# Google Colab Research Drive
|
||||
\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]*
|
||||
|
||||
# GitHub SHAs (api)
|
||||
\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b
|
||||
# GitHub SHAs (markdown)
|
||||
(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
|
||||
# GitHub SHAs
|
||||
\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
|
||||
# GitHub wiki
|
||||
\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
|
||||
# githubusercontent
|
||||
/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
|
||||
# githubassets
|
||||
\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+)
|
||||
# gist github
|
||||
\bgist\.github\.com/[^/\s"]+/[0-9a-f]+
|
||||
# git.io
|
||||
\bgit\.io/[0-9a-zA-Z]+
|
||||
# GitHub JSON
|
||||
"node_id": "[-a-zA-Z=;:/0-9+]*"
|
||||
# Contributor
|
||||
\[[^\]]+\]\(https://github\.com/[^/\s"]+\)
|
||||
# GHSA
|
||||
GHSA(?:-[0-9a-z]{4}){3}
|
||||
|
||||
# GitLab commit
|
||||
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
|
||||
# GitLab merge requests
|
||||
\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b
|
||||
# GitLab uploads
|
||||
\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]*
|
||||
# GitLab commits
|
||||
\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
|
||||
|
||||
# binanace
|
||||
accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
|
||||
|
||||
# bitbucket diff
|
||||
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
|
||||
# bitbucket repositories commits
|
||||
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
|
||||
# bitbucket commits
|
||||
\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
|
||||
|
||||
# bit.ly
|
||||
\bbit\.ly/\w+
|
||||
|
||||
# bitrise
|
||||
\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]*
|
||||
|
||||
# bootstrapcdn.com
|
||||
\bbootstrapcdn\.com/[-./\w]+
|
||||
|
||||
# cdn.cloudflare.com
|
||||
\bcdnjs\.cloudflare\.com/[./\w]+
|
||||
|
||||
# circleci
|
||||
\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+
|
||||
|
||||
# gitter
|
||||
\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+
|
||||
|
||||
# gravatar
|
||||
\bgravatar\.com/avatar/[0-9a-f]+
|
||||
|
||||
# ibm
|
||||
[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]*
|
||||
|
||||
# imgur
|
||||
\bimgur\.com/[^.]+
|
||||
|
||||
# Internet Archive
|
||||
\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*)
|
||||
|
||||
# discord
|
||||
/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,}
|
||||
|
||||
# Disqus
|
||||
\bdisqus\.com/[-\w/%.()!?&=_]*
|
||||
|
||||
# medium link
|
||||
\blink\.medium\.com/[a-zA-Z0-9]+
|
||||
# medium
|
||||
\bmedium\.com/\@?[^/\s"]+/[-\w]+
|
||||
|
||||
# microsoft
|
||||
\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]*
|
||||
# powerbi
|
||||
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
|
||||
# vs devops
|
||||
\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
|
||||
# microsoft store
|
||||
\bmicrosoft\.com/store/apps/\w+
|
||||
|
||||
# mvnrepository.com
|
||||
\bmvnrepository\.com/[-0-9a-z./]+
|
||||
|
||||
# now.sh
|
||||
/[0-9a-z-.]+\.now\.sh\b
|
||||
|
||||
# oracle
|
||||
\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]*
|
||||
|
||||
# chromatic.com
|
||||
/\S+.chromatic.com\S*[")]
|
||||
|
||||
# codacy
|
||||
\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+
|
||||
|
||||
# compai
|
||||
\bcompai\.pub/v1/png/[0-9a-f]+
|
||||
|
||||
# mailgun api
|
||||
\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]*
|
||||
# mailgun
|
||||
\b[0-9a-z]+.mailgun.org
|
||||
|
||||
# /message-id/
|
||||
/message-id/[-\w@./%]+
|
||||
|
||||
# Reddit
|
||||
\breddit\.com/r/[/\w_]*
|
||||
|
||||
# requestb.in
|
||||
\brequestb\.in/[0-9a-z]+
|
||||
|
||||
# sched
|
||||
\b[a-z0-9]+\.sched\.com\b
|
||||
|
||||
# Slack url
|
||||
slack://[a-zA-Z0-9?&=]+
|
||||
# Slack
|
||||
\bslack\.com/[-0-9a-zA-Z/_~?&=.]*
|
||||
# Slack edge
|
||||
\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+
|
||||
# Slack images
|
||||
\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+
|
||||
|
||||
# shields.io
|
||||
\bshields\.io/[-\w/%?=&.:+;,]*
|
||||
|
||||
# stackexchange -- https://stackexchange.com/feeds/sites
|
||||
\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)
|
||||
|
||||
# Sentry
|
||||
[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b
|
||||
|
||||
# Twitter markdown
|
||||
\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
|
||||
# Twitter hashtag
|
||||
\btwitter\.com/hashtag/[\w?_=&]*
|
||||
# Twitter status
|
||||
\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)
|
||||
# Twitter profile images
|
||||
\btwimg\.com/profile_images/[_\w./]*
|
||||
# Twitter media
|
||||
\btwimg\.com/media/[-_\w./?=]*
|
||||
# Twitter link shortened
|
||||
\bt\.co/\w+
|
||||
|
||||
# facebook
|
||||
\bfburl\.com/[0-9a-z_]+
|
||||
# facebook CDN
|
||||
\bfbcdn\.net/[\w/.,]*
|
||||
# facebook watch
|
||||
\bfb\.watch/[0-9A-Za-z]+
|
||||
|
||||
# dropbox
|
||||
\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
|
||||
|
||||
# ipfs protocol
|
||||
ipfs://[0-9a-z]*
|
||||
# ipfs url
|
||||
/ipfs/[0-9a-z]*
|
||||
|
||||
# w3
|
||||
\bw3\.org/[-0-9a-zA-Z/#.]+
|
||||
|
||||
# loom
|
||||
\bloom\.com/embed/[0-9a-f]+
|
||||
|
||||
# regex101
|
||||
\bregex101\.com/r/[^/\s"]+/\d+
|
||||
|
||||
# figma
|
||||
\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+
|
||||
|
||||
# freecodecamp.org
|
||||
\bfreecodecamp\.org/[-\w/.]+
|
||||
|
||||
# image.tmdb.org
|
||||
\bimage\.tmdb\.org/[/\w.]+
|
||||
|
||||
# mermaid
|
||||
\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+
|
||||
|
||||
# Wikipedia
|
||||
\ben\.wikipedia\.org/wiki/[-\w%.#]+
|
||||
|
||||
# gitweb
|
||||
[^"\s]+/gitweb/\S+;h=[0-9a-f]+
|
||||
|
||||
# HyperKitty lists
|
||||
/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/
|
||||
|
||||
# lists
|
||||
/thread\.html/[^"\s]+
|
||||
|
||||
# list-management
|
||||
\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+
|
||||
|
||||
# kubectl.kubernetes.io/last-applied-configuration
|
||||
"kubectl.kubernetes.io/last-applied-configuration": ".*"
|
||||
|
||||
# pgp
|
||||
\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]*
|
||||
|
||||
# Spotify
|
||||
\bopen\.spotify\.com/embed/playlist/\w+
|
||||
|
||||
# Mastodon
|
||||
\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]*
|
||||
|
||||
# scastie
|
||||
\bscastie\.scala-lang\.org/[^/]+/\w+
|
||||
|
||||
# images.unsplash.com
|
||||
\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+
|
||||
|
||||
# pastebin
|
||||
\bpastebin\.com/[\w/]+
|
||||
|
||||
# heroku
|
||||
\b\w+\.heroku\.com/source/archive/\w+
|
||||
|
||||
# quip
|
||||
\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)?
|
||||
|
||||
# badgen.net
|
||||
\bbadgen\.net/badge/[^")\]'\s]+
|
||||
|
||||
# statuspage.io
|
||||
\w+\.statuspage\.io\b
|
||||
|
||||
# media.giphy.com
|
||||
\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+
|
||||
|
||||
# tinyurl
|
||||
\btinyurl\.com/\w+
|
||||
|
||||
# getopts
|
||||
\bgetopts\s+(?:"[^"]+"|'[^']+')
|
||||
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
|
||||
|
||||
# URL escaped characters
|
||||
\%[0-9A-F][A-F]
|
||||
# IPv6
|
||||
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
|
||||
# c99 hex digits (not the full format, just one I've seen)
|
||||
0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
|
||||
# Punycode
|
||||
\bxn--[-0-9a-z]+
|
||||
# sha
|
||||
sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
|
||||
# sha-... -- uses a fancy capture
|
||||
(['"]|")[0-9a-f]{40,}\g{-1}
|
||||
# hex runs
|
||||
\b[0-9a-fA-F]{16,}\b
|
||||
# hex in url queries
|
||||
=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
|
||||
# ssh
|
||||
(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,}
|
||||
|
||||
# PGP
|
||||
\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
|
||||
# GPG keys
|
||||
\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b
|
||||
# Well known gpg keys
|
||||
.well-known/openpgpkey/[\w./]+
|
||||
|
||||
# uuid:
|
||||
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
|
||||
# hex digits including css/html color classes:
|
||||
(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
|
||||
# integrity
|
||||
integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}"
|
||||
|
||||
# https://www.gnu.org/software/groff/manual/groff.html
|
||||
# man troff content
|
||||
\\f[BCIPR]
|
||||
# '
|
||||
\\\(aq
|
||||
|
||||
# .desktop mime types
|
||||
^MimeTypes?=.*$
|
||||
# .desktop localized entries
|
||||
^[A-Z][a-z]+\[[a-z]+\]=.*$
|
||||
# Localized .desktop content
|
||||
Name\[[^\]]+\]=.*
|
||||
|
||||
# IServiceProvider
|
||||
\bI(?=(?:[A-Z][a-z]{2,})+\b)
|
||||
|
||||
# crypt
|
||||
"\$2[ayb]\$.{56}"
|
||||
|
||||
# scrypt / argon
|
||||
\$(?:scrypt|argon\d+[di]*)\$\S+
|
||||
|
||||
# Input to GitHub JSON
|
||||
content: "[-a-zA-Z=;:/0-9+]*="
|
||||
|
||||
# Python stringprefix / binaryprefix
|
||||
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
|
||||
(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
|
||||
|
||||
# Regular expressions for (P|p)assword
|
||||
\([A-Z]\|[a-z]\)[a-z]+
|
||||
|
||||
# JavaScript regular expressions
|
||||
# javascript test regex
|
||||
/.*/[gim]*\.test\(
|
||||
# javascript match regex
|
||||
\.match\(/[^/\s"]*/[gim]*\s*
|
||||
# javascript match regex
|
||||
\.match\(/\\[b].*?/[gim]*\s*\)(?:;|$)
|
||||
# javascript regex
|
||||
^\s*/\\[b].*/[gim]*\s*(?:\)(?:;|$)|,$)
|
||||
# javascript replace regex
|
||||
\.replace\(/[^/\s"]*/[gim]*\s*,
|
||||
|
||||
# Go regular expressions
|
||||
regexp?\.MustCompile\(`[^`]*`\)
|
||||
|
||||
# sed regular expressions
|
||||
sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}
|
||||
|
||||
# go install
|
||||
go install(?:\s+[a-z]+\.[-@\w/.]+)+
|
||||
|
||||
# kubernetes pod status lists
|
||||
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
|
||||
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
|
||||
|
||||
# kubectl - pods in CrashLoopBackOff
|
||||
\w+-[0-9a-f]+-\w+\s+\d+/\d+\s+CrashLoopBackOff\s+
|
||||
|
||||
# kubernetes object suffix
|
||||
-[0-9a-f]{10}-\w{5}\s
|
||||
|
||||
# posthog secrets
|
||||
posthog\.init\((['"])phc_[^"',]+\g{-1},
|
||||
|
||||
# xcode
|
||||
|
||||
# xcodeproject scenes
|
||||
(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}"
|
||||
|
||||
# xcode api botches
|
||||
customObjectInstantitationMethod
|
||||
|
||||
# font awesome classes
|
||||
\.fa-[-a-z0-9]+
|
||||
|
||||
# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
|
||||
# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
|
||||
# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
|
||||
## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
|
||||
## You could manually change `(?i)X...` to use `[Xx]...`
|
||||
## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
|
||||
# Lorem
|
||||
(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*
|
||||
|
||||
# Non-English
|
||||
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
|
||||
|
||||
# French
|
||||
# This corpus only had capital letters, but you probably want lowercase ones as well.
|
||||
\b[LN]'+[a-z]{2,}\b
|
||||
|
||||
# latex
|
||||
\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
|
||||
|
||||
# the negative lookahead here is to allow catching 'templatesz' as a misspelling
|
||||
# but to otherwise recognize a Windows path with \templates\foo.template or similar:
|
||||
\\(?:necessary|r(?:eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
|
||||
# ignore long runs of a single character:
|
||||
\b([A-Za-z])\g{-1}{3,}\b
|
||||
# Note that the next example is no longer necessary if you are using
|
||||
# to match a string starting with a `#`, use a character-class:
|
||||
[#]backwards
|
||||
# version suffix <word>v#
|
||||
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
|
||||
# Compiler flags (Scala)
|
||||
(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
# Compiler flags
|
||||
#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
|
||||
# Compiler flags (linker)
|
||||
,-B
|
||||
# curl arguments
|
||||
\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
|
||||
# set arguments
|
||||
\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)*
|
||||
# tar arguments
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
|
||||
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
|
||||
# macOS temp folders
|
||||
/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/
|
||||
117
.github/actions/spelling/excludes.txt
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
|
||||
(?:(?i)\.png$)
|
||||
(?:^|/)(?i)COPYRIGHT
|
||||
(?:^|/)(?i)LICEN[CS]E
|
||||
(?:^|/)3rdparty/
|
||||
(?:^|/)dirs$
|
||||
(?:^|/)go\.mod$
|
||||
(?:^|/)go\.sum$
|
||||
(?:^|/)package(?:-lock|)\.json$
|
||||
(?:^|/)sources(?:|\.dep)$
|
||||
(?:^|/)vendor/
|
||||
\.a$
|
||||
\.ai$
|
||||
\.avi$
|
||||
\.bmp$
|
||||
\.bz2$
|
||||
\.cer$
|
||||
\.class$
|
||||
\.crl$
|
||||
\.crt$
|
||||
\.csr$
|
||||
\.dll$
|
||||
\.docx?$
|
||||
\.drawio$
|
||||
\.DS_Store$
|
||||
\.eot$
|
||||
\.eps$
|
||||
\.exe$
|
||||
\.gif$
|
||||
\.gitattributes$
|
||||
\.graffle$
|
||||
\.gz$
|
||||
\.icns$
|
||||
\.ico$
|
||||
\.jar$
|
||||
\.jks$
|
||||
\.jpeg$
|
||||
\.jpg$
|
||||
\.key$
|
||||
\.lib$
|
||||
\.lock$
|
||||
\.map$
|
||||
\.min\..
|
||||
\.mod$
|
||||
\.mp3$
|
||||
\.mp4$
|
||||
\.o$
|
||||
\.ocf$
|
||||
\.otf$
|
||||
\.pbxproj$
|
||||
\.pdf$
|
||||
\.pem$
|
||||
\.png$
|
||||
\.psd$
|
||||
\.pyc$
|
||||
\.runsettings$
|
||||
\.s$
|
||||
\.sig$
|
||||
\.so$
|
||||
\.svg$
|
||||
\.svgz$
|
||||
\.svgz?$
|
||||
\.tar$
|
||||
\.tgz$
|
||||
\.tiff?$
|
||||
\.ttf$
|
||||
\.vsdx$
|
||||
\.wav$
|
||||
\.webm$
|
||||
\.webp$
|
||||
\.woff
|
||||
\.woff2?$
|
||||
\.xcf$
|
||||
\.xls
|
||||
\.xlsx?$
|
||||
\.xpm$
|
||||
\.yml$
|
||||
\.zip$
|
||||
^\.github/actions/spelling/
|
||||
^\.github/fabricbot.json$
|
||||
^\.gitignore$
|
||||
^\Q.git-blame-ignore-revs\E$
|
||||
^\Q.github/workflows/spelling.yml\E$
|
||||
^\Qdoc/reference/windows-terminal-logo.ans\E$
|
||||
^\Qsamples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters\E$
|
||||
^\Qsrc/host/exe/Host.EXE.vcxproj.filters\E$
|
||||
^\Qsrc/host/ft_host/chafa.txt\E$
|
||||
^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$
|
||||
^\XamlStyler.json$
|
||||
^build/config/
|
||||
^consolegit2gitfilters\.json$
|
||||
^dep/
|
||||
^doc/reference/master-sequence-list.csv$
|
||||
^doc/reference/UTF8-torture-test\.txt$
|
||||
^oss/
|
||||
^src/host/ft_uia/run\.bat$
|
||||
^src/host/runft\.bat$
|
||||
^src/host/runut\.bat$
|
||||
^src/interactivity/onecore/BgfxEngine\.
|
||||
^src/renderer/atlas/
|
||||
^src/renderer/wddmcon/WddmConRenderer\.
|
||||
^src/terminal/adapter/ut_adapter/run\.bat$
|
||||
^src/terminal/parser/delfuzzpayload\.bat$
|
||||
^src/terminal/parser/ft_fuzzer/run\.bat$
|
||||
^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$
|
||||
^src/terminal/parser/ft_fuzzwrapper/run\.bat$
|
||||
^src/terminal/parser/ut_parser/Base64Test.cpp$
|
||||
^src/terminal/parser/ut_parser/run\.bat$
|
||||
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
|
||||
^src/tools/lnkd/lnkd\.bat$
|
||||
^src/tools/pixels/pixels\.bat$
|
||||
^src/tools/texttests/fira\.txt$
|
||||
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
|
||||
^src/types/ut_types/UtilsTests.cpp$
|
||||
^tools/ReleaseEngineering/ServicingPipeline.ps1$
|
||||
ignore$
|
||||
SUMS$
|
||||
@@ -1,21 +1,31 @@
|
||||
abcde
|
||||
abcdef
|
||||
ABCDEFG
|
||||
ABCDEFGH
|
||||
AAAa
|
||||
AAAAA
|
||||
AAAAAAAAAAAAA
|
||||
AAAAAABBBBBBCCC
|
||||
AAAAABBBBBBCCC
|
||||
abcd
|
||||
abcd
|
||||
ABCDEFGHIJ
|
||||
abcdefghijk
|
||||
ABCDEFGHIJKLMNO
|
||||
abcdefghijklmnop
|
||||
ABCDEFGHIJKLMNOPQRST
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
ABCG
|
||||
ABE
|
||||
BBBBBBBBBBBBBBDDDD
|
||||
abf
|
||||
BBBBB
|
||||
BBBBBBBB
|
||||
BBBBBCCC
|
||||
BBBBCCCCC
|
||||
BBGGRR
|
||||
EFG
|
||||
EFGh
|
||||
QQQQQQQQQQABCDEFGHIJ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
|
||||
qrstuvwxyz
|
||||
qwerty
|
||||
QWERTYUIOP
|
||||
qwertyuiopasdfg
|
||||
YYYYYYYDDDDDDDDDDD
|
||||
ZAAZZ
|
||||
@@ -27,3 +37,4 @@ ZYXWVUT
|
||||
ZZBBZ
|
||||
ZZZBB
|
||||
ZZZBZ
|
||||
ZZZZZ
|
||||
6
.github/actions/spelling/expect/web.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
WCAG
|
||||
winui
|
||||
appshellintegration
|
||||
mdtauk
|
||||
gfycat
|
||||
Guake
|
||||
62
.github/actions/spelling/line_forbidden.patterns
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere
|
||||
# \bm_data\b
|
||||
|
||||
# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
|
||||
# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want
|
||||
# to use this:
|
||||
#\bfit\(
|
||||
|
||||
# s.b. GitHub
|
||||
\bGithub\b
|
||||
|
||||
# s.b. GitLab
|
||||
\bGitlab\b
|
||||
|
||||
# s.b. JavaScript
|
||||
\bJavascript\b
|
||||
|
||||
# s.b. Microsoft
|
||||
\bMicroSoft\b
|
||||
|
||||
# s.b. another
|
||||
\ban[- ]other\b
|
||||
|
||||
# s.b. greater than
|
||||
\bgreater then\b
|
||||
|
||||
# s.b. into
|
||||
#\sin to\s
|
||||
|
||||
# s.b. opt-in
|
||||
\sopt in\s
|
||||
|
||||
# s.b. less than
|
||||
\bless then\b
|
||||
|
||||
# s.b. otherwise
|
||||
\bother[- ]wise\b
|
||||
|
||||
# s.b. nonexistent
|
||||
\bnon existing\b
|
||||
\b[Nn]o[nt][- ]existent\b
|
||||
|
||||
# s.b. preexisting
|
||||
[Pp]re[- ]existing
|
||||
|
||||
# s.b. preempt
|
||||
[Pp]re[- ]empt\b
|
||||
|
||||
# s.b. preemptively
|
||||
[Pp]re[- ]emptively
|
||||
|
||||
# s.b. reentrancy
|
||||
[Rr]e[- ]entrancy
|
||||
|
||||
# s.b. reentrant
|
||||
[Rr]e[- ]entrant
|
||||
|
||||
# s.b. workaround(s)
|
||||
#\bwork[- ]arounds?\b
|
||||
|
||||
# Reject duplicate words
|
||||
\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s
|
||||
96
.github/actions/spelling/patterns/patterns.txt
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
|
||||
|
||||
https?://\S+
|
||||
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
|
||||
(?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|</UniqueIdentifier)
|
||||
(?:0[Xx]|\\x|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]{0,2}\b
|
||||
microsoft/cascadia-code\@[0-9a-fA-F]{40}
|
||||
\d+x\d+Logo
|
||||
Scro\&ll
|
||||
# selectionInput.cpp
|
||||
:\\windows\\syste\b
|
||||
TestUtils::VerifyExpectedString\(tb, L"[^"]+"
|
||||
(?:hostSm|mach)\.ProcessString\(L"[^"]+"
|
||||
\b([A-Za-z])\g{-1}{3,}\b
|
||||
0x[0-9A-Za-z]+
|
||||
Base64::s_(?:En|De)code\(L"[^"]+"
|
||||
VERIFY_ARE_EQUAL\(L"[^"]+"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/"
|
||||
std::memory_order_[\w]+
|
||||
D2DERR_SHADER_COMPILE_FAILED
|
||||
TIL_FEATURE_[0-9A-Z_]+
|
||||
vcvars\w*
|
||||
ROY\sG\.\sBIV
|
||||
!(?:(?i)ESC)!\[
|
||||
!(?:(?i)CSI)!(?:\d+(?:;\d+|)m|[ABCDF])
|
||||
|
||||
# Python stringprefix / binaryprefix
|
||||
\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'
|
||||
|
||||
# Automatically suggested patterns
|
||||
# hit-count: 3831 file-count: 582
|
||||
# IServiceProvider
|
||||
\bI(?=(?:[A-Z][a-z]{2,})+\b)
|
||||
|
||||
# hit-count: 71 file-count: 35
|
||||
# Compiler flags
|
||||
(?:^|[\t ,"'`=(])-[D](?=[A-Z]{2,}|[A-Z][a-z])
|
||||
(?:^|[\t ,"'`=(])-[X](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
|
||||
# hit-count: 41 file-count: 28
|
||||
# version suffix <word>v#
|
||||
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
|
||||
|
||||
# hit-count: 20 file-count: 9
|
||||
# hex runs
|
||||
\b[0-9a-fA-F]{16,}\b
|
||||
|
||||
# hit-count: 10 file-count: 7
|
||||
# uuid:
|
||||
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
|
||||
|
||||
# hit-count: 4 file-count: 4
|
||||
# mailto urls
|
||||
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
|
||||
|
||||
# hit-count: 4 file-count: 1
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
|
||||
|
||||
# hit-count: 2 file-count: 1
|
||||
# latex
|
||||
\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
|
||||
|
||||
# hit-count: 1 file-count: 1
|
||||
# hex digits including css/html color classes:
|
||||
(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
|
||||
|
||||
# hit-count: 1 file-count: 1
|
||||
# Non-English
|
||||
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
|
||||
|
||||
# hit-count: 1 file-count: 1
|
||||
# French
|
||||
# This corpus only had capital letters, but you probably want lowercase ones as well.
|
||||
\b[LN]'+[a-z]{2,}\b
|
||||
|
||||
# acceptable duplicates
|
||||
# ls directory listings
|
||||
[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+
|
||||
# C/idl types + English ...
|
||||
\s(Guid|long|LONG|that) \g{-1}\s
|
||||
|
||||
# javadoc / .net
|
||||
(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s
|
||||
|
||||
# Commit message -- Signed-off-by and friends
|
||||
^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$
|
||||
|
||||
# Autogenerated revert commit message
|
||||
^This reverts commit [0-9a-f]{40}\.$
|
||||
|
||||
# vtmode
|
||||
--vtmode\s+(\w+)\s+\g{-1}\s
|
||||
|
||||
# ignore long runs of a single character:
|
||||
\b([A-Za-z])\g{-1}{3,}\b
|
||||
12
.github/actions/spelling/reject.txt
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
^attache$
|
||||
^attacher$
|
||||
^attachers$
|
||||
benefitting
|
||||
occurences?
|
||||
^dependan.*
|
||||
^oer$
|
||||
Sorce
|
||||
^[Ss]pae.*
|
||||
^untill$
|
||||
^untilling$
|
||||
^wether.*
|
||||
20
.github/workflows/spelling.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Spell checking
|
||||
on:
|
||||
push:
|
||||
schedule:
|
||||
# * is a special character in YAML so you have to quote this string
|
||||
- cron: '15 * * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Spell checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.0.0
|
||||
with:
|
||||
fetch-depth: 5
|
||||
- uses: check-spelling/check-spelling@0.0.16-alpha
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
bucket: .github/actions
|
||||
project: spell-check
|
||||
134
.github/workflows/spelling2.yml
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
# spelling.yml is blocked per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p
|
||||
name: Spell checking
|
||||
|
||||
# Comment management is handled through a secondary job, for details see:
|
||||
# https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions
|
||||
#
|
||||
# `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment
|
||||
# (in odd cases, it might actually run just to collapse a commment, but that's fairly rare)
|
||||
# it needs `contents: write` in order to add a comment.
|
||||
#
|
||||
# `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment
|
||||
# or collapse a comment (in the case where it had previously made a comment and now no longer needs to show a comment)
|
||||
# it needs `pull-requests: write` in order to manipulate those comments.
|
||||
|
||||
# Updating pull request branches is managed via comment handling.
|
||||
# For details, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-expect-list
|
||||
#
|
||||
# These elements work together to make it happen:
|
||||
#
|
||||
# `on.issue_comment`
|
||||
# This event listens to comments by users asking to update the metadata.
|
||||
#
|
||||
# `jobs.update`
|
||||
# This job runs in response to an issue_comment and will push a new commit
|
||||
# to update the spelling metadata.
|
||||
#
|
||||
# `with.experimental_apply_changes_via_bot`
|
||||
# Tells the action to support and generate messages that enable it
|
||||
# to make a commit to update the spelling metadata.
|
||||
#
|
||||
# `with.ssh_key`
|
||||
# In order to trigger workflows when the commit is made, you can provide a
|
||||
# secret (typically, a write-enabled github deploy key).
|
||||
#
|
||||
# For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
tags-ignore:
|
||||
- "**"
|
||||
pull_request_target:
|
||||
branches:
|
||||
- "**"
|
||||
tags-ignore:
|
||||
- "**"
|
||||
types:
|
||||
- 'opened'
|
||||
- 'reopened'
|
||||
- 'synchronize'
|
||||
issue_comment:
|
||||
types:
|
||||
- 'created'
|
||||
|
||||
jobs:
|
||||
spelling:
|
||||
name: Spell checking
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
actions: read
|
||||
outputs:
|
||||
followup: ${{ steps.spelling.outputs.followup }}
|
||||
runs-on: ubuntu-latest
|
||||
if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'"
|
||||
concurrency:
|
||||
group: spelling-${{ github.event.pull_request.number || github.ref }}
|
||||
# note: If you use only_check_changed_files, you do not want cancel-in-progress
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: check-spelling
|
||||
id: spelling
|
||||
uses: check-spelling/check-spelling@v0.0.21
|
||||
with:
|
||||
suppress_push_for_open_pull_request: 1
|
||||
checkout: true
|
||||
check_file_names: 1
|
||||
spell_check_this: check-spelling/spell-check-this@prerelease
|
||||
post_comment: 0
|
||||
use_magic_file: 1
|
||||
extra_dictionary_limit: 10
|
||||
extra_dictionaries:
|
||||
cspell:software-terms/src/software-terms.txt
|
||||
cspell:python/src/python/python-lib.txt
|
||||
cspell:node/node.txt
|
||||
cspell:cpp/src/stdlib-c.txt
|
||||
cspell:cpp/src/stdlib-cpp.txt
|
||||
cspell:fullstack/fullstack.txt
|
||||
cspell:filetypes/filetypes.txt
|
||||
cspell:html/html.txt
|
||||
cspell:cpp/src/compiler-msvc.txt
|
||||
cspell:python/src/common/extra.txt
|
||||
cspell:powershell/powershell.txt
|
||||
cspell:aws/aws.txt
|
||||
cspell:cpp/src/lang-keywords.txt
|
||||
cspell:npm/npm.txt
|
||||
cspell:dotnet/dotnet.txt
|
||||
cspell:python/src/python/python.txt
|
||||
cspell:css/css.txt
|
||||
cspell:cpp/src/stdlib-cmath.txt
|
||||
check_extra_dictionaries: ''
|
||||
|
||||
comment-push:
|
||||
name: Report (Push)
|
||||
# If your workflow isn't running on push, you can remove this job
|
||||
runs-on: ubuntu-latest
|
||||
needs: spelling
|
||||
permissions:
|
||||
contents: write
|
||||
if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push'
|
||||
steps:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@v0.0.21
|
||||
with:
|
||||
checkout: true
|
||||
spell_check_this: check-spelling/spell-check-this@prerelease
|
||||
task: ${{ needs.spelling.outputs.followup }}
|
||||
|
||||
comment-pr:
|
||||
name: Report (PR)
|
||||
# If you workflow isn't running on pull_request*, you can remove this job
|
||||
runs-on: ubuntu-latest
|
||||
needs: spelling
|
||||
permissions:
|
||||
pull-requests: write
|
||||
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
|
||||
steps:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@v0.0.21
|
||||
with:
|
||||
checkout: true
|
||||
spell_check_this: check-spelling/spell-check-this@prerelease
|
||||
task: ${{ needs.spelling.outputs.followup }}
|
||||
@@ -5,7 +5,7 @@
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>1</VersionMinor>
|
||||
<VersionMinor>2</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
"switchToTab",
|
||||
"toggleFullscreen",
|
||||
"find",
|
||||
"setTabColor",
|
||||
"openTabColorPicker",
|
||||
"unbound"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -257,6 +259,22 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SetTabColorAction": {
|
||||
"description": "Arguments corresponding to a Set Tab Color Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "setTabColor" },
|
||||
"color": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": null,
|
||||
"description": "If provided, will set the tab's color to the given value. If omitted, will reset the tab's color."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -272,6 +290,7 @@
|
||||
{ "$ref": "#/definitions/ResizePaneAction" },
|
||||
{ "$ref": "#/definitions/SplitPaneAction" },
|
||||
{ "$ref": "#/definitions/OpenSettingsAction" },
|
||||
{ "$ref": "#/definitions/SetTabColorAction" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
doc/specs/#1502 - Advanced Tab Switcher/img/VSMinimumSize.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 345 KiB |
BIN
doc/specs/#1502 - Advanced Tab Switcher/img/VSTabSwitcher.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 34 KiB |
227
doc/specs/#1502 - Advanced Tab Switcher/spec.md
Normal file
@@ -0,0 +1,227 @@
|
||||
---
|
||||
author: Leon Liang @leonMSFT
|
||||
created on: 2019-11-27
|
||||
last updated: 2020-06-16
|
||||
issue id: 1502
|
||||
---
|
||||
|
||||
# Advanced Tab Switcher
|
||||
|
||||
## Abstract
|
||||
|
||||
Currently the user is able to cycle through tabs on the tab bar. However, this horizontal cycling can be pretty inconvenient when the tab titles are long or when there are too many tabs on the tab bar. It could also get hard to see all your available tabs if the tab titles are long and your screen is small. In addition, there's a common use case to quickly switch between two tabs, e.g. when one tab is used as reference and the other is the actively worked-on tab. If the tabs are not right next to each other on the tab bar, it could be difficult to quickly swap between the two. Having the tabs displayed in Most Recently Used (MRU) order would help with this problem. It could also make the user experience better when there are a handful of tabs that are frequently used, but are nowhere near each other on the tab bar.
|
||||
|
||||
Having a tab switcher UI, like the ones in Visual Studio and Visual Studio Code, could help with the tab experience. Presenting the tabs vertically in their own little UI allows the user to see more of the tabs at once, compared to scanning the tab row horizontally and scrolling left/right to find the tab you want. The tab order in those tab switchers are also in MRU order by default.
|
||||
|
||||
To try to alleviate some of these user scenarios, we want to create a tab switcher similar to the ones found in VSCode and VS. This spec will cover the design of the switcher, and how a user would interact with the switcher. It would be primarily keyboard driven, and would give a pop-up display of a vertical list of tabs. The tab switcher would also be able to display the tabs in Most Recently Used (MRU) order.
|
||||
|
||||
## Inspiration
|
||||
|
||||
This was mainly inspired by the tab switcher that's found in Visual Studio Code and Visual Studio.
|
||||
|
||||
VS Code's tab switcher appears directly underneath the tab bar.
|
||||
|
||||

|
||||
|
||||
Visual Studio's tab switcher presents itself as a box in the middle of the editor.
|
||||
|
||||

|
||||
|
||||
In terms of navigating the switcher, both VSCode and Visual Studio behave very similarly. Both open with the press of <kbd>ctrl+tab</kbd> and dismiss on release of <kbd>ctrl</kbd>. They both also allow the user to select the tab with the mouse and with <kbd>enter</kbd>. <kbd>esc</kbd> and a mouse click outside of the switcher both dismiss the window as well.
|
||||
|
||||
I'm partial towards looking like VSCode's Tab Switcher - specifically because it seems like both their Command Palette and Tab Switcher use the same UI. You can observe this by first bringing up the command palette, then hitting the keybinding to bring up the tab switcher. You'll notice that they're both using the same centered drop-down from the tab row. In fact, hitting the Tab Switcher keybinding in VSCode while the Command Palette is open simply auto fills the search box with "edit active", signifying that the user wants to select one of the tabs to edit, effectively "swapping" to the tab that's highlighted.
|
||||
|
||||
Since Terminal now has a command palette, it would be amazing to reuse that UI and simply fill it with the names of a user's currently open tabs!
|
||||
|
||||
## Solution Design
|
||||
|
||||
To extend upon the command palette, we simply need to create and maintain two Vector<Commands>, where each command will simply dispatch a `SwitchToTab` `ShortcutAction`. One vector will have the commands in tab row order, and the other will be in MRU order. They'll both have to be maintained along with our existing vector of tabs.
|
||||
|
||||
These vectors of commands can then be set as the commands to pull from in the command palette, and as long as the tab titles are available in these commands, the command palette will be able to naturally filter through the tabs as a user types in its search bar. Just like the command palette, a user will be able to navigate through the list of tabs with the arrow keys and pointer interactions. As part of this implementation, I can supplement these actions with "tab switcher specific" navigation keybindings that would only work if the command palette is in tab switcher mode.
|
||||
|
||||
The `TabSwitcherControl` will use `TerminalPage`'s `ShortcutActionDispatch` to dispatch a `SwitchToTab` `ShortcutAction`. This will eventually cause `TerminalPage::_OnTabSelectionChanged` to be called. We can update the MRU in this function to be sure that changing tabs from the TabSwitcher, clicking on a tab, or nextTab/prevTab-ing will keep the MRU up-to-date. Adding or closing tabs are handled in `_OpenNewTab` and `_CloseFocusedTab`, which will need to be modified to update the command vectors.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
The Tab Switcher will reuse a lot of the XAML code that's used in the command palette. This means it'll show up as a drop-down from the horizontal center of the tab row. It'll appear as a single overlay over the whole Terminal window. There will also be a search box on top of the list of tabs. Here's a rough mockup of how the command palette/tab switcher looks like:
|
||||
|
||||

|
||||
|
||||
Each entry in the list will show the tab's titles and their assigned number for quick switching, and only one line will be highlighted to signify the tab that is currently selected. The top 9 tabs in the list are numbered for quick switching, and the rest of the tabs will simply have an empty space where a number would be.
|
||||
|
||||
The list would look (roughly) like this:
|
||||
```
|
||||
1 foo (highlighted)
|
||||
2 boo
|
||||
3 Windows
|
||||
4 /c/Users/booboo
|
||||
5 Git Moo
|
||||
6 shoo
|
||||
7 /c/
|
||||
8 /d/
|
||||
9 /e/
|
||||
/f/
|
||||
/g/
|
||||
/h/
|
||||
```
|
||||
|
||||
The highlighted line can move up or down, and if the user moves up while the highlighted line is already at the top of the list, the highlight will wrap around to the bottom of the list. Similarly, it will wrap to the top if the highlight is at the bottom of the list and the user moves down.
|
||||
|
||||
If there's more tabs than the UI can display, the list of tabs will scroll up/down as the user keeps iterating up/down. Even if some of the numbered tabs (the first 9 tabs) are not visible, the user can still press any number 1 through 9 to quick switch to that tab.
|
||||
|
||||
To give an example of what happens after scrolling past the end, imagine a user is starting from the state in the mock above. The user then iterates down past the end of the visible list four times. The below mock shows the result.
|
||||
|
||||
```
|
||||
5 Git Moo
|
||||
6 shoo
|
||||
7 /c/
|
||||
8 /d
|
||||
9 /e/
|
||||
/f/
|
||||
/g/
|
||||
/h/
|
||||
/i/
|
||||
/j/
|
||||
/k/
|
||||
/l/ (highlighted)
|
||||
```
|
||||
|
||||
The tabs designated by numbers 1 through 4 are no longer visible (but still quick-switchable), and the list now starts with "Git Moo", which is associated with number 5.
|
||||
|
||||
### Using the Switcher
|
||||
|
||||
#### Opening the Tab Switcher
|
||||
|
||||
The user can press a keybinding named `tabSwitcher` to bring up the command palette UI with a list of tab titles.
|
||||
The user can also bring up the command palette first, and type a "tab switcher" prefix like "@" into the search bar to switch into "tab switcher mode".
|
||||
The user will be able to change it to whatever they like.
|
||||
There will also be an optional `anchor` arg that may be provided to this keybinding.
|
||||
|
||||
#### Keeping it open
|
||||
|
||||
We use the term `anchor` to illustrate the idea that the UI stays visible as long as something is "anchoring" it down.
|
||||
|
||||
Here's an example of how to set the `anchor` key in the settings:
|
||||
```
|
||||
{"keys": ["ctrl+tab"], "command": {"action": "openTabSwitcher", "anchor": "ctrl" }}
|
||||
```
|
||||
|
||||
This user provided the `anchor` key arg, and set it to <kbd>ctrl</kbd>. So, the user would open the UI with <kbd>ctrl+tab</kbd>, and as long as the user is holding <kbd>ctrl</kbd> down, the UI won't dismiss. The moment the user releases <kbd>ctrl</kbd>, the UI dismisses. The `anchor` key needs to be one of the keys in the `openTabSwitcher` keybinding. If it isn't, we'll display a warning dialog in this case saying that the `anchor` key isn't actually part of the keybinding, and the user might run into some weird behavior.
|
||||
|
||||
If `openTabSwitcher` is not given an `anchor` key, the switcher will stay visible even after the release of the keybinding.
|
||||
|
||||
#### Switching through Tabs
|
||||
|
||||
The user will be able to navigate through the switcher with the following keybindings:
|
||||
|
||||
- Switching Down: <kbd>tab</kbd> or <kbd>downArrow</kbd>
|
||||
- Switching Up: <kbd>shift+tab</kbd> or <kbd>upArrow</kbd>
|
||||
|
||||
As the user is cycling through the tab list, the selected tab will be highlighted but the terminal won't actually switch focus to the selected tab. This also applies to pointer interaction. Hovering over an item with a mouse will highlight the item but not switch to the tab.
|
||||
|
||||
#### Closing the Switcher and Bringing a Tab into Focus
|
||||
|
||||
There are two _dismissal_ keybindings:
|
||||
|
||||
1. <kbd>enter</kbd> : brings the currently selected tab into focus and dismisses the UI.
|
||||
2. <kbd>esc</kbd> : dismisses the UI without changing tab focus.
|
||||
|
||||
The following are ways a user can dismiss the UI, _whether or not_ the `Anchor` key is provided to `openTabSwitcher`.
|
||||
|
||||
1. The user can press a number associated with a tab to instantly switch to the tab and dismiss the switcher.
|
||||
2. The user can click on a tab to instantly switch to the tab and dismiss the switcher.
|
||||
3. The user can click outside of the UI to dismiss the switcher without bringing the selected tab into focus.
|
||||
4. The user can press any of the dismissal keybindings.
|
||||
|
||||
If the `anchor` key is provided, then in addition to the above methods, the UI will dismiss upon the release of the `anchor` key.
|
||||
|
||||
Pressing the `openTabSwitcher` keychord again will not close the switcher, it'll do nothing.
|
||||
|
||||
### Most Recently Used Order
|
||||
|
||||
We'll provide a setting that will allow the list of tabs to be presented in either _in-order_ (how the tabs are ordered on the tab bar), or _Most Recently Used Order_ (MRU). MRU means that the tab that the terminal most recently visited will be on the top of the list, and the tab that the terminal has not visited for the longest time will be on the bottom.
|
||||
|
||||
There will be an argument for the `openTabSwitcher` action called `displayOrder`. This can be either `inOrder` or `mruOrder`. Making the setting an argument passed into `openTabSwitcher` would allow the user to have one keybinding to open an MRU Tab Switcher, and different one for the In-Order Tab Switcher. For example:
|
||||
```
|
||||
{"keys": ["ctrl+tab"], "command": {"action": "openTabSwitcher", "anchor":"ctrl", "displayOrder":"mruOrder"}}
|
||||
{"keys": ["ctrl+shift+p"], "command": {"action": "openTabSwitcher", "anchor":"ctrl", "displayOrder":"inOrder"}}
|
||||
```
|
||||
By default (when the arg isn't specified), `displayOrder` will be "mruOrder".
|
||||
|
||||
### Numbered Tabs
|
||||
|
||||
Similar to how the user can currently switch to a particular tab with a combination of keys such as <kbd>ctrl+shift+1</kbd>, we want to have the tab switcher provide a number to the first nine tabs (1-9) in the list for quick switching. If there are more than nine tabs in the list, then the rest of the tabs will not have a number assigned.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
- The tab switcher will be using WinUI, and so it'll be automatically linked to the UIA tree. This allows screen readers to find it, and so narrator will be able to navigate the switcher easily.
|
||||
- The UI is also fully keyboard-driven, with the option of using a mouse to interact with the UI.
|
||||
- When the tab switcher pops up, the focus immediately swaps to it.
|
||||
- For the sake of more contrast with the background, we could use a ThemeShadow to bring the UI closer to the user, making the focus clearer.
|
||||
|
||||
### Security
|
||||
|
||||
This shouldn't introduce any security issues.
|
||||
|
||||
### Reliability
|
||||
|
||||
How we're updating the MRU is something to watch out for since it triggers on a lot of tab interactions. However, I don't foresee the update taking long at all, and I can't imagine that users can create and delete tabs fast enough to matter.
|
||||
|
||||
### Compatibility
|
||||
|
||||
- The existing way of navigating horizontally through the tabs on the tab bar should not break.
|
||||
- These should also be separate keybindings from the keybindings associated with using the tab switcher.
|
||||
- When a user reorders their tabs on the tab bar, the MRU order remains unchanged. For example:
|
||||
- Tab Bar:`[cmd(focused), ps, wsl]` and MRU:`[cmd, ps, wsl]`
|
||||
- Reordered Tab Bar:`[wsl, cmd(focused), ps]` and MRU:`[cmd, ps, wsl]`
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
## Potential Issues
|
||||
|
||||
We'll need to be careful about how the UI is presented depending on different sizes of the terminal. We also should test how the UI looks as it's open and resizing is happening. Visual Studio's tab switcher is a fixed size, and is always in the middle. Even when the VS window is smaller than the tab switcher size, the tab switcher will show up larger than the VS window itself.
|
||||
|
||||

|
||||

|
||||
|
||||
Visual Studio Code only allows the user to shrink the window until it hits a minimum width and height. This minimum width and height gives its tab switcher enough space to show a meaningful amount of information.
|
||||
|
||||

|
||||
|
||||
Terminal can't really replicate Visual Studio's version of the tab switcher in this situation. The TabSwitcher needs to be contained within the Terminal. So, if the TabSwitcher is always centered and has a percentage padding from the borders of the Terminal, it'll shrink as Terminal shrinks. Since the Terminal also has a minimum width, the switcher should always have enough space to be usefully visible.
|
||||
|
||||
## Future considerations
|
||||
|
||||
### Pane Navigation
|
||||
|
||||
There was discussion in [#1502] that brought up the idea of pane navigation, inspired by tmux.
|
||||
|
||||

|
||||
|
||||
Tmux allows the user to navigate directly to a pane and even give a preview of the pane. This would be extremely useful since it would allow the user to see a tree of their open tabs and panes. Currently there's no way to see what panes are open in each tab, so if you're looking for a particular pane, you'd need to cycle through your tabs to find it. If something like pane profile names (not sure what information to present in the switcher for panes) were presented in the TabSwitcher, the user could see all the panes in one box.
|
||||
|
||||
To support pane navigation, the tab switcher can simply have another column to the right of the tab list to show a list of panes inside the selected tab. As the user iterates through the tab list, they can simply hit right to dig deeper into the tab's panes, and hit left to come back to the tab list. Each tab's list of panes will be MRU or in-order, depending on which `displayOrder` arg was provided to the `openTabSwitcher` keybinding.
|
||||
|
||||
Pane navigation is a clear next step to build on top of the tab switcher, but this spec will specifically deal with just tab navigation in order to keep the scope tight. The tab switcher implementation just needs to allow for pane navigation to be added in later.
|
||||
|
||||
### Tab Preview on Hover
|
||||
|
||||
With this feature, having a tab highlighted in the switcher would make the Terminal display that tab as if it switched to it. I believe currently there is no way to set focus to a tab in a "preview" mode. This is important because MRU updates whenever a tab is focused, but we don't want the MRU to update on a preview. Given that this feature is a "nice thing to have", I'll leave it for
|
||||
after the tab switcher has landed.
|
||||
|
||||
## Resources
|
||||
|
||||
Feature Request: An advanced tab switcher [#1502]
|
||||
Ctrl+Tab toggle between last two windows like Alt+Tab [#973]
|
||||
The Command Palette Thread [#2046]
|
||||
The Command Palette Spec [#5674]
|
||||
Feature Request: Search [#605]
|
||||
|
||||
<!-- Footnotes -->
|
||||
[#605]: https://github.com/microsoft/terminal/issues/605
|
||||
[#973]: https://github.com/microsoft/terminal/issues/973
|
||||
[#1502]: https://github.com/microsoft/terminal/issues/1502
|
||||
[#2046]: https://github.com/microsoft/terminal/issues/2046
|
||||
[#5674]: https://github.com/microsoft/terminal/pull/5674
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2019-08-01
|
||||
last updated: 2020-06-10
|
||||
last updated: 2020-06-16
|
||||
issue id: 2046
|
||||
---
|
||||
|
||||
@@ -522,6 +522,13 @@ default. These are largely the actions that are bound by default.
|
||||
]
|
||||
```
|
||||
|
||||
## Addenda
|
||||
|
||||
This spec also has a follow-up spec which introduces further changes upon this
|
||||
original draft. Please also refer to:
|
||||
|
||||
* June 2020: Unified keybindings and commands, and synthesized action names.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* Commands will provide an easy point for allowing an extension to add its
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-06-15
|
||||
last updated: 2020-06-19
|
||||
issue id: 2046
|
||||
---
|
||||
|
||||
# Command Palette, Addendum 1 - Unified keybindings and commands, and synthesized action names
|
||||
|
||||
## Abstract
|
||||
|
||||
This document is intended to serve as an addition to the [Command Palette Spec].
|
||||
While that spec is complete in it's own right, subsequent discussion revealed
|
||||
additional ways to improve the functionality and usability of the command
|
||||
palette. This document builds largely on the topics already introduced in the
|
||||
original spec, so readers should first familiarize themselves with that
|
||||
document.
|
||||
|
||||
One point of note from the original document was that the original specification
|
||||
was entirely too verbose when defining both keybindings and commands for
|
||||
actions. Consider, for instance, a user that wants to bind the action "duplicate
|
||||
the current pane". In that spec, they need to add both a keybinding and a
|
||||
command:
|
||||
|
||||
```json
|
||||
{
|
||||
"keybindings": [
|
||||
{ "keys": [ "ctrl+alt+t" ], "command": { "action": "splitPane", "split":"auto", "splitMode": "duplicate" } },
|
||||
],
|
||||
"commands": [
|
||||
{ "name": "Duplicate Pane", "action": { "action": "splitPane", "split":"auto", "splitMode": "duplicate" }, "icon": null },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
These two entries are practically the same, except for two key differentiators:
|
||||
* the keybinding has a `keys` property, indicating which key chord activates the
|
||||
action.
|
||||
* The command has a `name` property, indicating what name to display for the
|
||||
command in the Command Palette.
|
||||
|
||||
What if the user didn't have to duplicate this action? What if the user could
|
||||
just add this action once, in their `keybindings` or `commands`, and have it
|
||||
work both as a keybinding AND a command?
|
||||
|
||||
|
||||
## Solution Design
|
||||
|
||||
This spec will outline two primary changes to keybindings and commands.
|
||||
1. Unify keybindings and commands, so both `keybindings` and `commands` can
|
||||
specify either actions bound to keys, and/or actions bound to entries in the
|
||||
Command Palette.
|
||||
2. Propose a mechanism by which actions do not _require_ a `name` to appear in
|
||||
the Command Palette.
|
||||
|
||||
These proposals are two atomic units - either could be approved or rejected
|
||||
independently of one another. They're presented together here in the same doc
|
||||
because together, they present a compelling story.
|
||||
|
||||
### Proposal 1: Unify Keybindings and Commands
|
||||
|
||||
As noted above, keybindings and commands have nearly the exact same syntax, save
|
||||
for a couple properties. To make things easier for the user, I'm proposing
|
||||
treating everything in _both_ the `keybindings` _and_ the `commands` arrays as
|
||||
**BOTH** a keybinding and a command.
|
||||
|
||||
Furthermore, as a change from the previous spec, we'll be using `bindings` from
|
||||
here on as the unified `keybindings` and `commands` lists. This is considering
|
||||
that we'll currently be using `bindings` for both commands and keybindings, but
|
||||
we'll potentially also have mouse & touch bindings in this array in the future.
|
||||
We'll "deprecate" the existing `keybindings` property, and begin to exclusively
|
||||
use `bindings` as the new property name. For compatibility reasons, we'll
|
||||
continue to parse `keybindings` in the same way we parse `bindings`. We'll
|
||||
simply layer `bindings` on top of the legacy `keybindings`.
|
||||
|
||||
* Anything entry that has a `keys` value will be added to the keybindings.
|
||||
Pressing that keybinding will activate the action defined in `command`.
|
||||
* Anything with a `name`<sup>[1]</sup> will be added as an entry (using that
|
||||
name) to the Command Palette's Action Mode.
|
||||
|
||||
###### Caveats
|
||||
|
||||
* **Nested commands** (commands with other nested commands). If a command has
|
||||
nested commands in the `commands` property, AND a `keys` property, then
|
||||
pressing that keybinding should open the Command Palette directly to that
|
||||
level of nesting of commands.
|
||||
* **"Iterable" commands** (with an `iterateOn` property): These are commands
|
||||
that are expanded into one command per profile. These cannot really be bound
|
||||
as keybindings - which action should be bound to the key? They can't all be
|
||||
bound to the same key. If a KeyBinding/Command json blob has a valid
|
||||
`iterateOn` property, then we'll ignore it as a keybinding. This includes any
|
||||
commands that are nested as children of this command - we won't be able to
|
||||
know which of the expanded children will be the one to bind the keys to.
|
||||
|
||||
<sup>[1]</sup>: This requirement will be relaxed given **Proposal 2**, below,
|
||||
but ignored for the remainder of this section, for illustrative purposes.
|
||||
|
||||
#### Example
|
||||
|
||||
Consider the following settings:
|
||||
|
||||
```json
|
||||
"bindings": [
|
||||
{ "name": "Duplicate Tab", "command": "duplicateTab", "keys": "ctrl+alt+a" },
|
||||
{ "command": "nextTab", "keys": "ctrl+alt+b" },
|
||||
{
|
||||
"icon": "...",
|
||||
"name": { "key": "NewTabWithProfileRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": { "key": "NewTabWithProfileCommandName" },
|
||||
"command": { "action": "newTab", "profile": "${profile.name}" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"icon": "...",
|
||||
"name": "Connect to ssh...",
|
||||
"commands": [
|
||||
{
|
||||
"keys": "ctrl+alt+c",
|
||||
"icon": "...",
|
||||
"name": "first.com",
|
||||
"command": { "action": "newTab", "commandline": "ssh me@first.com" }
|
||||
},
|
||||
{
|
||||
"keys": "ctrl+alt+d",
|
||||
"icon": "...",
|
||||
"name": "second.com",
|
||||
"command": { "action": "newTab", "commandline": "ssh me@second.com" }
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"keys": "ctrl+alt+e",
|
||||
"icon": "...",
|
||||
"name": { "key": "SplitPaneWithProfileRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": { "key": "SplitPaneWithProfileCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"keys": "ctrl+alt+f",
|
||||
"icon": "...",
|
||||
"name": { "key": "SplitPaneName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "automatic" }
|
||||
},
|
||||
{
|
||||
"icon": "...",
|
||||
"name": { "key": "SplitPaneVerticalName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" }
|
||||
},
|
||||
{
|
||||
"icon": "...",
|
||||
"name": { "key": "SplitPaneHorizontalName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
This will generate a tree of commands as follows:
|
||||
|
||||
```
|
||||
<Command Palette>
|
||||
├─ Duplicate tab { ctrl+alt+a }
|
||||
├─ New Tab With Profile...
|
||||
│ ├─ Profile 1
|
||||
│ ├─ Profile 2
|
||||
│ └─ Profile 3
|
||||
├─ Connect to ssh...
|
||||
│ ├─ first.com { ctrl+alt+c }
|
||||
│ └─ second.com { ctrl+alt+d }
|
||||
└─ New Pane... { ctrl+alt+e }
|
||||
├─ Profile 1...
|
||||
| ├─ Split Automatically
|
||||
| ├─ Split Vertically
|
||||
| └─ Split Horizontally
|
||||
├─ Profile 2...
|
||||
| ├─ Split Automatically
|
||||
| ├─ Split Vertically
|
||||
| └─ Split Horizontally
|
||||
└─ Profile 3...
|
||||
├─ Split Automatically
|
||||
├─ Split Vertically
|
||||
└─ Split Horizontally
|
||||
```
|
||||
|
||||
Note also the keybindings in the above example:
|
||||
* <kbd>ctrl+alt+a</kbd>: This key chord is bound to the "Duplicate tab"
|
||||
(`duplicateTab`) action, which is also bound to the command with the same
|
||||
name.
|
||||
* <kbd>ctrl+alt+b</kbd>: This key chord is bound to the `nextTab` action, which
|
||||
doesn't have an associated command.
|
||||
* <kbd>ctrl+alt+c</kbd>: This key chord is bound to the "Connect to
|
||||
ssh../first.com" action, which will open a new tab with the `commandline`
|
||||
`"ssh me@first.com"`. When the user presses this keybinding, the action will
|
||||
be executed immediately, without the Command Palette appearing.
|
||||
* <kbd>ctrl+alt+d</kbd>: This is the same as the above, but with the "Connect to
|
||||
ssh../second.com" action.
|
||||
* <kbd>ctrl+alt+e</kbd>: This key chord is bound to opening the Command Palette
|
||||
to the "New Pane..." command's menu. When the user presses this keybinding,
|
||||
they'll be prompted with this command's sub-commands:
|
||||
```
|
||||
Profile 1...
|
||||
Profile 2...
|
||||
Profile 3...
|
||||
```
|
||||
* <kbd>ctrl+alt+f</kbd>: This key will _not_ be bound to any action. The parent
|
||||
action is iterable, which means that the `SplitPaneName` command is going to
|
||||
get turned into one command for each and every profile, and therefore cannot
|
||||
be bound to just a single action.
|
||||
|
||||
### Proposal 2: Automatically synthesize action names
|
||||
|
||||
Previously, all Commands were required to have a `name`. This name was used as
|
||||
the text for the action in the Action Mode of the Command Palette. However, this
|
||||
is a little tedious for users who already have lots of keys bound. They'll need
|
||||
to go through and add names to each of their existing keybindings to ensure that
|
||||
the actions appear in the palette. Could we instead synthesize the names for the
|
||||
commands ourselves? This would enable users to automatically get each of their
|
||||
existing keybindings to appear in the palette without any extra work.
|
||||
|
||||
To support this, the following changes will be made:
|
||||
* `ActionAndArgs` will get a `GenerateName()` method defined. This will create a
|
||||
string describing the `ShortcutAction` and it's associated `ActionArgs`.
|
||||
- Not EVERY action _needs_ to define a result for `GenerateName`. Actions that
|
||||
don't _won't_ be automatically added to the Command Palette.
|
||||
- Each of the strings used in `GenerateName` will need to come from our
|
||||
resources, so they can be localized appropriately.
|
||||
* When we're parsing commands, if a command doesn't have a `name`, we'll instead
|
||||
attempt to use `GenerateName` to create the unique string for the action
|
||||
associated with this command. If the command does have a `name` set, we'll use
|
||||
that string instead, allowing the user to override the default name.
|
||||
- If a command has it's name set to `null`, then we'll ignore the command
|
||||
entirely, not just use the generated name.
|
||||
|
||||
[**Appendix 1**](#appendix-1-name-generation-samples-for-ShortcutActions) below
|
||||
shows a complete sample of the strings that will be generated for each of the existing
|
||||
`ShortcutActions`, and many of the actions that have been proposed, but not yet
|
||||
implemented.
|
||||
|
||||
These strings should be human-friendly versions of the actions and their
|
||||
associated args. For some of these actions, with very few arguments, the strings
|
||||
can be relatively simple. Take for example, `CopyText`:
|
||||
|
||||
JSON | Generated String
|
||||
-- | --
|
||||
`{ "action":"copyText" }` | "Copy text"
|
||||
`{ "action":"copyText", "singleLine": true }` | "Copy text as a single line"
|
||||
`{ "action":"copyText", "singleLine": false, "copyFormatting": false }` | "Copy text without formatting"
|
||||
`{ "action":"copyText", "singleLine": true, "copyFormatting": true }` | "Copy text as a single line without formatting"
|
||||
|
||||
CopyText is a bit of a simplistic case however, with very few args or
|
||||
permutations of argument values. For things like `newTab`, `splitPane`, where
|
||||
there are many possible arguments and values, it will be acceptable to simply
|
||||
append `", property:value"` strings to the generated names for each of the set
|
||||
values.
|
||||
|
||||
For example:
|
||||
|
||||
JSON | Generated String
|
||||
-- | --
|
||||
`{ "action":"newTab", "profile": "Hello" }` | "Open a new tab, profile:Hello"
|
||||
`{ "action":"newTab", "profile": "Hello", "directory":"C:\\", "commandline": "wsl.exe", title": "Foo" }` | "Open a new tab, profile:Hello, directory:C:\\, commandline:wsl.exe, title:Foo"
|
||||
|
||||
|
||||
This is being chosen in favor of something that might be more human-friendly,
|
||||
like "Open a new tab with profile {profile name} in {directory} with
|
||||
{commandline} and a title of {title}". This string would be much harder to
|
||||
synthesize, especially considering localization concerns.
|
||||
|
||||
#### Remove the resource key notation
|
||||
|
||||
Since we'll be using localized names for each of the actions in `GenerateName`,
|
||||
we no longer _need_ to provide the `{ "name":{ "key": "SomeResourceKey" } }`
|
||||
syntax introduced in the original spec. This functionality was used to allow us
|
||||
to define localizable names for the default commands.
|
||||
|
||||
However, I think we should keep this functionality, to allow us additional
|
||||
flexibility when defining default commands.
|
||||
|
||||
### Complete Defaults
|
||||
|
||||
Considering both of the above proposals, the default keybindings and commands
|
||||
will be defined as follows:
|
||||
|
||||
* The current default keybindings will be untouched. These actions will
|
||||
automatically be added to the Command Palette, using their names generated
|
||||
from `GenerateName`.
|
||||
- **TODO: FOR DISCUSSION**: Should we manually set the names for the default
|
||||
"New Tab, profile index: 0" keybindings to `null`? This seems like a not
|
||||
terribly helpful name for the Command Palette, especially considering the
|
||||
iterable commands listed below.
|
||||
* We'll add a few new commands:
|
||||
- A nested, iterable command for "Open new tab with
|
||||
profile..."/"Profile:{profile name}"
|
||||
- A nested, iterable command for "Select color scheme..."/"{scheme name}"
|
||||
- A nested, iterable command for "New Pane..."/"Profile:{profile
|
||||
name}..."/["Automatic", "Horizontal", "Vertical"]
|
||||
> 👉 NOTE: These default nested commands can be removed by the user defining
|
||||
> `{ "name": "Open new tab with profile...", "action":null }` (et al) in their
|
||||
> settings.
|
||||
- If we so chose, in the future we can add further commands that we think are
|
||||
helpful to `defaults.json`, without needing to give them keys. For example,
|
||||
we could add
|
||||
```json
|
||||
{ "command": { "action": "copy", "singleLine": true } }
|
||||
```
|
||||
to `bindings`, to add a "copy text as a single line" command, without
|
||||
necessarily binding it to a keystroke.
|
||||
|
||||
|
||||
These changes to the `defaults.json` are represented in json as the following:
|
||||
|
||||
```json
|
||||
"bindings": [
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "NewTabWithProfileRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": "${profile.name}",
|
||||
"command": { "action": "newTab", "profile": "${profile.name}" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SelectColorSchemeRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "schemes",
|
||||
"icon": null,
|
||||
"name": "${scheme.name}",
|
||||
"command": { "action": "selectColorScheme", "scheme": "${scheme.name}" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SplitPaneWithProfileRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": { "key": "SplitPaneWithProfileCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SplitPaneName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "automatic" }
|
||||
},
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SplitPaneVerticalName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" }
|
||||
},
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SplitPaneHorizontalName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
A complete diagram of what the default Command Palette will look like given the
|
||||
default keybindings and these changes is given in [**Appendix
|
||||
2**](#appendix-2-complete-default-command-palette).
|
||||
|
||||
## Concerns
|
||||
|
||||
**DISCUSSION**: "New tab with index {index}". How does this play with
|
||||
the new tab dropdown customizations in [#5888]? In recent iterations of that
|
||||
spec, we changed the meaning of `{ "action": "newTab", "index": 1 }` to mean
|
||||
"open the first entry in the new tab menu". If that's a profile, then we'll open
|
||||
a new tab with it. If it's an action, we'll perform that action. If it's a
|
||||
nested menu, then we'll open the menu to that entry.
|
||||
|
||||
Additionally, how exactly does that play with something like `{ "action":
|
||||
"newTab", "index": 1, "commandline": "wsl.exe" }`? This is really a discussion
|
||||
for that spec, but is an issue highlighted by this spec. If the first entry is
|
||||
anything other than a `profile`, then the `commandline` parameter doesn't really
|
||||
mean anything anymore. I'm tempted to revert this particular portion of the new
|
||||
tab menu customization spec over this.
|
||||
|
||||
We could instead add an `index` to `openNewTabDropdown`, and have that string
|
||||
instead be "Open new tab dropdown, index:1". That would help disambiguate the
|
||||
two.
|
||||
|
||||
Following discussion, it was decided that this was in fact the cleanest
|
||||
solution, when accounting for both the needs of the new tab dropdown and the
|
||||
command palette. The [#5888] spec has been updated to reflect this.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* Some of these command names are starting to get _very_ long. Perhaps we need a
|
||||
netting to display Command Palette entries on two lines (or multiple, as
|
||||
necessary).
|
||||
* When displaying the entries of a nested command to the user, should we display
|
||||
a small label showing the name of the previous command? My gut says _yes_. In
|
||||
the Proposal 1 example, pressing `ctrl+alt+e` to jump to "Split Pane..."
|
||||
should probably show a small label that displays "Split Pane..." above the
|
||||
list of nested commands.
|
||||
* It wouldn't be totally impossible to allow keys to be bound to an iterable
|
||||
command, and then simply have the key work as "open the command palette with
|
||||
only the commands generated by this iterable command". This is left as a
|
||||
future option, as it might require some additional technical plumbing.
|
||||
|
||||
## Appendix 1: Name generation samples for `ShortcutAction`s
|
||||
|
||||
### Current `ShortcutActions`
|
||||
|
||||
* `CopyText`
|
||||
- "Copy text"
|
||||
- "Copy text as a single line"
|
||||
- "Copy text without formatting"
|
||||
- "Copy text as a single line without formatting"
|
||||
* `PasteText`
|
||||
- "Paste text"
|
||||
* `OpenNewTabDropdown`
|
||||
- "Open new tab dropdown"
|
||||
* `DuplicateTab`
|
||||
- "Duplicate tab"
|
||||
* `NewTab`
|
||||
- "Open a new tab, profile:{profile name}, directory:{directory}, commandline:{commandline}, title:{title}"
|
||||
* `NewWindow`
|
||||
- "Open a new window"
|
||||
- "Open a new window, profile:{profile name}, directory:{directory}, commandline:{commandline}, title:{title}"
|
||||
* `CloseWindow`
|
||||
- "Close window"
|
||||
* `CloseTab`
|
||||
- "Close tab"
|
||||
* `ClosePane`
|
||||
- "Close pane"
|
||||
* `NextTab`
|
||||
- "Switch to the next tab"
|
||||
* `PrevTab`
|
||||
- "Switch to the previous tab"
|
||||
* `SplitPane`
|
||||
- "Open a new pane, profile:{profile name}, split direction:{direction}, split size:{X%/Y chars}, resize parents, directory:{directory}, commandline:{commandline}, title:{title}"
|
||||
- "Duplicate the current pane, split direction:{direction}, split size:{X%/Y chars}, resize parents, directory:{directory}, commandline:{commandline}, title:{title}"
|
||||
* `SwitchToTab`
|
||||
- "Switch to tab {index}"
|
||||
* `AdjustFontSize`
|
||||
- "Increase the font size"
|
||||
- "Decrease the font size"
|
||||
* `ResetFontSize`
|
||||
- "Reset the font size"
|
||||
* `ScrollUp`
|
||||
- "Scroll up a line"
|
||||
- "Scroll up {amount} lines"
|
||||
* `ScrollDown`
|
||||
- "Scroll down a line"
|
||||
- "Scroll down {amount} lines"
|
||||
* `ScrollUpPage`
|
||||
- "Scroll up a page"
|
||||
- "Scroll up {amount} pages"
|
||||
* `ScrollDownPage`
|
||||
- "Scroll down a page"
|
||||
- "Scroll down {amount} pages"
|
||||
* `ResizePane`
|
||||
- "Resize pane {direction}"
|
||||
- "Resize pane {direction} {percent}%"
|
||||
* `MoveFocus`
|
||||
- "Move focus {direction}"
|
||||
* `Find`
|
||||
- "Toggle the search box"
|
||||
* `ToggleFullscreen`
|
||||
- "Toggle fullscreen mode"
|
||||
* `OpenSettings`
|
||||
- "Open settings"
|
||||
- "Open settings file"
|
||||
- "Open default settings file"
|
||||
* `ToggleCommandPalette`
|
||||
- "Toggle the Command Palette"
|
||||
- "Toggle the Command Palette in commandline mode"
|
||||
|
||||
### Other yet unimplemented actions:
|
||||
* `SwitchColorScheme`
|
||||
- "Select color scheme {name}"
|
||||
* `ToggleRetroEffect`
|
||||
- "Toggle the retro terminal effect"
|
||||
* `ExecuteCommandline`
|
||||
- "Run a wt commandline: {cmdline}"
|
||||
* `ExecuteActions`
|
||||
- OPINION: THIS ONE SHOULDN'T HAVE A NAME. We're not including any of these by
|
||||
default. The user knows what they're putting in the settings by adding this
|
||||
action, let them name it.
|
||||
- Alternatively: "Run actions: {action.ToName() for action in actions}"
|
||||
* `SendInput`
|
||||
- OPINION: THIS ONE SHOULDN'T HAVE A NAME. We're not including any of these by
|
||||
default. The user knows what they're putting in the settings by adding this
|
||||
action, let them name it.
|
||||
* `ToggleMarkMode`
|
||||
- "Toggle Mark Mode"
|
||||
* `NextTab`
|
||||
- "Switch to the next most-recent tab"
|
||||
* `SetTabColor`
|
||||
- "Set the color of the current tab to {#color}"
|
||||
* It would be _really_ cool if we could display a sample of the color
|
||||
inline, but that's left as a future consideration.
|
||||
- "Set the color for this tab..."
|
||||
* this command isn't nested, but hitting enter immediately does something
|
||||
with the UI, so that's _fine_
|
||||
* `RenameTab`
|
||||
- "Rename this tab to {name}"
|
||||
- "Rename this tab..."
|
||||
* this command isn't nested, but hitting enter immediately does something
|
||||
with the UI, so that's _fine_
|
||||
|
||||
|
||||
## Appendix 2: Complete Default Command Palette
|
||||
|
||||
This diagram shows what the default value of the Command Palette would be. This
|
||||
assumes that the user has 3 profiles, "Profile 1", "Profile 2", and "Profile 3",
|
||||
as well as 3 schemes: "Scheme 1", "Scheme 2", and "Scheme 3".
|
||||
|
||||
```
|
||||
<Command Palette>
|
||||
├─ Close Window
|
||||
├─ Toggle fullscreen mode
|
||||
├─ Open new tab dropdown
|
||||
├─ Open settings
|
||||
├─ Open default settings file
|
||||
├─ Toggle the search box
|
||||
├─ New Tab
|
||||
├─ New Tab, profile index: 0
|
||||
├─ New Tab, profile index: 1
|
||||
├─ New Tab, profile index: 2
|
||||
├─ New Tab, profile index: 3
|
||||
├─ New Tab, profile index: 4
|
||||
├─ New Tab, profile index: 5
|
||||
├─ New Tab, profile index: 6
|
||||
├─ New Tab, profile index: 7
|
||||
├─ New Tab, profile index: 8
|
||||
├─ Duplicate tab
|
||||
├─ Switch to the next tab
|
||||
├─ Switch to the previous tab
|
||||
├─ Switch to tab 0
|
||||
├─ Switch to tab 1
|
||||
├─ Switch to tab 2
|
||||
├─ Switch to tab 3
|
||||
├─ Switch to tab 4
|
||||
├─ Switch to tab 5
|
||||
├─ Switch to tab 6
|
||||
├─ Switch to tab 7
|
||||
├─ Switch to tab 8
|
||||
├─ Close pane
|
||||
├─ Open a new pane, split: horizontal
|
||||
├─ Open a new pane, split: vertical
|
||||
├─ Duplicate the current pane
|
||||
├─ Resize pane down
|
||||
├─ Resize pane left
|
||||
├─ Resize pane right
|
||||
├─ Resize pane up
|
||||
├─ Move focus down
|
||||
├─ Move focus left
|
||||
├─ Move focus right
|
||||
├─ Move focus up
|
||||
├─ Copy Text
|
||||
├─ Paste Text
|
||||
├─ Scroll down a line
|
||||
├─ Scroll down a page
|
||||
├─ Scroll up a line
|
||||
├─ Scroll up a page
|
||||
├─ Increase the font size
|
||||
├─ Decrease the font size
|
||||
├─ Reset the font size
|
||||
├─ New Tab With Profile...
|
||||
│ ├─ Profile 1
|
||||
│ ├─ Profile 2
|
||||
│ └─ Profile 3
|
||||
├─ Select Color Scheme...
|
||||
│ ├─ Scheme 1
|
||||
│ ├─ Scheme 2
|
||||
│ └─ Scheme 3
|
||||
└─ New Pane...
|
||||
├─ Profile 1...
|
||||
| ├─ Split Automatically
|
||||
| ├─ Split Vertically
|
||||
| └─ Split Horizontally
|
||||
├─ Profile 2...
|
||||
| ├─ Split Automatically
|
||||
| ├─ Split Vertically
|
||||
| └─ Split Horizontally
|
||||
└─ Profile 3...
|
||||
├─ Split Automatically
|
||||
├─ Split Vertically
|
||||
└─ Split Horizontally
|
||||
```
|
||||
|
||||
|
||||
<!-- Footnotes -->
|
||||
[Command Palette Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Command%20Palette.md
|
||||
@@ -7,7 +7,7 @@ This document outlines the roadmap towards delivering Windows Terminal 2.0 by Sp
|
||||
|
||||
## Milestones
|
||||
|
||||
The Windows Terminal project is engineered and delivered as a set of 4-week milestones. New features will go into [Windows Terminal Preview](https://aka.ms/terminal-preview) first, then a month after they been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
|
||||
The Windows Terminal project is engineered and delivered as a set of 4-week milestones. New features will go into [Windows Terminal Preview](https://aka.ms/terminal-preview) first, then a month after they've been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
|
||||
|
||||
| Duration | Activity | Releases |
|
||||
| --- | --- | --- |
|
||||
@@ -21,7 +21,7 @@ Below is the schedule for when milestones will be included in release builds of
|
||||
|
||||
| Milestone End Date | Milestone Name | Preview Release Blog Post |
|
||||
| ------------------ | -------------- | ------------------------- |
|
||||
| 2020-06-30 | [1.1] in Windows Terminal Preview | |
|
||||
| 2020-06-18 | [1.1] in Windows Terminal Preview | [Windows Terminal Preview 1.1 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-1-release/) |
|
||||
| 2020-07-31 | [1.2] in Windows Terminal Preview<br>[1.1] in Windows Terminal | |
|
||||
| 2020-08-31 | 1.3 in Windows Terminal Preview<br>[1.2] in Windows Terminal | |
|
||||
| 2020-09-30 | 1.4 in Windows Terminal Preview<br>1.3 in Windows Terminal | |
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Samples.Terminal
|
||||
internal ReadConsoleInputStream(HFILE handle,
|
||||
BlockingCollection<Kernel32.INPUT_RECORD> nonKeyEvents)
|
||||
{
|
||||
Debug.Assert(handle.IsInvalid == false, "handle.IsInvalid == false");
|
||||
Debug.Assert(!handle.IsInvalid, "handle.IsInvalid == false");
|
||||
|
||||
_handle = handle.DangerousGetHandle();
|
||||
_nonKeyEvents = nonKeyEvents;
|
||||
@@ -111,7 +111,7 @@ namespace Samples.Terminal
|
||||
if (record.EventType == Kernel32.EVENT_TYPE.KEY_EVENT)
|
||||
{
|
||||
// skip key up events - if not, every key will be duped in the stream
|
||||
if (record.Event.KeyEvent.bKeyDown == false) continue;
|
||||
if (!record.Event.KeyEvent.bKeyDown) continue;
|
||||
|
||||
// pack ucs-2/utf-16le/unicode chars into position in our byte[] buffer.
|
||||
var glyph = (ushort) record.Event.KeyEvent.uChar;
|
||||
|
||||
@@ -264,15 +264,16 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
{
|
||||
// First we try to find the run where the insertion happens, using lowerBound and upperBound to track
|
||||
// where we are currently at.
|
||||
const auto begin = _list.begin();
|
||||
size_t lowerBound = 0;
|
||||
size_t upperBound = 0;
|
||||
for (size_t i = 0; i < _list.size(); i++)
|
||||
{
|
||||
upperBound += _list.at(i).GetLength();
|
||||
const auto curr = begin + i;
|
||||
upperBound += curr->GetLength();
|
||||
|
||||
if (iStart >= lowerBound && iStart < upperBound)
|
||||
{
|
||||
const auto curr = std::next(_list.begin(), i);
|
||||
|
||||
// The run that we try to insert into has the same color as the new one.
|
||||
// e.g.
|
||||
// AAAAABBBBBBBCCC
|
||||
@@ -385,7 +386,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// fact that an existing piece of the run was split in half (to hold the latter half).
|
||||
const size_t cNewRun = _list.size() + newAttrs.size() + 1;
|
||||
std::vector<TextAttributeRun> newRun;
|
||||
newRun.resize(cNewRun);
|
||||
newRun.reserve(cNewRun);
|
||||
|
||||
// We will start analyzing from the beginning of our existing run.
|
||||
// Use some pointers to keep track of where we are in walking through our runs.
|
||||
@@ -393,10 +394,9 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// Get the existing run that we'll be updating/manipulating.
|
||||
const auto existingRun = _list.begin();
|
||||
auto pExistingRunPos = existingRun;
|
||||
const auto pExistingRunEnd = existingRun + _list.size();
|
||||
const auto pExistingRunEnd = _list.end();
|
||||
auto pInsertRunPos = newAttrs.begin();
|
||||
size_t cInsertRunRemaining = newAttrs.size();
|
||||
auto pNewRunPos = newRun.begin();
|
||||
size_t iExistingRunCoverage = 0;
|
||||
|
||||
// Copy the existing run into the new buffer up to the "start index" where the new run will be injected.
|
||||
@@ -410,7 +410,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
iExistingRunCoverage += pExistingRunPos->GetLength();
|
||||
|
||||
// Copy it to the new run buffer and advance both pointers.
|
||||
*pNewRunPos++ = *pExistingRunPos++;
|
||||
newRun.push_back(*pExistingRunPos++);
|
||||
}
|
||||
|
||||
// When we get to this point, we've copied full segments from the original existing run
|
||||
@@ -429,12 +429,8 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// We need to fix this up below so it says G2 instead to leave room for the Y3 to fit in
|
||||
// the new/final run.
|
||||
|
||||
// Copying above advanced the pointer to an empty cell beyond what we copied.
|
||||
// Back up one cell so we can manipulate the final item we copied from the existing run to the new run.
|
||||
pNewRunPos--;
|
||||
|
||||
// Fetch out the length so we can fix it up based on the below conditions.
|
||||
size_t length = pNewRunPos->GetLength();
|
||||
size_t length = newRun.back().GetLength();
|
||||
|
||||
// If we've covered more cells already than the start of the attributes to be inserted...
|
||||
if (iExistingRunCoverage > iStart)
|
||||
@@ -449,7 +445,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// Now we're still on that "last cell copied" into the new run.
|
||||
// If the color of that existing copied cell matches the color of the first segment
|
||||
// of the run we're about to insert, we can just increment the length to extend the coverage.
|
||||
if (pNewRunPos->GetAttributes() == pInsertRunPos->GetAttributes())
|
||||
if (newRun.back().GetAttributes() == pInsertRunPos->GetAttributes())
|
||||
{
|
||||
length += pInsertRunPos->GetLength();
|
||||
|
||||
@@ -460,18 +456,11 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
}
|
||||
|
||||
// We're done manipulating the length. Store it back.
|
||||
pNewRunPos->SetLength(length);
|
||||
|
||||
// Now that we're done adjusting the last copied item, advance the pointer into a fresh/blank
|
||||
// part of the new run array.
|
||||
pNewRunPos++;
|
||||
newRun.back().SetLength(length);
|
||||
}
|
||||
|
||||
// Bulk copy the majority (or all, depending on circumstance) of the insert run into the final run buffer.
|
||||
std::copy_n(pInsertRunPos, cInsertRunRemaining, pNewRunPos);
|
||||
|
||||
// Advance the new run pointer into the position just after everything we copied.
|
||||
pNewRunPos += cInsertRunRemaining;
|
||||
std::copy_n(pInsertRunPos, cInsertRunRemaining, std::back_inserter(newRun));
|
||||
|
||||
// We're technically done with the insert run now and have 0 remaining, but won't bother updating its pointers
|
||||
// and counts any further because we won't use them.
|
||||
@@ -488,9 +477,6 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// If we still have original existing run cells remaining, copy them into the final new run.
|
||||
if (pExistingRunPos != pExistingRunEnd || iExistingRunCoverage != (iEnd + 1))
|
||||
{
|
||||
// Back up one cell so we can inspect the most recent item copied into the new run for optimizations.
|
||||
pNewRunPos--;
|
||||
|
||||
// We advanced the existing run pointer and its count to on or past the end of what the insertion run filled in.
|
||||
// If this ended up being past the end of what the insertion run covers, we have to account for the cells after
|
||||
// the insertion run but before the next piece of the original existing run.
|
||||
@@ -514,11 +500,11 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// This case is slightly off from the example above. This case is for if the B2 above was actually Y2.
|
||||
// That Y2 from the existing run is the same color as the Y2 we just filled a few columns left in the final run
|
||||
// so we can just adjust the final run's column count instead of adding another segment here.
|
||||
if (pNewRunPos->GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
{
|
||||
size_t length = pNewRunPos->GetLength();
|
||||
size_t length = newRun.back().GetLength();
|
||||
length += (iExistingRunCoverage - (iEnd + 1));
|
||||
pNewRunPos->SetLength(length);
|
||||
newRun.back().SetLength(length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -526,14 +512,14 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// its length for the discrepancy in columns not yet covered by the final/new run.
|
||||
|
||||
// Move forward to a blank spot in the new run
|
||||
pNewRunPos++;
|
||||
newRun.emplace_back();
|
||||
|
||||
// Copy the existing run's color information to the new run
|
||||
pNewRunPos->SetAttributes(pExistingRunPos->GetAttributes());
|
||||
newRun.back().SetAttributes(pExistingRunPos->GetAttributes());
|
||||
|
||||
// Adjust the length of that copied color to cover only the reduced number of columns needed
|
||||
// now that some have been replaced by the insert run.
|
||||
pNewRunPos->SetLength(iExistingRunCoverage - (iEnd + 1));
|
||||
newRun.back().SetLength(iExistingRunCoverage - (iEnd + 1));
|
||||
}
|
||||
|
||||
// Now that we're done recovering a piece of the existing run we skipped, move the pointer forward again.
|
||||
@@ -551,34 +537,25 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// New Run desired when done = R3 -> B7
|
||||
// Existing run pointer is on B2.
|
||||
// We want to merge the 2 from the B2 into the B5 so we get B7.
|
||||
else if (pNewRunPos->GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
else if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
{
|
||||
// Add the value from the existing run into the current new run position.
|
||||
size_t length = pNewRunPos->GetLength();
|
||||
size_t length = newRun.back().GetLength();
|
||||
length += pExistingRunPos->GetLength();
|
||||
pNewRunPos->SetLength(length);
|
||||
newRun.back().SetLength(length);
|
||||
|
||||
// Advance the existing run position since we consumed its value and merged it in.
|
||||
pExistingRunPos++;
|
||||
}
|
||||
|
||||
// OK. We're done inspecting the most recently copied cell for optimizations.
|
||||
pNewRunPos++;
|
||||
|
||||
// Now bulk copy any segments left in the original existing run
|
||||
if (pExistingRunPos < pExistingRunEnd)
|
||||
{
|
||||
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), pNewRunPos);
|
||||
|
||||
// Fix up the end pointer so we know where we are for counting how much of the new run's memory space we used.
|
||||
pNewRunPos += (pExistingRunEnd - pExistingRunPos);
|
||||
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), std::back_inserter(newRun));
|
||||
}
|
||||
}
|
||||
|
||||
// OK, phew. We're done. Now we just need to free the existing run, store the new run in its place,
|
||||
// and update the count for the correct length of the new run now that we've filled it up.
|
||||
|
||||
newRun.erase(pNewRunPos, newRun.end());
|
||||
// OK, phew. We're done. Now we just need to free the existing run and store the new run in its place.
|
||||
_list.swap(newRun);
|
||||
|
||||
return S_OK;
|
||||
|
||||
@@ -47,7 +47,7 @@ void UnicodeStorage::Erase(const key_type key)
|
||||
// - rowMap - A map of the old row IDs to the new row IDs.
|
||||
// - width - The width of the new row. Remove any items that are beyond the row width.
|
||||
// - Use nullopt if we're not resizing the width of the row, just renumbering the rows.
|
||||
void UnicodeStorage::Remap(const std::map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
|
||||
void UnicodeStorage::Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
|
||||
{
|
||||
// Make a temporary map to hold all the new row positioning
|
||||
std::unordered_map<key_type, mapped_type> newMap;
|
||||
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
|
||||
void Erase(const key_type key);
|
||||
|
||||
void Remap(const std::map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
|
||||
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
|
||||
|
||||
private:
|
||||
std::unordered_map<key_type, mapped_type> _map;
|
||||
|
||||
@@ -872,7 +872,7 @@ UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
|
||||
// - newRowWidth - Optional new value for the row width.
|
||||
void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
|
||||
{
|
||||
std::map<SHORT, SHORT> rowMap;
|
||||
std::unordered_map<SHORT, SHORT> rowMap;
|
||||
SHORT i = 0;
|
||||
for (auto& it : _storage)
|
||||
{
|
||||
|
||||
349
src/cascadia/LocalTests_TerminalApp/CommandTests.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalApp/CascadiaSettings.h"
|
||||
#include "JsonTestClass.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
// TODO:microsoft/terminal#3838:
|
||||
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
|
||||
// an updated TAEF that will let us install framework packages when the test
|
||||
// package is deployed. Until then, these tests won't deploy in CI.
|
||||
|
||||
class CommandTests : public JsonTestClass
|
||||
{
|
||||
// Use a custom AppxManifest to ensure that we can activate winrt types
|
||||
// from our test. This property will tell taef to manually use this as
|
||||
// the AppxManifest for this test class.
|
||||
// This does not yet work for anything XAML-y. See TabTests.cpp for more
|
||||
// details on that.
|
||||
BEGIN_TEST_CLASS(CommandTests)
|
||||
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(ManyCommandsSameAction);
|
||||
TEST_METHOD(LayerCommand);
|
||||
TEST_METHOD(TestSplitPaneArgs);
|
||||
TEST_METHOD(TestResourceKeyName);
|
||||
TEST_METHOD(TestAutogeneratedName);
|
||||
TEST_METHOD(TestLayerOnAutogeneratedName);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void CommandTests::ManyCommandsSameAction()
|
||||
{
|
||||
const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" };
|
||||
const std::string commands1String{ R"([ { "name":"action1", "command": { "action": "copy", "singleLine": false } } ])" };
|
||||
const std::string commands2String{ R"([
|
||||
{ "name":"action2", "command": "paste" },
|
||||
{ "name":"action3", "command": "paste" }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
const auto commands1Json = VerifyParseSucceeded(commands1String);
|
||||
const auto commands2Json = VerifyParseSucceeded(commands2String);
|
||||
|
||||
std::map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(2u, commands.size());
|
||||
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(4u, commands.size());
|
||||
}
|
||||
|
||||
void CommandTests::LayerCommand()
|
||||
{
|
||||
// Each one of the commands in this test should layer upon the previous, overriding the action.
|
||||
const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" };
|
||||
const std::string commands1String{ R"([ { "name":"action0", "command": "paste" } ])" };
|
||||
const std::string commands2String{ R"([ { "name":"action0", "command": "newTab" } ])" };
|
||||
const std::string commands3String{ R"([ { "name":"action0", "command": null } ])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
const auto commands1Json = VerifyParseSucceeded(commands1String);
|
||||
const auto commands2Json = VerifyParseSucceeded(commands2String);
|
||||
const auto commands3Json = VerifyParseSucceeded(commands3String);
|
||||
|
||||
std::map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action());
|
||||
VERIFY_IS_NULL(command.Action().Args());
|
||||
}
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
{
|
||||
// This last command should "unbind" the action.
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands3Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestSplitPaneArgs()
|
||||
{
|
||||
// This is the same as KeyBindingsTests::TestSplitPaneArgs, but with
|
||||
// looking up the action and its args from a map of commands, instead
|
||||
// of from keybindings.
|
||||
|
||||
const std::string commands0String{ R"([
|
||||
{ "name": "command0", "command": { "action": "splitPane", "split": null } },
|
||||
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "name": "command3", "command": { "action": "splitPane", "split": "none" } },
|
||||
{ "name": "command4", "command": { "action": "splitPane" } },
|
||||
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
|
||||
{ "name": "command6", "command": { "action": "splitPane", "split": "foo" } }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(7u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"command0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command3");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command4");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command5");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command6");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
void CommandTests::TestResourceKeyName()
|
||||
{
|
||||
// This test checks looking up a name from a resource key.
|
||||
|
||||
const std::string commands0String{ R"([ { "name": { "key": "DuplicateTabCommandKey"}, "command": "copy" } ])" };
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
// NOTE: We're relying on DuplicateTabCommandKey being defined as
|
||||
// "Duplicate Tab" here. If that string changes in our resources,
|
||||
// this test will break.
|
||||
auto command = commands.at(L"Duplicate tab");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestAutogeneratedName()
|
||||
{
|
||||
// This test ensures that we'll correctly create commands for actions
|
||||
// that don't have given names, pursuant to the spec in GH#6532.
|
||||
|
||||
// NOTE: The keys used to look up these commands are partially generated
|
||||
// from strings in our Resources.resw. If those string values should
|
||||
// change, it's likely that this test will break.
|
||||
|
||||
const std::string commands0String{ R"([
|
||||
{ "command": { "action": "splitPane", "split": null } },
|
||||
{ "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "command": { "action": "splitPane", "split": "none" } },
|
||||
{ "command": { "action": "splitPane" } },
|
||||
{ "command": { "action": "splitPane", "split": "auto" } },
|
||||
{ "command": { "action": "splitPane", "split": "foo" } }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
|
||||
// There are only 3 commands here: all of the `"none"`, `"auto"`,
|
||||
// `"foo"`, `null`, and <no args> bindings all generate the same action,
|
||||
// which will generate just a single name for all of them.
|
||||
VERIFY_ARE_EQUAL(3u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: Vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: Horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
void CommandTests::TestLayerOnAutogeneratedName()
|
||||
{
|
||||
const std::string commands0String{ R"([
|
||||
{ "command": { "action": "splitPane" } },
|
||||
{ "name":"Split pane", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TEST_METHOD(TestStringOverload);
|
||||
|
||||
TEST_METHOD(TestSetTabColorArgs);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -400,6 +402,63 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestSetTabColorArgs()
|
||||
{
|
||||
const std::string bindings0String{ R"([
|
||||
{ "keys": ["ctrl+c"], "command": { "action": "setTabColor", "color": null } },
|
||||
{ "keys": ["ctrl+d"], "command": { "action": "setTabColor", "color": "#123456" } },
|
||||
{ "keys": ["ctrl+e"], "command": { "action": "setTabColor", "color": "thisStringObviouslyWontWork" } },
|
||||
{ "keys": ["ctrl+f"], "command": "setTabColor" },
|
||||
])" };
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
|
||||
VERIFY_IS_NOT_NULL(appKeyBindings);
|
||||
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
|
||||
appKeyBindings->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(4u, appKeyBindings->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TabColor());
|
||||
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
|
||||
VERIFY_ARE_EQUAL(static_cast<uint32_t>(til::color(0x563412)), realArgs.TabColor().Value());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestStringOverload()
|
||||
{
|
||||
const std::string bindings0String{ R"([
|
||||
|
||||
@@ -80,6 +80,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TEST_METHOD(TestTrailingCommas);
|
||||
|
||||
TEST_METHOD(TestCommandsAndKeybindings);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -2331,4 +2333,204 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_TRUE(false, L"This call to LayerJson should succeed, even with the trailing comma");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::TestCommandsAndKeybindings()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile1",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"commandline": "pwsh.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile2",
|
||||
"historySize": 3,
|
||||
"commandline": "wsl.exe"
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{ "keys": "ctrl+a", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "name": "ctrl+b", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+c", "name": "ctrl+c", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+d", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+e", "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "keys": "ctrl+f", "name":null, "command": { "action": "splitPane", "split": "horizontal" } }
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
VerifyParseSucceeded(settingsJson);
|
||||
CascadiaSettings settings{};
|
||||
settings._ParseJsonString(settingsJson, false);
|
||||
settings.LayerJson(settings._userSettings);
|
||||
settings._ValidateSettings();
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings.GetProfiles().size());
|
||||
|
||||
const auto profile2Guid = settings._profiles.at(2).GetGuid();
|
||||
VERIFY_ARE_NOT_EQUAL(GUID{ 0 }, profile2Guid);
|
||||
|
||||
auto appKeyBindings = settings._globals._keybindings;
|
||||
VERIFY_ARE_EQUAL(5u, appKeyBindings->_keyShortcuts.size());
|
||||
|
||||
// A/D, B, C, E will be in the list of commands, for 4 total.
|
||||
// * A and D share the same name, so they'll only generate a single action.
|
||||
// * F's name is set manually to `null`
|
||||
auto commands = settings._globals.GetCommands();
|
||||
VERIFY_ARE_EQUAL(4u, commands.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
|
||||
Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set.");
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
|
||||
Log::Comment(L"Now verify the commands");
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: Vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"ctrl+b");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"ctrl+c");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: Horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<ClCompile Include="ProfileTests.cpp" />
|
||||
<ClCompile Include="ColorSchemeTests.cpp" />
|
||||
<ClCompile Include="KeyBindingsTests.cpp" />
|
||||
<ClCompile Include="CommandTests.cpp" />
|
||||
<ClCompile Include="TabTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
#include "ActionArgs.h"
|
||||
#include "ActionAndArgs.h"
|
||||
#include "ActionAndArgs.g.cpp"
|
||||
|
||||
static constexpr std::string_view ActionKey{ "action" };
|
||||
|
||||
// This key is reserved to remove a keybinding, instead of mapping it to an action.
|
||||
static constexpr std::string_view UnboundKey{ "unbound" };
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::string_view CopyTextKey{ "copy" };
|
||||
static constexpr std::string_view PasteTextKey{ "paste" };
|
||||
@@ -33,6 +29,16 @@ static constexpr std::string_view ResizePaneKey{ "resizePane" };
|
||||
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
|
||||
static constexpr std::string_view FindKey{ "find" };
|
||||
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
|
||||
static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
|
||||
static constexpr std::string_view SetTabColorKey{ "setTabColor" };
|
||||
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
|
||||
static constexpr std::string_view RenameTabKey{ "renameTab" };
|
||||
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
|
||||
|
||||
static constexpr std::string_view ActionKey{ "action" };
|
||||
|
||||
// This key is reserved to remove a keybinding, instead of mapping it to an action.
|
||||
static constexpr std::string_view UnboundKey{ "unbound" };
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
@@ -44,7 +50,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// the actual strings being pointed to. However, since both these strings and
|
||||
// the map are all const for the lifetime of the app, we have nothing to worry
|
||||
// about here.
|
||||
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionNamesMap{
|
||||
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionKeyNamesMap{
|
||||
{ CopyTextKey, ShortcutAction::CopyText },
|
||||
{ PasteTextKey, ShortcutAction::PasteText },
|
||||
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
|
||||
@@ -68,8 +74,13 @@ namespace winrt::TerminalApp::implementation
|
||||
{ OpenSettingsKey, ShortcutAction::OpenSettings },
|
||||
{ ToggleFullscreenKey, ShortcutAction::ToggleFullscreen },
|
||||
{ SplitPaneKey, ShortcutAction::SplitPane },
|
||||
{ SetTabColorKey, ShortcutAction::SetTabColor },
|
||||
{ OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker },
|
||||
{ UnboundKey, ShortcutAction::Invalid },
|
||||
{ FindKey, ShortcutAction::Find }
|
||||
{ FindKey, ShortcutAction::Find },
|
||||
{ RenameTabKey, ShortcutAction::RenameTab },
|
||||
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
|
||||
{ ExecuteCommandlineKey, ShortcutAction::ExecuteCommandline },
|
||||
};
|
||||
|
||||
using ParseResult = std::tuple<IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
|
||||
@@ -95,8 +106,14 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
{ ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::ExecuteCommandline, winrt::TerminalApp::implementation::ExecuteCommandlineArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::SetTabColor, winrt::TerminalApp::implementation::SetTabColorArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::RenameTab, winrt::TerminalApp::implementation::RenameTabArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::Invalid, nullptr },
|
||||
};
|
||||
|
||||
@@ -111,8 +128,8 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Try matching the command to one we have. If we can't find the
|
||||
// action name in our list of names, let's just unbind that key.
|
||||
const auto found = ActionAndArgs::ActionNamesMap.find(actionString);
|
||||
return found != ActionAndArgs::ActionNamesMap.end() ? found->second : ShortcutAction::Invalid;
|
||||
const auto found = ActionAndArgs::ActionKeyNamesMap.find(actionString);
|
||||
return found != ActionAndArgs::ActionKeyNamesMap.end() ? found->second : ShortcutAction::Invalid;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -209,4 +226,56 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring ActionAndArgs::GenerateName() const
|
||||
{
|
||||
// Use a magic static to initialize this map, because we won't be able
|
||||
// to load the resources at _init_, only at runtime.
|
||||
static const auto GeneratedActionNames = []() {
|
||||
return std::unordered_map<ShortcutAction, winrt::hstring>{
|
||||
{ ShortcutAction::CopyText, RS_(L"CopyTextCommandKey") },
|
||||
{ ShortcutAction::PasteText, RS_(L"PasteTextCommandKey") },
|
||||
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
|
||||
{ ShortcutAction::DuplicateTab, RS_(L"DuplicateTabCommandKey") },
|
||||
{ ShortcutAction::NewTab, RS_(L"NewTabCommandKey") },
|
||||
{ ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") },
|
||||
{ ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
|
||||
{ ShortcutAction::CloseTab, RS_(L"CloseTabCommandKey") },
|
||||
{ ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
|
||||
{ ShortcutAction::NextTab, RS_(L"NextTabCommandKey") },
|
||||
{ ShortcutAction::PrevTab, RS_(L"PrevTabCommandKey") },
|
||||
{ ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
|
||||
{ ShortcutAction::ResetFontSize, RS_(L"ResetFontSizeCommandKey") },
|
||||
{ ShortcutAction::ScrollUp, RS_(L"ScrollUpCommandKey") },
|
||||
{ ShortcutAction::ScrollDown, RS_(L"ScrollDownCommandKey") },
|
||||
{ ShortcutAction::ScrollUpPage, RS_(L"ScrollUpPageCommandKey") },
|
||||
{ ShortcutAction::ScrollDownPage, RS_(L"ScrollDownPageCommandKey") },
|
||||
{ ShortcutAction::SwitchToTab, RS_(L"SwitchToTabCommandKey") },
|
||||
{ ShortcutAction::ResizePane, RS_(L"ResizePaneCommandKey") },
|
||||
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
|
||||
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
|
||||
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
|
||||
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
|
||||
{ ShortcutAction::Invalid, L"" },
|
||||
{ ShortcutAction::ExecuteCommandline, L"" },
|
||||
{ ShortcutAction::Find, RS_(L"FindCommandKey") },
|
||||
{ ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
|
||||
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
|
||||
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
|
||||
{ ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") },
|
||||
};
|
||||
}();
|
||||
|
||||
if (_Args)
|
||||
{
|
||||
auto nameFromArgs = _Args.GenerateName();
|
||||
if (!nameFromArgs.empty())
|
||||
{
|
||||
return nameFromArgs;
|
||||
}
|
||||
}
|
||||
|
||||
const auto found = GeneratedActionNames.find(_Action);
|
||||
return found != GeneratedActionNames.end() ? found->second : L"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct ActionAndArgs : public ActionAndArgsT<ActionAndArgs>
|
||||
{
|
||||
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionNamesMap;
|
||||
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionKeyNamesMap;
|
||||
static winrt::com_ptr<ActionAndArgs> FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
|
||||
ActionAndArgs() = default;
|
||||
hstring GenerateName() const;
|
||||
|
||||
GETSET_PROPERTY(TerminalApp::ShortcutAction, Action, TerminalApp::ShortcutAction::Invalid);
|
||||
GETSET_PROPERTY(IActionArgs, Args, nullptr);
|
||||
};
|
||||
|
||||
@@ -14,4 +14,257 @@
|
||||
#include "MoveFocusArgs.g.cpp"
|
||||
#include "AdjustFontSizeArgs.g.cpp"
|
||||
#include "SplitPaneArgs.g.cpp"
|
||||
#include "ExecuteCommandlineArgs.g.cpp"
|
||||
#include "OpenSettingsArgs.g.cpp"
|
||||
#include "SetTabColorArgs.g.cpp"
|
||||
#include "RenameTabArgs.g.cpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
winrt::hstring NewTerminalArgs::GenerateName() const
|
||||
{
|
||||
std::wstringstream ss;
|
||||
|
||||
if (!_Profile.empty())
|
||||
{
|
||||
ss << fmt::format(L"profile: {}, ", _Profile);
|
||||
}
|
||||
else if (_ProfileIndex)
|
||||
{
|
||||
ss << fmt::format(L"profile index: {}, ", _ProfileIndex.Value());
|
||||
}
|
||||
|
||||
if (!_Commandline.empty())
|
||||
{
|
||||
ss << fmt::format(L"commandline: {}, ", _Commandline);
|
||||
}
|
||||
|
||||
if (!_StartingDirectory.empty())
|
||||
{
|
||||
ss << fmt::format(L"directory: {}, ", _StartingDirectory);
|
||||
}
|
||||
|
||||
if (!_TabTitle.empty())
|
||||
{
|
||||
ss << fmt::format(L"title: {}, ", _TabTitle);
|
||||
}
|
||||
auto s = ss.str();
|
||||
if (s.empty())
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Chop off the last ", "
|
||||
return winrt::hstring{ s.substr(0, s.size() - 2) };
|
||||
}
|
||||
|
||||
winrt::hstring CopyTextArgs::GenerateName() const
|
||||
{
|
||||
if (_SingleLine)
|
||||
{
|
||||
return RS_(L"CopyTextAsSingleLineCommandKey");
|
||||
}
|
||||
return RS_(L"CopyTextCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring NewTabArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring newTerminalArgsStr;
|
||||
if (_TerminalArgs)
|
||||
{
|
||||
newTerminalArgsStr = _TerminalArgs.GenerateName();
|
||||
}
|
||||
|
||||
if (newTerminalArgsStr.empty())
|
||||
{
|
||||
return RS_(L"NewTabCommandKey");
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(L"{}, {}", RS_(L"NewTabCommandKey"), newTerminalArgsStr)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring SwitchToTabArgs::GenerateName() const
|
||||
{
|
||||
return winrt::hstring{
|
||||
fmt::format(L"{}, index:{}", RS_(L"SwitchToTabCommandKey"), _TabIndex)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring ResizePaneArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring directionString;
|
||||
switch (_Direction)
|
||||
{
|
||||
case Direction::Left:
|
||||
directionString = RS_(L"DirectionLeft");
|
||||
break;
|
||||
case Direction::Right:
|
||||
directionString = RS_(L"DirectionRight");
|
||||
break;
|
||||
case Direction::Up:
|
||||
directionString = RS_(L"DirectionUp");
|
||||
break;
|
||||
case Direction::Down:
|
||||
directionString = RS_(L"DirectionDown");
|
||||
break;
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"ResizePaneWithArgCommandKey")),
|
||||
directionString)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring MoveFocusArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring directionString;
|
||||
switch (_Direction)
|
||||
{
|
||||
case Direction::Left:
|
||||
directionString = RS_(L"DirectionLeft");
|
||||
break;
|
||||
case Direction::Right:
|
||||
directionString = RS_(L"DirectionRight");
|
||||
break;
|
||||
case Direction::Up:
|
||||
directionString = RS_(L"DirectionUp");
|
||||
break;
|
||||
case Direction::Down:
|
||||
directionString = RS_(L"DirectionDown");
|
||||
break;
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"MoveFocusWithArgCommandKey")),
|
||||
directionString)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring AdjustFontSizeArgs::GenerateName() const
|
||||
{
|
||||
// If the amount is just 1 (or -1), we'll just return "Increase font
|
||||
// size" (or "Decrease font size"). If the amount delta has a greater
|
||||
// absolute value, we'll include it like"
|
||||
// * Decrease font size, amount: {delta}"
|
||||
if (_Delta < 0)
|
||||
{
|
||||
return _Delta == -1 ? RS_(L"DecreaseFontSizeCommandKey") :
|
||||
winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"DecreaseFontSizeWithAmountCommandKey")),
|
||||
-_Delta)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return _Delta == 1 ? RS_(L"IncreaseFontSizeCommandKey") :
|
||||
winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"IncreaseFontSizeWithAmountCommandKey")),
|
||||
_Delta)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring SplitPaneArgs::GenerateName() const
|
||||
{
|
||||
// The string will be similar to the following:
|
||||
// * "Duplicate pane[, split: <direction>][, new terminal arguments...]"
|
||||
// * "Split pane[, split: <direction>][, new terminal arguments...]"
|
||||
//
|
||||
// Direction will only be added to the string if the split direction is
|
||||
// not "auto".
|
||||
// If this is a "duplicate pane" action, then the new terminal arguments
|
||||
// will be omitted (as they're unused)
|
||||
|
||||
std::wstringstream ss;
|
||||
if (_SplitMode == SplitType::Duplicate)
|
||||
{
|
||||
ss << std::wstring_view(RS_(L"DuplicatePaneCommandKey"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << std::wstring_view(RS_(L"SplitPaneCommandKey"));
|
||||
}
|
||||
ss << L", ";
|
||||
|
||||
// This text is intentionally _not_ localized, to attempt to mirror the
|
||||
// exact syntax that the property would have in JSON.
|
||||
switch (_SplitStyle)
|
||||
{
|
||||
case SplitState::Vertical:
|
||||
ss << L"split: vertical, ";
|
||||
break;
|
||||
case SplitState::Horizontal:
|
||||
ss << L"split: horizontal, ";
|
||||
break;
|
||||
}
|
||||
|
||||
winrt::hstring newTerminalArgsStr;
|
||||
if (_TerminalArgs)
|
||||
{
|
||||
newTerminalArgsStr = _TerminalArgs.GenerateName();
|
||||
}
|
||||
|
||||
if (_SplitMode != SplitType::Duplicate && !newTerminalArgsStr.empty())
|
||||
{
|
||||
ss << newTerminalArgsStr.c_str();
|
||||
ss << L", ";
|
||||
}
|
||||
|
||||
// Chop off the last ", "
|
||||
auto s = ss.str();
|
||||
return winrt::hstring{ s.substr(0, s.size() - 2) };
|
||||
}
|
||||
|
||||
winrt::hstring OpenSettingsArgs::GenerateName() const
|
||||
{
|
||||
switch (_Target)
|
||||
{
|
||||
case SettingsTarget::DefaultsFile:
|
||||
return RS_(L"OpenDefaultSettingsCommandKey");
|
||||
case SettingsTarget::AllFiles:
|
||||
return RS_(L"OpenBothSettingsFilesCommandKey");
|
||||
default:
|
||||
return RS_(L"OpenSettingsCommandKey");
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring ExecuteCommandlineArgs::GenerateName() const
|
||||
{
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"ExecuteCommandlineCommandKey")),
|
||||
_Commandline)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring SetTabColorArgs::GenerateName() const
|
||||
{
|
||||
// "Set tab color to #RRGGBB"
|
||||
// "Reset tab color"
|
||||
if (_TabColor)
|
||||
{
|
||||
til::color c{ _TabColor.Value() };
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"SetTabColorCommandKey")),
|
||||
c.ToHexString(true))
|
||||
};
|
||||
}
|
||||
|
||||
return RS_(L"ResetTabColorCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring RenameTabArgs::GenerateName() const
|
||||
{
|
||||
// "Rename tab to \"{_Title}\""
|
||||
// "Reset tab title"
|
||||
if (!_Title.empty())
|
||||
{
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"RenameTabCommandKey")),
|
||||
_Title.c_str())
|
||||
};
|
||||
}
|
||||
return RS_(L"ResetTabNameCommandKey");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,10 +14,14 @@
|
||||
#include "MoveFocusArgs.g.h"
|
||||
#include "AdjustFontSizeArgs.g.h"
|
||||
#include "SplitPaneArgs.g.h"
|
||||
#include "ExecuteCommandlineArgs.g.h"
|
||||
#include "OpenSettingsArgs.g.h"
|
||||
#include "SetTabColorArgs.g.h"
|
||||
#include "RenameTabArgs.g.h"
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "Utils.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalWarnings.h"
|
||||
|
||||
// Notes on defining ActionArgs and ActionEventArgs:
|
||||
@@ -56,6 +60,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view ProfileKey{ "profile" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const winrt::TerminalApp::NewTerminalArgs& other)
|
||||
{
|
||||
return other.Commandline() == _Commandline &&
|
||||
@@ -100,6 +106,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view SingleLineKey{ "singleLine" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<CopyTextArgs>();
|
||||
@@ -127,6 +135,8 @@ namespace winrt::TerminalApp::implementation
|
||||
GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr);
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<NewTabArgs>();
|
||||
@@ -153,6 +163,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view TabIndexKey{ "index" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SwitchToTabArgs>();
|
||||
@@ -217,6 +229,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view DirectionKey{ "direction" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<ResizePaneArgs>();
|
||||
@@ -253,6 +267,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view DirectionKey{ "direction" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<MoveFocusArgs>();
|
||||
@@ -289,6 +305,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view AdjustFontSizeDelta{ "delta" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<AdjustFontSizeArgs>();
|
||||
@@ -355,13 +373,16 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view SplitModeKey{ "splitMode" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SplitPaneArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_SplitStyle == _SplitStyle &&
|
||||
otherAsUs->_TerminalArgs == _TerminalArgs &&
|
||||
(otherAsUs->_TerminalArgs ? otherAsUs->_TerminalArgs.Equals(_TerminalArgs) :
|
||||
otherAsUs->_TerminalArgs == _TerminalArgs) &&
|
||||
otherAsUs->_SplitMode == _SplitMode;
|
||||
}
|
||||
return false;
|
||||
@@ -383,6 +404,37 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
};
|
||||
|
||||
struct ExecuteCommandlineArgs : public ExecuteCommandlineArgsT<ExecuteCommandlineArgs>
|
||||
{
|
||||
ExecuteCommandlineArgs() = default;
|
||||
GETSET_PROPERTY(winrt::hstring, Commandline, false);
|
||||
|
||||
static constexpr std::string_view CommandlineKey{ "commandline" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<ExecuteCommandlineArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_Commandline == _Commandline;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<ExecuteCommandlineArgs>();
|
||||
if (auto commandline{ json[JsonKey(CommandlineKey)] })
|
||||
{
|
||||
args->_Commandline = winrt::to_hstring(commandline.asString());
|
||||
}
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
// Possible SettingsTarget values
|
||||
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
|
||||
static constexpr std::string_view SettingsFileString{ "settingsFile" };
|
||||
@@ -421,6 +473,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view TargetKey{ "target" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<OpenSettingsArgs>();
|
||||
@@ -441,6 +495,74 @@ namespace winrt::TerminalApp::implementation
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
struct SetTabColorArgs : public SetTabColorArgsT<SetTabColorArgs>
|
||||
{
|
||||
SetTabColorArgs() = default;
|
||||
GETSET_PROPERTY(Windows::Foundation::IReference<uint32_t>, TabColor, nullptr);
|
||||
|
||||
static constexpr std::string_view ColorKey{ "color" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SetTabColorArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_TabColor == _TabColor;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<SetTabColorArgs>();
|
||||
std::optional<til::color> temp;
|
||||
try
|
||||
{
|
||||
::TerminalApp::JsonUtils::GetOptionalColor(json, ColorKey, temp);
|
||||
if (temp.has_value())
|
||||
{
|
||||
args->_TabColor = static_cast<uint32_t>(temp.value());
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
struct RenameTabArgs : public RenameTabArgsT<RenameTabArgs>
|
||||
{
|
||||
RenameTabArgs() = default;
|
||||
GETSET_PROPERTY(winrt::hstring, Title, L"");
|
||||
|
||||
static constexpr std::string_view TitleKey{ "title" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<RenameTabArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_Title == _Title;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<RenameTabArgs>();
|
||||
if (auto title{ json[JsonKey(TitleKey)] })
|
||||
{
|
||||
args->_Title = winrt::to_hstring(title.asString());
|
||||
}
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace TerminalApp
|
||||
interface IActionArgs
|
||||
{
|
||||
Boolean Equals(IActionArgs other);
|
||||
String GenerateName();
|
||||
};
|
||||
|
||||
interface IActionEventArgs
|
||||
@@ -53,7 +54,9 @@ namespace TerminalApp
|
||||
// ProfileIndex can be null (for "use the default"), so this needs to be
|
||||
// a IReference, so it's nullable
|
||||
Windows.Foundation.IReference<Int32> ProfileIndex { get; };
|
||||
|
||||
Boolean Equals(NewTerminalArgs other);
|
||||
String GenerateName();
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs
|
||||
@@ -98,8 +101,23 @@ namespace TerminalApp
|
||||
SplitType SplitMode { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ExecuteCommandlineArgs : IActionArgs
|
||||
{
|
||||
String Commandline;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass OpenSettingsArgs : IActionArgs
|
||||
{
|
||||
SettingsTarget Target { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass SetTabColorArgs : IActionArgs
|
||||
{
|
||||
Windows.Foundation.IReference<UInt32> TabColor { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass RenameTabArgs : IActionArgs
|
||||
{
|
||||
String Title { get; };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -237,4 +237,115 @@ namespace winrt::TerminalApp::implementation
|
||||
ToggleFullscreen();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleCommandPalette(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
// TODO GH#6677: When we add support for commandline mode, first set the
|
||||
// mode that the command palette should be in, before making it visible.
|
||||
CommandPalette().Visibility(CommandPalette().Visibility() == Visibility::Visible ?
|
||||
Visibility::Collapsed :
|
||||
Visibility::Visible);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleSetTabColor(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
std::optional<til::color> tabColor;
|
||||
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::SetTabColorArgs>())
|
||||
{
|
||||
if (realArgs.TabColor() != nullptr)
|
||||
{
|
||||
tabColor = realArgs.TabColor().Value();
|
||||
}
|
||||
}
|
||||
|
||||
auto activeTab = _GetFocusedTab();
|
||||
if (activeTab)
|
||||
{
|
||||
if (tabColor.has_value())
|
||||
{
|
||||
activeTab->SetTabColor(tabColor.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTab->ResetTabColor();
|
||||
}
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
auto activeTab = _GetFocusedTab();
|
||||
if (activeTab)
|
||||
{
|
||||
activeTab->ActivateColorPicker();
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleRenameTab(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
std::optional<winrt::hstring> title;
|
||||
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::RenameTabArgs>())
|
||||
{
|
||||
title = realArgs.Title();
|
||||
}
|
||||
|
||||
auto activeTab = _GetFocusedTab();
|
||||
if (activeTab)
|
||||
{
|
||||
if (title.has_value())
|
||||
{
|
||||
activeTab->SetTabText(title.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTab->ResetTabText();
|
||||
}
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleExecuteCommandline(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& actionArgs)
|
||||
{
|
||||
if (const auto& realArgs = actionArgs.ActionArgs().try_as<TerminalApp::ExecuteCommandlineArgs>())
|
||||
{
|
||||
// Convert the commandline into an array of args with
|
||||
// CommandLineToArgvW, similar to how the app typically does when
|
||||
// called from the commandline.
|
||||
int argc = 0;
|
||||
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(realArgs.Commandline().c_str(), &argc) };
|
||||
if (argv)
|
||||
{
|
||||
std::vector<winrt::hstring> args;
|
||||
|
||||
// Make sure the first argument is wt.exe, because ParseArgs
|
||||
// will always skip the program name.
|
||||
args.emplace_back(L"wt.exe");
|
||||
for (auto& elem : wil::make_range(argv.get(), argc))
|
||||
{
|
||||
args.emplace_back(elem);
|
||||
}
|
||||
winrt::array_view<const winrt::hstring> argsView{ args };
|
||||
|
||||
::TerminalApp::AppCommandlineArgs appArgs;
|
||||
bool handled = appArgs.ParseArgs(argsView) == 0;
|
||||
if (handled)
|
||||
{
|
||||
_startupActions = appArgs.GetStartupActions();
|
||||
_ProcessStartupActions(false);
|
||||
}
|
||||
|
||||
actionArgs.Handled(handled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,3 +637,52 @@ std::optional<winrt::TerminalApp::LaunchMode> AppCommandlineArgs::GetLaunchMode(
|
||||
{
|
||||
return _launchMode;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to parse an array of commandline args into a list of
|
||||
// commands to execute, and then parses these commands. As commands are
|
||||
// successfully parsed, they will generate ShortcutActions for us to be
|
||||
// able to execute. If we fail to parse any commands, we'll return the
|
||||
// error code from the failure to parse that command, and stop processing
|
||||
// additional commands.
|
||||
// - The first arg in args should be the program name "wt" (or some variant). It
|
||||
// will be ignored during parsing.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// Return Value:
|
||||
// - 0 if the commandline was successfully parsed
|
||||
int AppCommandlineArgs::ParseArgs(winrt::array_view<const winrt::hstring>& args)
|
||||
{
|
||||
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
|
||||
|
||||
for (auto& cmdBlob : commands)
|
||||
{
|
||||
// On one hand, it seems like we should be able to have one
|
||||
// AppCommandlineArgs for parsing all of them, and collect the
|
||||
// results one at a time.
|
||||
//
|
||||
// On the other hand, re-using a CLI::App seems to leave state from
|
||||
// previous parsings around, so we could get mysterious behavior
|
||||
// where one command affects the values of the next.
|
||||
//
|
||||
// From https://cliutils.github.io/CLI11/book/chapters/options.html:
|
||||
// > If that option is not given, CLI11 will not touch the initial
|
||||
// > value. This allows you to set up defaults by simply setting
|
||||
// > your value beforehand.
|
||||
//
|
||||
// So we pretty much need the to either manually reset the state
|
||||
// each command, or build new ones.
|
||||
const auto result = ParseCommand(cmdBlob);
|
||||
|
||||
// If this succeeded, result will be 0. Otherwise, the caller should
|
||||
// exit(result), to exit the program.
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// If all the args were successfully parsed, we'll have some commands
|
||||
// built in _appArgs, which we'll use when the application starts up.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ public:
|
||||
|
||||
AppCommandlineArgs();
|
||||
~AppCommandlineArgs() = default;
|
||||
|
||||
int ParseCommand(const Commandline& command);
|
||||
int ParseArgs(winrt::array_view<const winrt::hstring>& args);
|
||||
|
||||
static std::vector<Commandline> BuildCommands(const std::vector<const wchar_t*>& args);
|
||||
static std::vector<Commandline> BuildCommands(winrt::array_view<const winrt::hstring>& args);
|
||||
|
||||
@@ -55,8 +55,11 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
for (auto& kv : _keyShortcuts)
|
||||
{
|
||||
if (kv.second.Action() == actionAndArgs.Action() &&
|
||||
kv.second.Args().Equals(actionAndArgs.Args()))
|
||||
const auto action = kv.second.Action();
|
||||
const auto args = kv.second.Args();
|
||||
const auto actionMatched = action == actionAndArgs.Action();
|
||||
const auto argsMatched = args ? args.Equals(actionAndArgs.Args()) : args == actionAndArgs.Args();
|
||||
if (actionMatched && argsMatched)
|
||||
{
|
||||
return kv.first;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
|
||||
|
||||
// Iterate over all the possible actions in the names list, and see if
|
||||
// it has a binding.
|
||||
for (auto& actionName : ActionAndArgs::ActionNamesMap)
|
||||
for (auto& actionName : ActionAndArgs::ActionKeyNamesMap)
|
||||
{
|
||||
const auto searchedForName = actionName.first;
|
||||
const auto searchedForAction = actionName.second;
|
||||
@@ -85,7 +85,7 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
|
||||
// - Deserialize an AppKeyBindings from the key mappings that are in the array
|
||||
// `json`. The json array should contain an array of objects with both a
|
||||
// `command` string and a `keys` array, where `command` is one of the names
|
||||
// listed in `ActionAndArgs::ActionNamesMap`, and `keys` is an array of
|
||||
// listed in `ActionAndArgs::ActionKeyNamesMap`, and `keys` is an array of
|
||||
// keypresses. Currently, the array should contain a single string, which can
|
||||
// be deserialized into a KeyChord.
|
||||
// - Applies the deserialized keybindings to the provided `bindings` object. If
|
||||
|
||||
@@ -472,7 +472,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a point containing the requested dimensions in pixels.
|
||||
winrt::Windows::Foundation::Point AppLogic::GetLaunchDimensions(uint32_t dpi)
|
||||
winrt::Windows::Foundation::Size AppLogic::GetLaunchDimensions(uint32_t dpi)
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
@@ -502,7 +502,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// of the height calculation here.
|
||||
auto titlebar = TitlebarControl{ static_cast<uint64_t>(0) };
|
||||
titlebar.Measure({ SHRT_MAX, SHRT_MAX });
|
||||
proposedSize.Y += (titlebar.DesiredSize().Height) * scale;
|
||||
proposedSize.Height += (titlebar.DesiredSize().Height) * scale;
|
||||
}
|
||||
else if (_settings->GlobalSettings().AlwaysShowTabs())
|
||||
{
|
||||
@@ -517,7 +517,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// For whatever reason, there's about 6px of unaccounted-for space
|
||||
// in the application. I couldn't tell you where these 6px are
|
||||
// coming from, but they need to be included in this math.
|
||||
proposedSize.Y += (tabControl.DesiredSize().Height + 6) * scale;
|
||||
proposedSize.Width += (tabControl.DesiredSize().Height + 6) * scale;
|
||||
}
|
||||
|
||||
return proposedSize;
|
||||
@@ -972,7 +972,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// or 0. (see AppLogic::_ParseArgs)
|
||||
int32_t AppLogic::SetStartupCommandline(array_view<const winrt::hstring> args)
|
||||
{
|
||||
const auto result = _ParseArgs(args);
|
||||
const auto result = _appArgs.ParseArgs(args);
|
||||
if (result == 0)
|
||||
{
|
||||
_appArgs.ValidateStartupCommands();
|
||||
@@ -982,53 +982,6 @@ namespace winrt::TerminalApp::implementation
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to parse an array of commandline args into a list of
|
||||
// commands to execute, and then parses these commands. As commands are
|
||||
// successfully parsed, they will generate ShortcutActions for us to be
|
||||
// able to execute. If we fail to parse any commands, we'll return the
|
||||
// error code from the failure to parse that command, and stop processing
|
||||
// additional commands.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// Return Value:
|
||||
// - 0 if the commandline was successfully parsed
|
||||
int AppLogic::_ParseArgs(winrt::array_view<const hstring>& args)
|
||||
{
|
||||
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
|
||||
|
||||
for (auto& cmdBlob : commands)
|
||||
{
|
||||
// On one hand, it seems like we should be able to have one
|
||||
// AppCommandlineArgs for parsing all of them, and collect the
|
||||
// results one at a time.
|
||||
//
|
||||
// On the other hand, re-using a CLI::App seems to leave state from
|
||||
// previous parsings around, so we could get mysterious behavior
|
||||
// where one command affects the values of the next.
|
||||
//
|
||||
// From https://cliutils.github.io/CLI11/book/chapters/options.html:
|
||||
// > If that option is not given, CLI11 will not touch the initial
|
||||
// > value. This allows you to set up defaults by simply setting
|
||||
// > your value beforehand.
|
||||
//
|
||||
// So we pretty much need the to either manually reset the state
|
||||
// each command, or build new ones.
|
||||
const auto result = _appArgs.ParseCommand(cmdBlob);
|
||||
|
||||
// If this succeeded, result will be 0. Otherwise, the caller should
|
||||
// exit(result), to exit the program.
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// If all the args were successfully parsed, we'll have some commands
|
||||
// built in _appArgs, which we'll use when the application starts up.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - If there were any errors parsing the commandline that was used to
|
||||
// initialize the terminal, this will return a string containing that
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::hstring ApplicationDisplayName() const;
|
||||
winrt::hstring ApplicationVersion() const;
|
||||
|
||||
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
|
||||
Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi);
|
||||
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
|
||||
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
|
||||
LaunchMode GetLaunchMode();
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace TerminalApp
|
||||
String ApplicationDisplayName { get; };
|
||||
String ApplicationVersion { get; };
|
||||
|
||||
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
|
||||
Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi);
|
||||
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
|
||||
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
|
||||
LaunchMode GetLaunchMode();
|
||||
|
||||
191
src/cascadia/TerminalApp/Command.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Command.h"
|
||||
#include "Command.g.cpp"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "ActionAndArgs.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::TerminalApp;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view IconPathKey{ "iconPath" };
|
||||
static constexpr std::string_view ActionKey{ "command" };
|
||||
static constexpr std::string_view ArgsKey{ "args" };
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Function Description:
|
||||
// - attempt to get the name of this command from the provided json object.
|
||||
// * If the "name" property is a string, return that value.
|
||||
// * If the "name" property is an object, attempt to lookup the string
|
||||
// resource specified by the "key" property, to support localizable
|
||||
// command names.
|
||||
// Arguments:
|
||||
// - json: The Json::Value representing the command object we should get the name for.
|
||||
// Return Value:
|
||||
// - the empty string if we couldn't find a name, otherwise the command's name.
|
||||
static winrt::hstring _nameFromJson(const Json::Value& json)
|
||||
{
|
||||
if (const auto name{ json[JsonKey(NameKey)] })
|
||||
{
|
||||
if (name.isObject())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (const auto keyJson{ name[JsonKey("key")] })
|
||||
{
|
||||
// Make sure the key is present before we try
|
||||
// loading it. Otherwise we'll crash
|
||||
const auto resourceKey = GetWstringFromJson(keyJson);
|
||||
if (HasLibraryResourceWithName(resourceKey))
|
||||
{
|
||||
return GetLibraryResourceString(resourceKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
else if (name.isString())
|
||||
{
|
||||
auto nameStr = name.asString();
|
||||
return winrt::to_hstring(nameStr);
|
||||
}
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the name for the command specified in `json`. If there is no "name"
|
||||
// property in the provided json object, then instead generate a name for
|
||||
// the provided ActionAndArgs.
|
||||
// Arguments:
|
||||
// - json: json for the command to generate a name for.
|
||||
// - actionAndArgs: An ActionAndArgs object to use to generate a name for,
|
||||
// if the json object doesn't contain a "name".
|
||||
// Return Value:
|
||||
// - The "name" from the json, or the generated name from ActionAndArgs::GenerateName
|
||||
static winrt::hstring _nameFromJsonOrAction(const Json::Value& json,
|
||||
winrt::com_ptr<ActionAndArgs> actionAndArgs)
|
||||
{
|
||||
auto manualName = _nameFromJson(json);
|
||||
if (!manualName.empty())
|
||||
{
|
||||
return manualName;
|
||||
}
|
||||
if (!actionAndArgs)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
return actionAndArgs->GenerateName();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Deserialize a Command from the `json` object. The json object should
|
||||
// contain a "name" and "action", and optionally an "icon".
|
||||
// * "name": string|object - the name of the command to display in the
|
||||
// command palette. If this is an object, look for the "key" property,
|
||||
// and try to load the string from our resources instead.
|
||||
// * "action": string|object - A ShortcutAction, either as a name or as an
|
||||
// ActionAndArgs serialization. See ActionAndArgs::FromJson for details.
|
||||
// If this is null, we'll remove this command from the list of commands.
|
||||
// Arguments:
|
||||
// - json: the Json::Value to deserialize into a Command
|
||||
// - warnings: If there were any warnings during parsing, they'll be
|
||||
// appended to this vector.
|
||||
// Return Value:
|
||||
// - the newly constructed Command object.
|
||||
winrt::com_ptr<Command> Command::FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
|
||||
{
|
||||
auto result = winrt::make_self<Command>();
|
||||
|
||||
// TODO GH#6644: iconPath not implemented quite yet. Can't seem to get
|
||||
// the binding quite right. Additionally, do we want it to be an image,
|
||||
// or a FontIcon? I've had difficulty binding either/or.
|
||||
|
||||
if (const auto actionJson{ json[JsonKey(ActionKey)] })
|
||||
{
|
||||
auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings);
|
||||
|
||||
if (actionAndArgs)
|
||||
{
|
||||
result->_setAction(*actionAndArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something like
|
||||
// { name: "foo", action: "unbound" }
|
||||
// will _remove_ the "foo" command, by returning null here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result->_setName(_nameFromJsonOrAction(json, actionAndArgs));
|
||||
}
|
||||
else
|
||||
{
|
||||
// { name: "foo", action: null } will land in this case, which
|
||||
// should also be used for unbinding.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (result->_Name.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Attempt to parse all the json objects in `json` into new Command
|
||||
// objects, and add them to the map of commands.
|
||||
// - If any parsed command has
|
||||
// the same Name as an existing command in commands, the new one will
|
||||
// layer on top of the existing one.
|
||||
// Arguments:
|
||||
// - commands: a map of Name->Command which new commands should be layered upon.
|
||||
// - json: A Json::Value containing an array of serialized commands
|
||||
// Return Value:
|
||||
// - A vector containing any warnings detected while parsing
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> Command::LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json)
|
||||
{
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> warnings;
|
||||
|
||||
for (const auto& value : json)
|
||||
{
|
||||
if (value.isObject())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = Command::FromJson(value, warnings);
|
||||
if (result)
|
||||
{
|
||||
// Override commands with the same name
|
||||
commands.insert_or_assign(result->Name(), *result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there wasn't a parsed command, then try to get the
|
||||
// name from the json blob. If that name currently
|
||||
// exists in our list of commands, we should remove it.
|
||||
const auto name = _nameFromJson(value);
|
||||
if (!name.empty())
|
||||
{
|
||||
commands.erase(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
}
|
||||
45
src/cascadia/TerminalApp/Command.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- Command.h
|
||||
|
||||
Abstract:
|
||||
- A command represents a single entry in the Command Palette. This is an object
|
||||
that has a user facing "name" to display to the user, and an associated action
|
||||
which can be dispatched.
|
||||
|
||||
- For more information, see GH#2046, #5400, #5674, and #6635
|
||||
|
||||
Author(s):
|
||||
- Mike Griese - June 2020
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "Command.g.h"
|
||||
#include "TerminalWarnings.h"
|
||||
#include "..\inc\cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct Command : CommandT<Command>
|
||||
{
|
||||
Command() = default;
|
||||
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json, std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::ActionAndArgs, Action, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, KeyChordText, _PropertyChangedHandlers);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(Command);
|
||||
}
|
||||
16
src/cascadia/TerminalApp/Command.idl
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../ShortcutActionDispatch.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass Command : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
Command();
|
||||
|
||||
String Name;
|
||||
ActionAndArgs Action;
|
||||
String KeyChordText;
|
||||
}
|
||||
}
|
||||
480
src/cascadia/TerminalApp/CommandPalette.cpp
Normal file
@@ -0,0 +1,480 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "CommandPalette.h"
|
||||
#include "ActionArgs.h"
|
||||
#include "ActionAndArgs.h"
|
||||
|
||||
#include "CommandPalette.g.cpp"
|
||||
#include <winrt/Microsoft.Terminal.Settings.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
CommandPalette::CommandPalette()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::Command>();
|
||||
_allActions = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
|
||||
if (CommandPaletteShadow())
|
||||
{
|
||||
// Hook up the shadow on the command palette to the backdrop that
|
||||
// will actually show it. This needs to be done at runtime, and only
|
||||
// if the shadow actually exists. ThemeShadow isn't supported below
|
||||
// version 18362.
|
||||
CommandPaletteShadow().Receivers().Append(_shadowBackdrop());
|
||||
// "raise" the command palette up by 16 units, so it will cast a shadow.
|
||||
_backdrop().Translation({ 0, 0, 16 });
|
||||
}
|
||||
|
||||
// Whatever is hosting us will enable us by setting our visibility to
|
||||
// "Visible". When that happens, set focus to our search box.
|
||||
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (Visibility() == Visibility::Visible)
|
||||
{
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Raise an event to return control to the Terminal.
|
||||
_close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Moves the focus up or down the list of commands. If we're at the top,
|
||||
// we'll loop around to the bottom, and vice-versa.
|
||||
// Arguments:
|
||||
// - moveDown: if true, we're attempting to move to the next item in the
|
||||
// list. Otherwise, we're attempting to move to the previous.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_selectNextItem(const bool moveDown)
|
||||
{
|
||||
const auto selected = _filteredActionsView().SelectedIndex();
|
||||
const int numItems = ::base::saturated_cast<int>(_filteredActionsView().Items().Size());
|
||||
// Wraparound math. By adding numItems and then calculating modulo numItems,
|
||||
// we clamp the values to the range [0, numItems) while still supporting moving
|
||||
// upward from 0 to numItems - 1.
|
||||
const auto newIndex = ((numItems + selected + (moveDown ? 1 : -1)) % numItems);
|
||||
_filteredActionsView().SelectedIndex(newIndex);
|
||||
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Process keystrokes in the input box. This is used for moving focus up
|
||||
// and down the list of commands in Action mode, and for executing
|
||||
// commands in both Action mode and Commandline mode.
|
||||
// Arguments:
|
||||
// - e: the KeyRoutedEventArgs containing info about the keystroke.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_keyDownHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
|
||||
if (key == VirtualKey::Up)
|
||||
{
|
||||
// Action Mode: Move focus to the next item in the list.
|
||||
_selectNextItem(false);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Down)
|
||||
{
|
||||
// Action Mode: Move focus to the previous item in the list.
|
||||
_selectNextItem(true);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Enter)
|
||||
{
|
||||
if (_mode == PaletteMode::ActionMode)
|
||||
{
|
||||
// Action Mode: Dispatch the action of the selected command.
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
{
|
||||
_dispatchAction();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_dispatchCommandline();
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Escape)
|
||||
{
|
||||
// Action Mode: Dismiss the palette if the text is empty, otherwise clear the search string.
|
||||
if (_searchBox().Text().empty())
|
||||
{
|
||||
_close();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox().Text(L"");
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is triggered when someone clicks anywhere in the bounds of
|
||||
// the window that's _not_ the command palette UI. When that happens,
|
||||
// we'll want to dismiss the palette.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_rootPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /*e*/)
|
||||
{
|
||||
_close();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is only triggered when someone clicks in the space right
|
||||
// next to the text box in the command palette. We _don't_ want that click
|
||||
// to light dismiss the palette, so we'll mark it handled here.
|
||||
// Arguments:
|
||||
// - e: the PointerRoutedEventArgs that we want to mark as handled
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_backdropPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e)
|
||||
{
|
||||
e.Handled(true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is called when the user clicks on an individual item from
|
||||
// the list. We'll get the item that was clicked and dispatch the command
|
||||
// that the user clicked on.
|
||||
// Arguments:
|
||||
// - e: an ItemClickEventArgs who's ClickedItem() will be the command that was clicked on.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_listItemClicked(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Controls::ItemClickEventArgs const& e)
|
||||
{
|
||||
if (auto command{ e.ClickedItem().try_as<TerminalApp::Command>() })
|
||||
{
|
||||
const auto actionAndArgs = command.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
_close();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for when the text in the input box changes. In Action
|
||||
// Mode, we'll update the list of displayed commands, and select the first one.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_filterTextChanged(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
|
||||
{
|
||||
_checkMode();
|
||||
_updateFilteredActions();
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
|
||||
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
|
||||
Collections::IObservableVector<Command> CommandPalette::FilteredActions()
|
||||
{
|
||||
return _filteredActions;
|
||||
}
|
||||
|
||||
void CommandPalette::SetActions(Collections::IVector<TerminalApp::Command> const& actions)
|
||||
{
|
||||
_allActions = actions;
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
static bool _compareCommandNames(const TerminalApp::Command& lhs, const TerminalApp::Command& rhs)
|
||||
{
|
||||
std::wstring_view leftName{ lhs.Name() };
|
||||
std::wstring_view rightName{ rhs.Name() };
|
||||
return leftName.compare(rightName) < 0;
|
||||
}
|
||||
|
||||
// This is a helper struct to aid in sorting Commands by a given weighting.
|
||||
struct WeightedCommand
|
||||
{
|
||||
TerminalApp::Command command;
|
||||
int weight;
|
||||
|
||||
bool operator<(const WeightedCommand& other) const
|
||||
{
|
||||
// If two commands have the same weight, then we'll sort them alphabetically.
|
||||
if (weight == other.weight)
|
||||
{
|
||||
return !_compareCommandNames(command, other.command);
|
||||
}
|
||||
return weight < other.weight;
|
||||
}
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
// - Update our list of filtered actions to reflect the current contents of
|
||||
// the input box. For more details on which commands will be displayed,
|
||||
// see `_getWeight`.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_updateFilteredActions()
|
||||
{
|
||||
_filteredActions.Clear();
|
||||
|
||||
if (_mode == PaletteMode::CommandlineMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto searchText = _searchBox().Text();
|
||||
const bool addAll = searchText.empty();
|
||||
|
||||
// If there's no filter text, then just add all the commands in order to the list.
|
||||
// - TODO GH#6647:Possibly add the MRU commands first in order, followed
|
||||
// by the rest of the commands.
|
||||
if (addAll)
|
||||
{
|
||||
// Add all the commands, but make sure they're sorted alphabetically.
|
||||
std::vector<TerminalApp::Command> sortedCommands;
|
||||
sortedCommands.reserve(_allActions.Size());
|
||||
|
||||
for (auto action : _allActions)
|
||||
{
|
||||
sortedCommands.push_back(action);
|
||||
}
|
||||
std::sort(sortedCommands.begin(),
|
||||
sortedCommands.end(),
|
||||
_compareCommandNames);
|
||||
|
||||
for (auto action : sortedCommands)
|
||||
{
|
||||
_filteredActions.Append(action);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Here, there was some filter text.
|
||||
// Show these actions in a weighted order.
|
||||
// - Matching the first character of a word, then the first char of a
|
||||
// subsequent word seems better than just "the order they appear in
|
||||
// the list".
|
||||
// - TODO GH#6647:"Recently used commands" ordering also seems valuable.
|
||||
// * This could be done by weighting the recently used commands
|
||||
// higher the more recently they were used, then weighting all
|
||||
// the unused commands as 1
|
||||
|
||||
// Use a priority queue to order commands so that "better" matches
|
||||
// appear first in the list. The ordering will be determined by the
|
||||
// match weight produced by _getWeight.
|
||||
std::priority_queue<WeightedCommand> heap;
|
||||
for (auto action : _allActions)
|
||||
{
|
||||
const auto weight = CommandPalette::_getWeight(searchText, action.Name());
|
||||
if (weight > 0)
|
||||
{
|
||||
WeightedCommand wc;
|
||||
wc.command = action;
|
||||
wc.weight = weight;
|
||||
heap.push(wc);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, all the commands in heap are matches. We've also
|
||||
// sorted commands with the same weight alphabetically.
|
||||
// Remove everything in-order from the queue, and add to the list of
|
||||
// filtered actions.
|
||||
while (!heap.empty())
|
||||
{
|
||||
auto top = heap.top();
|
||||
heap.pop();
|
||||
_filteredActions.Append(top.command);
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Calculates a "weighting" by which should be used to order a command
|
||||
// name relative to other names, given a specific search string.
|
||||
// Currently, this is based off of two factors:
|
||||
// * The weight is incremented once for each matched character of the
|
||||
// search text.
|
||||
// * If a matching character from the search text was found at the start
|
||||
// of a word in the name, then we increment the weight again.
|
||||
// * For example, for a search string "sp", we want "Split Pane" to
|
||||
// appear in the list before "Close Pane"
|
||||
// * Consecutive matches will be weighted higher than matches with
|
||||
// characters in between the search characters.
|
||||
// - This will return 0 if the command should not be shown. If all the
|
||||
// characters of search text appear in order in `name`, then this function
|
||||
// will return a positive number. There can be any number of characters
|
||||
// separating consecutive characters in searchText.
|
||||
// * For example:
|
||||
// "name": "New Tab"
|
||||
// "name": "Close Tab"
|
||||
// "name": "Close Pane"
|
||||
// "name": "[-] Split Horizontal"
|
||||
// "name": "[ | ] Split Vertical"
|
||||
// "name": "Next Tab"
|
||||
// "name": "Prev Tab"
|
||||
// "name": "Open Settings"
|
||||
// "name": "Open Media Controls"
|
||||
// * "open" should return both "**Open** Settings" and "**Open** Media Controls".
|
||||
// * "Tab" would return "New **Tab**", "Close **Tab**", "Next **Tab**" and "Prev
|
||||
// **Tab**".
|
||||
// * "P" would return "Close **P**ane", "[-] S**p**lit Horizontal", "[ | ]
|
||||
// S**p**lit Vertical", "**P**rev Tab", "O**p**en Settings" and "O**p**en Media
|
||||
// Controls".
|
||||
// * "sv" would return "[ | ] Split Vertical" (by matching the **S** in
|
||||
// "Split", then the **V** in "Vertical").
|
||||
// Arguments:
|
||||
// - searchText: the string of text to search for in `name`
|
||||
// - name: the name to check
|
||||
// Return Value:
|
||||
// - the relative weight of this match
|
||||
int CommandPalette::_getWeight(const winrt::hstring& searchText,
|
||||
const winrt::hstring& name)
|
||||
{
|
||||
int totalWeight = 0;
|
||||
bool lastWasSpace = true;
|
||||
|
||||
auto it = name.cbegin();
|
||||
|
||||
for (auto searchChar : searchText)
|
||||
{
|
||||
searchChar = std::towlower(searchChar);
|
||||
// Advance the iterator to the next character that we're looking
|
||||
// for.
|
||||
|
||||
bool lastWasMatch = true;
|
||||
while (true)
|
||||
{
|
||||
// If we are at the end of the name string, we haven't found
|
||||
// it.
|
||||
if (it == name.cend())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// found it
|
||||
if (std::towlower(*it) == searchChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
lastWasSpace = *it == L' ';
|
||||
++it;
|
||||
lastWasMatch = false;
|
||||
}
|
||||
|
||||
// Advance the iterator by one character so that we don't
|
||||
// end up on the same character in the next iteration.
|
||||
++it;
|
||||
|
||||
totalWeight += 1;
|
||||
totalWeight += lastWasSpace ? 1 : 0;
|
||||
totalWeight += (lastWasMatch) ? 1 : 0;
|
||||
}
|
||||
|
||||
return totalWeight;
|
||||
}
|
||||
|
||||
void CommandPalette::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
|
||||
{
|
||||
_dispatch = dispatch;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Dismiss the command palette. This will:
|
||||
// * select all the current text in the input box
|
||||
// * set our visibility to Hidden
|
||||
// * raise our Closed event, so the page can return focus to the active Terminal
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_close()
|
||||
{
|
||||
Visibility(Visibility::Collapsed);
|
||||
|
||||
// Clear the text box each time we close the dialog. This is consistent with VsCode.
|
||||
_searchBox().Text(L"");
|
||||
}
|
||||
|
||||
void CommandPalette::_checkMode()
|
||||
{
|
||||
_mode = PaletteMode::ActionMode;
|
||||
|
||||
auto inputText = _searchBox().Text();
|
||||
if (inputText.size() > 0)
|
||||
{
|
||||
if (inputText[0] == L'>')
|
||||
{
|
||||
_mode = PaletteMode::CommandlineMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPalette::_dispatchAction()
|
||||
{
|
||||
// Action Mode: Dispatch the action of the selected command.
|
||||
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
{
|
||||
if (const auto data = selectedItem.try_as<Command>())
|
||||
{
|
||||
const auto actionAndArgs = data.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
_close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPalette::_dispatchCommandline()
|
||||
{
|
||||
const auto inputText = _searchBox().Text();
|
||||
const std::wstring input{ _searchBox().Text() };
|
||||
const auto rawCmdline{ input.substr(1) };
|
||||
|
||||
// Trim leading whitespace
|
||||
const auto firstNonSpace = rawCmdline.find_first_not_of(L" ");
|
||||
if (firstNonSpace == std::wstring::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
winrt::hstring cmdline{ rawCmdline.substr(firstNonSpace) };
|
||||
|
||||
// Build the NewTab action from the values we've parsed on the commandline.
|
||||
auto executeActionAndArgs = winrt::make_self<implementation::ActionAndArgs>();
|
||||
executeActionAndArgs->Action(ShortcutAction::ExecuteCommandline);
|
||||
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
|
||||
args->Commandline(cmdline);
|
||||
executeActionAndArgs->Args(*args);
|
||||
|
||||
if (_dispatch.DoAction(*executeActionAndArgs))
|
||||
{
|
||||
_close();
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/cascadia/TerminalApp/CommandPalette.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommandPalette.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
enum class PaletteMode : uint32_t
|
||||
{
|
||||
ActionMode = 0,
|
||||
CommandlineMode
|
||||
};
|
||||
|
||||
struct CommandPalette : CommandPaletteT<CommandPalette>
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> FilteredActions();
|
||||
void SetActions(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& actions);
|
||||
|
||||
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
|
||||
|
||||
private:
|
||||
friend struct CommandPaletteT<CommandPalette>; // for Xaml to bind events
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _filteredActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allActions{ nullptr };
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
|
||||
PaletteMode _mode{ PaletteMode::ActionMode };
|
||||
|
||||
void _filterTextChanged(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& args);
|
||||
void _keyDownHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
|
||||
void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _backdropPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
|
||||
void _listItemClicked(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::ItemClickEventArgs const& e);
|
||||
|
||||
void _selectNextItem(const bool moveDown);
|
||||
|
||||
void _checkMode();
|
||||
|
||||
void _updateFilteredActions();
|
||||
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
|
||||
void _close();
|
||||
|
||||
void _dispatchAction();
|
||||
void _dispatchCommandline();
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(CommandPalette);
|
||||
}
|
||||
18
src/cascadia/TerminalApp/CommandPalette.idl
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../Command.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.Grid
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<Command> FilteredActions { get; };
|
||||
|
||||
void SetActions(Windows.Foundation.Collections.IVector<Command> actions);
|
||||
|
||||
void SetDispatch(ShortcutActionDispatch dispatch);
|
||||
}
|
||||
}
|
||||
204
src/cascadia/TerminalApp/CommandPalette.xaml
Normal file
@@ -0,0 +1,204 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Grid
|
||||
x:Class="TerminalApp.CommandPalette"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1903="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 8)"
|
||||
PointerPressed="_rootPointerPressed"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<!-- ThemeShadow is only on 18362. This "Windows10version1903" bit
|
||||
adds it conditionally -->
|
||||
<Windows10version1903:ThemeShadow x:Name="CommandPaletteShadow" />
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="#333333" />
|
||||
</Style>
|
||||
<!-- TextBox colors !-->
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#333333"/>
|
||||
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#B5B5B5"/>
|
||||
<SolidColorBrush x:Key="TextControlForeground" Color="#B5B5B5"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#B5B5B5"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlForegroundPointerOver" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#333333"/>
|
||||
<SolidColorBrush x:Key="TextControlForegroundFocused" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="#CCCCCC" />
|
||||
</Style>
|
||||
<!-- TextBox colors !-->
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#CCCCCC"/>
|
||||
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#636363"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#DADADA"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#CCCCCC"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource SystemColorWindowColor}" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="6*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="8*"/>
|
||||
<RowDefinition Height="2*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Setting the row/col span of this shadow backdrop is a bit of a hack. In
|
||||
order to receive pointer events, an element needs to be _not_ transparent.
|
||||
However, we want to be able to eat all the clicks outside the immediate
|
||||
bounds of the command palette, and we don't want a semi-transparent overlay
|
||||
over all of the UI. Fortunately, if we make this _shadowBackdrop the size of
|
||||
the entire page, then it can be mostly transparent, and cause the root grid
|
||||
to receive clicks _anywhere_ in its bounds. -->
|
||||
|
||||
<Grid
|
||||
x:Name="_shadowBackdrop"
|
||||
Background="Transparent"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Grid.RowSpan="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="_backdrop"
|
||||
Style="{ThemeResource CommandPaletteBackground}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
PointerPressed="_backdropPointerPressed"
|
||||
Margin="8"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Windows10version1903:Shadow="{StaticResource CommandPaletteShadow}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
x:Uid="CommandPalette_SearchBox"
|
||||
x:Name="_searchBox"
|
||||
Margin="8"
|
||||
IsSpellCheckEnabled="False"
|
||||
TextChanged="_filterTextChanged"
|
||||
KeyDown="_keyDownHandler"
|
||||
Text="">
|
||||
</TextBox>
|
||||
|
||||
<TextBlock
|
||||
Padding="16"
|
||||
x:Name="_noMatchesText"
|
||||
x:Uid="CommandPalette_NoMatchesText"
|
||||
FontStyle="Italic"
|
||||
Visibility="Collapsed"
|
||||
Grid.Row="1">
|
||||
</TextBlock>
|
||||
|
||||
<ListView
|
||||
Grid.Row="2"
|
||||
x:Name="_filteredActionsView"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
SelectionMode="Single"
|
||||
CanReorderItems="False"
|
||||
AllowDrop="False"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="_listItemClicked"
|
||||
ItemsSource="{x:Bind FilteredActions}">
|
||||
|
||||
<ItemsControl.ItemTemplate >
|
||||
<DataTemplate x:DataType="local:Command">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" >
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- TODO GH#6644: Add Icon to command palette entries, in column 0 -->
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
BorderBrush="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Background="{ThemeResource SystemControlForegroundBaseLowBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="1"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
</Grid>
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../../inc/DefaultSettings.h"
|
||||
#include "Utils.h"
|
||||
#include "JsonUtils.h"
|
||||
#include <sstream>
|
||||
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
@@ -17,7 +16,8 @@ using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace winrt::Microsoft::UI::Xaml::Controls;
|
||||
|
||||
static constexpr std::string_view KeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view BindingsKey{ "bindings" };
|
||||
static constexpr std::string_view DefaultProfileKey{ "defaultProfile" };
|
||||
static constexpr std::string_view AlwaysShowTabsKey{ "alwaysShowTabs" };
|
||||
static constexpr std::string_view InitialRowsKey{ "initialRows" };
|
||||
@@ -205,18 +205,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
_TabWidthMode = _ParseTabWidthMode(GetWstringFromJson(tabWidthMode));
|
||||
}
|
||||
|
||||
if (auto keybindings{ json[JsonKey(KeybindingsKey)] })
|
||||
{
|
||||
auto warnings = _keybindings->LayerJson(keybindings);
|
||||
// It's possible that the user provided keybindings have some warnings
|
||||
// in them - problems that we should alert the user to, but we can
|
||||
// recover from. Most of these warnings cannot be detected later in the
|
||||
// Validate settings phase, so we'll collect them now. If there were any
|
||||
// warnings generated from parsing these keybindings, add them to our
|
||||
// list of warnings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
}
|
||||
|
||||
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
|
||||
|
||||
JsonUtils::GetBool(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
|
||||
@@ -228,6 +216,31 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
JsonUtils::GetBool(json, DebugFeaturesKey, _DebugFeaturesEnabled);
|
||||
|
||||
JsonUtils::GetBool(json, EnableStartupTaskKey, _StartOnUserLogin);
|
||||
|
||||
// This is a helper lambda to get the keybindings and commands out of both
|
||||
// and array of objects. We'll use this twice, once on the legacy
|
||||
// `keybindings` key, and again on the newer `bindings` key.
|
||||
auto parseBindings = [this, &json](auto jsonKey) {
|
||||
if (auto bindings{ json[JsonKey(jsonKey)] })
|
||||
{
|
||||
auto warnings = _keybindings->LayerJson(bindings);
|
||||
// It's possible that the user provided keybindings have some warnings
|
||||
// in them - problems that we should alert the user to, but we can
|
||||
// recover from. Most of these warnings cannot be detected later in the
|
||||
// Validate settings phase, so we'll collect them now. If there were any
|
||||
// warnings generated from parsing these keybindings, add them to our
|
||||
// list of warnings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
|
||||
// Now parse the array again, but this time as a list of commands.
|
||||
warnings = winrt::TerminalApp::implementation::Command::LayerJson(_commands, bindings);
|
||||
// It's possible that the user provided commands have some warnings
|
||||
// in them, similar to the keybindings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
}
|
||||
};
|
||||
parseBindings(LegacyKeybindingsKey);
|
||||
parseBindings(BindingsKey);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -365,3 +378,8 @@ std::vector<TerminalApp::SettingsLoadWarnings> GlobalAppSettings::GetKeybindings
|
||||
{
|
||||
return _keybindingsWarnings;
|
||||
}
|
||||
|
||||
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GlobalAppSettings::GetCommands() const noexcept
|
||||
{
|
||||
return _commands;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ Author(s):
|
||||
#pragma once
|
||||
#include "AppKeyBindings.h"
|
||||
#include "ColorScheme.h"
|
||||
#include "Command.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
@@ -54,6 +55,8 @@ public:
|
||||
|
||||
std::vector<TerminalApp::SettingsLoadWarnings> GetKeybindingsWarnings() const;
|
||||
|
||||
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GetCommands() const noexcept;
|
||||
|
||||
// These are implemented manually to handle the string/GUID exchange
|
||||
// by higher layers in the app.
|
||||
void DefaultProfile(const GUID defaultProfile) noexcept;
|
||||
@@ -79,7 +82,6 @@ public:
|
||||
GETSET_PROPERTY(bool, SoftwareRendering, false);
|
||||
GETSET_PROPERTY(bool, ForceVTInput, false);
|
||||
GETSET_PROPERTY(bool, DebugFeaturesEnabled); // default value set in constructor
|
||||
|
||||
GETSET_PROPERTY(bool, StartOnUserLogin, false);
|
||||
|
||||
private:
|
||||
@@ -90,6 +92,7 @@ private:
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> _keybindingsWarnings;
|
||||
|
||||
std::unordered_map<std::wstring, ColorScheme> _colorSchemes;
|
||||
std::unordered_map<winrt::hstring, winrt::TerminalApp::Command> _commands;
|
||||
|
||||
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
|
||||
|
||||
|
||||
471
src/cascadia/TerminalApp/JsonUtilsNew.h
Normal file
@@ -0,0 +1,471 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- JsonUtils.h
|
||||
|
||||
Abstract:
|
||||
- Helpers for the TerminalApp project
|
||||
Author(s):
|
||||
- Mike Griese - August 2019
|
||||
- Dustin Howett - January 2020
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <json.h>
|
||||
|
||||
#include "../types/inc/utils.hpp"
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
// If we don't use winrt, nobody will include the ConversionTrait for winrt::guid.
|
||||
// If nobody includes it, this forward declaration will suffice.
|
||||
struct guid;
|
||||
}
|
||||
|
||||
namespace TerminalApp::JsonUtils
|
||||
{
|
||||
namespace Detail
|
||||
{
|
||||
// Function Description:
|
||||
// - Returns a string_view to a Json::Value's internal string storage,
|
||||
// hopefully without copying it.
|
||||
__declspec(noinline) inline const std::string_view GetStringView(const Json::Value& json)
|
||||
{
|
||||
const char* begin{ nullptr };
|
||||
const char* end{ nullptr };
|
||||
json.getString(&begin, &end);
|
||||
const std::string_view zeroCopyString{ begin, gsl::narrow_cast<size_t>(end - begin) };
|
||||
return zeroCopyString;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct DeduceOptional
|
||||
{
|
||||
using Type = typename std::decay<T>::type;
|
||||
};
|
||||
|
||||
template<typename TOpt>
|
||||
struct DeduceOptional<std::optional<TOpt>>
|
||||
{
|
||||
using Type = typename std::decay<TOpt>::type;
|
||||
};
|
||||
}
|
||||
|
||||
// These exceptions cannot use localized messages, as we do not have
|
||||
// guaranteed access to the resource loader.
|
||||
class TypeMismatchException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
TypeMismatchException() :
|
||||
runtime_error("unexpected data type") {}
|
||||
};
|
||||
|
||||
class KeyedException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
KeyedException(const std::string_view key, std::exception_ptr exception) :
|
||||
runtime_error(fmt::format("error parsing \"{0}\"", key).c_str()),
|
||||
_key{ key },
|
||||
_innerException{ std::move(exception) } {}
|
||||
|
||||
std::string GetKey() const
|
||||
{
|
||||
return _key;
|
||||
}
|
||||
|
||||
[[noreturn]] void RethrowInner() const
|
||||
{
|
||||
std::rethrow_exception(_innerException);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _key;
|
||||
std::exception_ptr _innerException;
|
||||
};
|
||||
|
||||
class UnexpectedValueException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
UnexpectedValueException(const std::string_view value) :
|
||||
runtime_error(fmt::format("unexpected value \"{0}\"", value).c_str()),
|
||||
_value{ value } {}
|
||||
|
||||
std::string GetValue() const
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _value;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ConversionTrait
|
||||
{
|
||||
// FromJson, CanConvert are not defined so as to cause a compile error (which forces a specialization)
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<std::string>
|
||||
{
|
||||
std::string FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asString();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isString();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<std::wstring>
|
||||
{
|
||||
std::wstring FromJson(const Json::Value& json)
|
||||
{
|
||||
return til::u8u16(Detail::GetStringView(json));
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isString();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<bool>
|
||||
{
|
||||
bool FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asBool();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isBool();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<int>
|
||||
{
|
||||
int FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asInt();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isInt();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<unsigned int>
|
||||
{
|
||||
unsigned int FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asUInt();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isUInt();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<float>
|
||||
{
|
||||
float FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asFloat();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isNumeric();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<double>
|
||||
{
|
||||
double FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asDouble();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isNumeric();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<GUID>
|
||||
{
|
||||
GUID FromJson(const Json::Value& json)
|
||||
{
|
||||
return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json)));
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
if (!json.isString())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto string{ Detail::GetStringView(json) };
|
||||
return string.length() == 38 && string.front() == '{' && string.back() == '}';
|
||||
}
|
||||
};
|
||||
|
||||
// (GUID and winrt::guid are mutually convertible!)
|
||||
template<>
|
||||
struct ConversionTrait<winrt::guid> : public ConversionTrait<GUID>
|
||||
{
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<til::color>
|
||||
{
|
||||
til::color FromJson(const Json::Value& json)
|
||||
{
|
||||
return ::Microsoft::Console::Utils::ColorFromHexString(Detail::GetStringView(json));
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
if (!json.isString())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto string{ Detail::GetStringView(json) };
|
||||
return (string.length() == 7 || string.length() == 3) && string.front() == '#';
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename TBase>
|
||||
struct EnumMapper
|
||||
{
|
||||
using pair_type = std::pair<std::string_view, T>;
|
||||
T FromJson(const Json::Value& json)
|
||||
{
|
||||
const auto name{ Detail::GetStringView(json) };
|
||||
for (const auto& pair : TBase::mappings)
|
||||
{
|
||||
if (pair.first == name)
|
||||
{
|
||||
return pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
throw UnexpectedValueException{ name };
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isString();
|
||||
}
|
||||
};
|
||||
|
||||
// FlagMapper is EnumMapper, but it works for bitfields.
|
||||
// It supports a string (single flag) or an array of strings.
|
||||
// Does an O(n*m) search; meant for small search spaces!
|
||||
//
|
||||
// Cleverly leverage EnumMapper to do the heavy lifting.
|
||||
template<typename T, typename TBase>
|
||||
struct FlagMapper : public EnumMapper<T, TBase>
|
||||
{
|
||||
static constexpr T AllSet{ static_cast<T>(~0u) };
|
||||
static constexpr T AllClear{ static_cast<T>(0u) };
|
||||
|
||||
T FromJson(const Json::Value& json)
|
||||
{
|
||||
if (json.isString())
|
||||
{
|
||||
return EnumMapper::FromJson(json);
|
||||
}
|
||||
else if (json.isArray())
|
||||
{
|
||||
unsigned int seen{ 0 };
|
||||
T value{};
|
||||
for (const auto& element : json)
|
||||
{
|
||||
const auto newFlag{ EnumMapper::FromJson(element) };
|
||||
if (++seen > 1 &&
|
||||
((newFlag == AllClear && value != AllClear) ||
|
||||
(value == AllClear && newFlag != AllClear)))
|
||||
{
|
||||
// attempt to combine AllClear (explicitly) with anything else
|
||||
throw UnexpectedValueException{ element.asString() };
|
||||
}
|
||||
value |= newFlag;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// We'll only get here if CanConvert has failed us.
|
||||
return AllClear;
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return EnumMapper::CanConvert(json) || json.isArray();
|
||||
}
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
// - Helper that will populate a reference with a value converted from a json object.
|
||||
// Arguments:
|
||||
// - json: the json object to convert
|
||||
// - target: the value to populate with the converted result
|
||||
// Return Value:
|
||||
// - a boolean indicating whether the value existed (in this case, was non-null)
|
||||
//
|
||||
// GetValue, type-deduced, manual converter
|
||||
template<typename T, typename Converter>
|
||||
bool GetValue(const Json::Value& json, T& target, Converter&& conv)
|
||||
{
|
||||
if (json)
|
||||
{
|
||||
if (!conv.CanConvert(json))
|
||||
{
|
||||
throw TypeMismatchException{};
|
||||
}
|
||||
|
||||
target = conv.FromJson(json);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Overload on GetValue that will populate a std::optional with a value converted from json
|
||||
// - If the json value doesn't exist we'll leave the target object unmodified.
|
||||
// - If the json object is set to `null`, then
|
||||
// we'll instead set the target back to nullopt.
|
||||
// Arguments:
|
||||
// - json: the json object to convert
|
||||
// - target: the value to populate with the converted result
|
||||
// Return Value:
|
||||
// - a boolean indicating whether the optional was changed
|
||||
//
|
||||
// GetValue, type-deduced for optional, manual converter
|
||||
template<typename TOpt, typename Converter>
|
||||
bool GetValue(const Json::Value& json, std::optional<TOpt>& target, Converter&& conv)
|
||||
{
|
||||
if (json.isNull())
|
||||
{
|
||||
target = std::nullopt;
|
||||
return true; // null is valid for optionals
|
||||
}
|
||||
|
||||
std::decay_t<TOpt> local{};
|
||||
if (GetValue(json, local, std::forward<Converter>(conv)))
|
||||
{
|
||||
target = std::move(local);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// GetValue, forced return type, manual converter
|
||||
template<typename T, typename Converter>
|
||||
std::decay_t<T> GetValue(const Json::Value& json, Converter&& conv)
|
||||
{
|
||||
std::decay_t<T> local{};
|
||||
GetValue(json, local, std::forward<Converter>(conv));
|
||||
return local; // returns zero-initialized or value
|
||||
}
|
||||
|
||||
// GetValueForKey, type-deduced, manual converter
|
||||
template<typename T, typename Converter>
|
||||
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv)
|
||||
{
|
||||
if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) })
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetValue(*found, target, std::forward<Converter>(conv));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Wrap any caught exceptions in one that preserves context.
|
||||
throw KeyedException(key, std::current_exception());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// GetValueForKey, forced return type, manual converter
|
||||
template<typename T, typename Converter>
|
||||
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv)
|
||||
{
|
||||
std::decay_t<T> local{};
|
||||
GetValueForKey(json, key, local, std::forward<Converter>(conv));
|
||||
return local; // returns zero-initialized?
|
||||
}
|
||||
|
||||
// GetValue, type-deduced, with automatic converter
|
||||
template<typename T>
|
||||
bool GetValue(const Json::Value& json, T& target)
|
||||
{
|
||||
return GetValue(json, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
|
||||
}
|
||||
|
||||
// GetValue, forced return type, with automatic converter
|
||||
template<typename T>
|
||||
std::decay_t<T> GetValue(const Json::Value& json)
|
||||
{
|
||||
std::decay_t<T> local{};
|
||||
GetValue(json, local, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
|
||||
return local; // returns zero-initialized or value
|
||||
}
|
||||
|
||||
// GetValueForKey, type-deduced, with automatic converter
|
||||
template<typename T>
|
||||
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target)
|
||||
{
|
||||
return GetValueForKey(json, key, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
|
||||
}
|
||||
|
||||
// GetValueForKey, forced return type, with automatic converter
|
||||
template<typename T>
|
||||
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key)
|
||||
{
|
||||
return GetValueForKey<T>(json, key, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
|
||||
}
|
||||
|
||||
// Get multiple values for keys (json, k, &v, k, &v, k, &v, ...).
|
||||
// Uses the default converter for each v.
|
||||
// Careful: this can cause a template explosion.
|
||||
constexpr void GetValuesForKeys(const Json::Value& /*json*/) {}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args)
|
||||
{
|
||||
GetValueForKey(json, key1, val1);
|
||||
GetValuesForKeys(json, std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
#define JSON_ENUM_MAPPER(...) \
|
||||
template<> \
|
||||
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
|
||||
public ::TerminalApp::JsonUtils::EnumMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
|
||||
|
||||
#define JSON_FLAG_MAPPER(...) \
|
||||
template<> \
|
||||
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
|
||||
public ::TerminalApp::JsonUtils::FlagMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
|
||||
|
||||
#define JSON_MAPPINGS(Count) \
|
||||
static constexpr std::array<pair_type, Count> mappings
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "CascadiaSettings.h"
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Graphics::Display;
|
||||
using namespace winrt::Windows::UI;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
@@ -921,6 +922,102 @@ bool Pane::CanSplit(SplitState splitType)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper to determine if a given Pane can be split, but without
|
||||
// using the ActualWidth() and ActualHeight() methods. This is used during
|
||||
// processing of many "split-pane" commands, which could happen _before_ we've
|
||||
// laid out a Pane for the first time. When this happens, the Pane's don't
|
||||
// have an actual size yet. However, we'd still like to figure out if the pane
|
||||
// could be split, once they're all laid out.
|
||||
// - This method assumes that the Pane we're attempting to split is `target`,
|
||||
// and this method should be called on the root of a tree of Panes.
|
||||
// - We'll walk down the tree attempting to find `target`. As we traverse the
|
||||
// tree, we'll reduce the size passed to each subsequent recursive call. The
|
||||
// size passed to this method represents how much space this Pane _will_ have
|
||||
// to use.
|
||||
// * If this pane is a leaf, and it's the pane we're looking for, use the
|
||||
// available space to calculate which direction to split in.
|
||||
// * If this pane is _any other leaf_, then just return nullopt, to indicate
|
||||
// that the `target` Pane is not down this branch.
|
||||
// * If this pane is a parent, calculate how much space our children will be
|
||||
// able to use, and recurse into them.
|
||||
// Arguments:
|
||||
// - target: The Pane we're attempting to split.
|
||||
// - splitType: The direction we're attempting to split in.
|
||||
// - availableSpace: The theoretical space that's available for this pane to be able to split.
|
||||
// Return Value:
|
||||
// - nullopt if `target` is not this pane or a child of this pane, otherwise
|
||||
// true iff we could split this pane, given `availableSpace`
|
||||
// Note:
|
||||
// - This method is highly similar to Pane::PreCalculateAutoSplit
|
||||
std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> target,
|
||||
SplitState splitType,
|
||||
const winrt::Windows::Foundation::Size availableSpace) const
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
if (target.get() == this)
|
||||
{
|
||||
//If this pane is a leaf, and it's the pane we're looking for, use
|
||||
//the available space to calculate which direction to split in.
|
||||
const Size minSize = _GetMinSize();
|
||||
|
||||
if (splitType == SplitState::None)
|
||||
{
|
||||
return { false };
|
||||
}
|
||||
|
||||
if (splitType == SplitState::Vertical)
|
||||
{
|
||||
const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize;
|
||||
const auto newWidth = widthMinusSeparator * Half;
|
||||
|
||||
return { newWidth > minSize.Width };
|
||||
}
|
||||
|
||||
if (splitType == SplitState::Horizontal)
|
||||
{
|
||||
const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize;
|
||||
const auto newHeight = heightMinusSeparator * Half;
|
||||
|
||||
return { newHeight > minSize.Height };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this pane is _any other leaf_, then just return nullopt, to
|
||||
// indicate that the `target` Pane is not down this branch.
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this pane is a parent, calculate how much space our children will
|
||||
// be able to use, and recurse into them.
|
||||
|
||||
const bool isVerticalSplit = _splitState == SplitState::Vertical;
|
||||
const float firstWidth = isVerticalSplit ?
|
||||
(availableSpace.Width * _desiredSplitPosition) - PaneBorderSize :
|
||||
availableSpace.Width;
|
||||
const float secondWidth = isVerticalSplit ?
|
||||
(availableSpace.Width - firstWidth) - PaneBorderSize :
|
||||
availableSpace.Width;
|
||||
const float firstHeight = !isVerticalSplit ?
|
||||
(availableSpace.Height * _desiredSplitPosition) - PaneBorderSize :
|
||||
availableSpace.Height;
|
||||
const float secondHeight = !isVerticalSplit ?
|
||||
(availableSpace.Height - firstHeight) - PaneBorderSize :
|
||||
availableSpace.Height;
|
||||
|
||||
const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, { firstWidth, firstHeight });
|
||||
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, { secondWidth, secondHeight });
|
||||
}
|
||||
|
||||
// We should not possibly be getting here - both the above branches should
|
||||
// return a value.
|
||||
FAIL_FAST();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Split the focused pane in our tree of panes, and place the given
|
||||
// TermControl into the newly created pane. If we're the focused pane, then
|
||||
|
||||
@@ -64,7 +64,9 @@ public:
|
||||
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
std::optional<winrt::TerminalApp::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
|
||||
|
||||
std::optional<bool> PreCalculateCanSplit(const std::shared_ptr<Pane> target,
|
||||
winrt::TerminalApp::SplitState splitType,
|
||||
const winrt::Windows::Foundation::Size availableSpace) const;
|
||||
void Shutdown();
|
||||
void Close();
|
||||
|
||||
|
||||
@@ -347,4 +347,158 @@
|
||||
<value>Command Prompt</value>
|
||||
<comment>This is the name of "Command Prompt", as localized in Windows. The localization here should match the one in the Windows product for "Command Prompt"</comment>
|
||||
</data>
|
||||
<data name="CommandPalette_SearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Type a command name...</value>
|
||||
</data>
|
||||
<data name="CommandPalette_NoMatchesText.Text" xml:space="preserve">
|
||||
<value>No matching commands</value>
|
||||
</data>
|
||||
<data name="CloseWindowCommandKey" xml:space="preserve">
|
||||
<value>Close window</value>
|
||||
</data>
|
||||
<data name="ToggleFullscreenCommandKey" xml:space="preserve">
|
||||
<value>Toggle fullscreen</value>
|
||||
</data>
|
||||
<data name="OpenNewTabDropdownCommandKey" xml:space="preserve">
|
||||
<value>Open new tab dropdown</value>
|
||||
</data>
|
||||
<data name="OpenSettingsCommandKey" xml:space="preserve">
|
||||
<value>Open settings file</value>
|
||||
</data>
|
||||
<data name="OpenDefaultSettingsCommandKey" xml:space="preserve">
|
||||
<value>Open default settings file</value>
|
||||
</data>
|
||||
<data name="OpenBothSettingsFilesCommandKey" xml:space="preserve">
|
||||
<value>Open both settings and default settings files</value>
|
||||
</data>
|
||||
<data name="FindCommandKey" xml:space="preserve">
|
||||
<value>Find</value>
|
||||
</data>
|
||||
<data name="ResizePaneCommandKey" xml:space="preserve">
|
||||
<value>Resize pane</value>
|
||||
</data>
|
||||
<data name="MoveFocusCommandKey" xml:space="preserve">
|
||||
<value>Move focus</value>
|
||||
</data>
|
||||
<data name="MoveFocusWithArgCommandKey" xml:space="preserve">
|
||||
<value>Move focus {0}</value>
|
||||
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", or "DirectionDown"</comment>
|
||||
</data>
|
||||
<data name="ResizePaneWithArgCommandKey" xml:space="preserve">
|
||||
<value>Resize pane {0}</value>
|
||||
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", or "DirectionDown"</comment>
|
||||
</data>
|
||||
<data name="DirectionLeft" xml:space="preserve">
|
||||
<value>left</value>
|
||||
</data>
|
||||
<data name="DirectionRight" xml:space="preserve">
|
||||
<value>right</value>
|
||||
</data>
|
||||
<data name="DirectionUp" xml:space="preserve">
|
||||
<value>up</value>
|
||||
</data>
|
||||
<data name="DirectionDown" xml:space="preserve">
|
||||
<value>down</value>
|
||||
</data>
|
||||
<data name="SwitchToTabCommandKey" xml:space="preserve">
|
||||
<value>Switch to tab</value>
|
||||
</data>
|
||||
<data name="NewTabCommandKey" xml:space="preserve">
|
||||
<value>New tab</value>
|
||||
</data>
|
||||
<data name="SplitPaneCommandKey" xml:space="preserve">
|
||||
<value>Split pane</value>
|
||||
</data>
|
||||
<data name="NewWindowCommandKey" xml:space="preserve">
|
||||
<value>New window</value>
|
||||
</data>
|
||||
<data name="DuplicateTabCommandKey" xml:space="preserve">
|
||||
<value>Duplicate tab</value>
|
||||
</data>
|
||||
<data name="DuplicatePaneCommandKey" xml:space="preserve">
|
||||
<value>Duplicate pane</value>
|
||||
</data>
|
||||
<data name="NextTabCommandKey" xml:space="preserve">
|
||||
<value>Next tab</value>
|
||||
</data>
|
||||
<data name="PrevTabCommandKey" xml:space="preserve">
|
||||
<value>Previous tab</value>
|
||||
</data>
|
||||
<data name="ClosePaneCommandKey" xml:space="preserve">
|
||||
<value>Close pane</value>
|
||||
</data>
|
||||
<data name="CloseTabCommandKey" xml:space="preserve">
|
||||
<value>Close tab</value>
|
||||
</data>
|
||||
<data name="SplitHorizontalCommandKey" xml:space="preserve">
|
||||
<value>Split pane horizontally</value>
|
||||
</data>
|
||||
<data name="SplitVerticalCommandKey" xml:space="preserve">
|
||||
<value>Split pane vertically</value>
|
||||
</data>
|
||||
<data name="CopyTextCommandKey" xml:space="preserve">
|
||||
<value>Copy text</value>
|
||||
</data>
|
||||
<data name="CopyTextAsSingleLineCommandKey" xml:space="preserve">
|
||||
<value>Copy text as a single line</value>
|
||||
</data>
|
||||
<data name="PasteTextCommandKey" xml:space="preserve">
|
||||
<value>Paste</value>
|
||||
</data>
|
||||
<data name="ScrollDownCommandKey" xml:space="preserve">
|
||||
<value>Scroll down one line</value>
|
||||
</data>
|
||||
<data name="ScrollDownPageCommandKey" xml:space="preserve">
|
||||
<value>Scroll down one page</value>
|
||||
</data>
|
||||
<data name="ScrollUpCommandKey" xml:space="preserve">
|
||||
<value>Scroll up one line</value>
|
||||
</data>
|
||||
<data name="ScrollUpPageCommandKey" xml:space="preserve">
|
||||
<value>Scroll up one page</value>
|
||||
</data>
|
||||
<data name="AdjustFontSizeCommandKey" xml:space="preserve">
|
||||
<value>Adjust font size</value>
|
||||
</data>
|
||||
<data name="IncreaseFontSizeCommandKey" xml:space="preserve">
|
||||
<value>Increase font size</value>
|
||||
</data>
|
||||
<data name="DecreaseFontSizeCommandKey" xml:space="preserve">
|
||||
<value>Decrease font size</value>
|
||||
</data>
|
||||
<data name="IncreaseFontSizeWithAmountCommandKey" xml:space="preserve">
|
||||
<value>Increase font size, amount: {0}</value>
|
||||
<comment>{0} will be replaced with a positive number</comment>
|
||||
</data>
|
||||
<data name="DecreaseFontSizeWithAmountCommandKey" xml:space="preserve">
|
||||
<value>Decrease font size, amount: {0}</value>
|
||||
<comment>{0} will be replaced with a positive number</comment>
|
||||
</data>
|
||||
<data name="ResetFontSizeCommandKey" xml:space="preserve">
|
||||
<value>Reset font size</value>
|
||||
</data>
|
||||
<data name="ToggleCommandPaletteCommandKey" xml:space="preserve">
|
||||
<value>Toggle command palette</value>
|
||||
</data>
|
||||
<data name="ExecuteCommandlineCommandKey" xml:space="preserve">
|
||||
<value>Run the commandline "{0}" in this window</value>
|
||||
<comment>{0} will be replaced with a user-provided commandline</comment>
|
||||
</data>
|
||||
<data name="SetTabColorCommandKey" xml:space="preserve">
|
||||
<value>Set tab color to {0}</value>
|
||||
<comment>{0} will be replaced with a color, displayed in hexadecimal (#RRGGBB) notation.</comment>
|
||||
</data>
|
||||
<data name="ResetTabColorCommandKey" xml:space="preserve">
|
||||
<value>Reset tab color</value>
|
||||
</data>
|
||||
<data name="OpenTabColorPickerCommandKey" xml:space="preserve">
|
||||
<value>Set the tab color...</value>
|
||||
</data>
|
||||
<data name="RenameTabCommandKey" xml:space="preserve">
|
||||
<value>Rename tab to "{0}"</value>
|
||||
<comment>{0} will be replaced with user-defined string</comment>
|
||||
</data>
|
||||
<data name="ResetTabNameCommandKey" xml:space="preserve">
|
||||
<value>Reset tab title</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -159,6 +159,30 @@ namespace winrt::TerminalApp::implementation
|
||||
_ToggleFullscreenHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::ToggleCommandPalette:
|
||||
{
|
||||
_ToggleCommandPaletteHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::ExecuteCommandline:
|
||||
{
|
||||
_ExecuteCommandlineHandlers(*this, *eventArgs);
|
||||
}
|
||||
case ShortcutAction::SetTabColor:
|
||||
{
|
||||
_SetTabColorHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::OpenTabColorPicker:
|
||||
{
|
||||
_OpenTabColorPickerHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::RenameTab:
|
||||
{
|
||||
_RenameTabHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -23,30 +23,35 @@ namespace winrt::TerminalApp::implementation
|
||||
bool DoAction(const ActionAndArgs& actionAndArgs);
|
||||
|
||||
// clang-format off
|
||||
TYPED_EVENT(CopyText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(PasteText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenNewTabDropdown,TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(DuplicateTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NewTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ClosePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SwitchToTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ResetFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollUp, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollDown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollUpPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollDownPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ResizePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(Find, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(MoveFocus, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CopyText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(PasteText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenNewTabDropdown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(DuplicateTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NewTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ClosePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SwitchToTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ResetFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollUp, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollDown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollUpPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollDownPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ResizePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(Find, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(MoveFocus, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ToggleCommandPalette, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ExecuteCommandline, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SetTabColor, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenTabColorPicker, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(RenameTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
|
||||
@@ -4,9 +4,6 @@ import "../ActionArgs.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
// TODO: GH#1069 - Many of these shortcut actions are "legacy" now that we
|
||||
// have support for arbitrary args (#1142). We should remove them, and our
|
||||
// legacy deserializers.
|
||||
enum ShortcutAction
|
||||
{
|
||||
Invalid = 0,
|
||||
@@ -35,7 +32,12 @@ namespace TerminalApp
|
||||
MoveFocus,
|
||||
Find,
|
||||
ToggleFullscreen,
|
||||
OpenSettings
|
||||
SetTabColor,
|
||||
OpenTabColorPicker,
|
||||
OpenSettings,
|
||||
ExecuteCommandline,
|
||||
RenameTab,
|
||||
ToggleCommandPalette
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionAndArgs {
|
||||
@@ -73,5 +75,10 @@ namespace TerminalApp
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> Find;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> MoveFocus;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ToggleFullscreen;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ToggleCommandPalette;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ExecuteCommandline;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> SetTabColor;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> OpenTabColorPicker;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> RenameTab;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,15 @@ namespace winrt::TerminalApp::implementation
|
||||
void Tab::_MakeTabViewItem()
|
||||
{
|
||||
_tabViewItem = ::winrt::MUX::Controls::TabViewItem{};
|
||||
|
||||
_tabViewItem.DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_inRename = true;
|
||||
tab->_UpdateTabHeader();
|
||||
}
|
||||
});
|
||||
|
||||
_UpdateTitle();
|
||||
}
|
||||
|
||||
@@ -378,6 +387,18 @@ namespace winrt::TerminalApp::implementation
|
||||
_activePane->Close();
|
||||
}
|
||||
|
||||
void Tab::SetTabText(winrt::hstring title)
|
||||
{
|
||||
_runtimeTabText = title;
|
||||
_UpdateTitle();
|
||||
}
|
||||
|
||||
void Tab::ResetTabText()
|
||||
{
|
||||
_runtimeTabText = L"";
|
||||
_UpdateTitle();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Register any event handlers that we may need with the given TermControl.
|
||||
// This should be called on each and every TermControl that we add to the tree
|
||||
@@ -499,7 +520,7 @@ namespace winrt::TerminalApp::implementation
|
||||
chooseColorMenuItem.Click([weakThis](auto&&, auto&&) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_tabColorPickup.ShowAt(tab->_tabViewItem);
|
||||
tab->ActivateColorPicker();
|
||||
}
|
||||
});
|
||||
chooseColorMenuItem.Text(RS_(L"TabColorChoose"));
|
||||
@@ -509,14 +530,14 @@ namespace winrt::TerminalApp::implementation
|
||||
_tabColorPickup.ColorSelected([weakThis](auto newTabColor) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_SetTabColor(newTabColor);
|
||||
tab->SetTabColor(newTabColor);
|
||||
}
|
||||
});
|
||||
|
||||
_tabColorPickup.ColorCleared([weakThis]() {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_ResetTabColor();
|
||||
tab->ResetTabColor();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -697,7 +718,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - color: the shiny color the user picked for their tab
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::_SetTabColor(const winrt::Windows::UI::Color& color)
|
||||
void Tab::SetTabColor(const winrt::Windows::UI::Color& color)
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
@@ -758,7 +779,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::_ResetTabColor()
|
||||
void Tab::ResetTabColor()
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
@@ -796,6 +817,17 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Display the tab color picker at the location of the TabViewItem for this tab.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::ActivateColorPicker()
|
||||
{
|
||||
_tabColorPickup.ShowAt(_tabViewItem);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// Toggles the visual state of the tab view item,
|
||||
// so that changes to the tab color are reflected immediately
|
||||
@@ -843,6 +875,10 @@ namespace winrt::TerminalApp::implementation
|
||||
return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical);
|
||||
}
|
||||
|
||||
bool Tab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const
|
||||
{
|
||||
return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false);
|
||||
}
|
||||
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
|
||||
DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>);
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const;
|
||||
bool PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const;
|
||||
|
||||
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
|
||||
void ResizePane(const winrt::TerminalApp::Direction& direction);
|
||||
@@ -51,8 +52,15 @@ namespace winrt::TerminalApp::implementation
|
||||
void Shutdown();
|
||||
void ClosePane();
|
||||
|
||||
void SetTabText(winrt::hstring title);
|
||||
void ResetTabText();
|
||||
|
||||
std::optional<winrt::Windows::UI::Color> GetTabColor();
|
||||
|
||||
void SetTabColor(const winrt::Windows::UI::Color& color);
|
||||
void ResetTabColor();
|
||||
void ActivateColorPicker();
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
@@ -80,8 +88,6 @@ namespace winrt::TerminalApp::implementation
|
||||
void _Focus();
|
||||
|
||||
void _CreateContextMenu();
|
||||
void _SetTabColor(const winrt::Windows::UI::Color& color);
|
||||
void _ResetTabColor();
|
||||
void _RefreshVisualState();
|
||||
|
||||
void _BindEventHandlers(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) noexcept;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <winrt/Windows.Storage.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
|
||||
#include "KeyChordSerialization.h"
|
||||
#include "AzureCloudShellGenerator.h" // For AzureConnectionType
|
||||
#include "TelnetGenerator.h" // For TelnetConnectionType
|
||||
#include "TabRowControl.h"
|
||||
@@ -49,13 +50,39 @@ namespace winrt::TerminalApp::implementation
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
void TerminalPage::SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI)
|
||||
winrt::fire_and_forget TerminalPage::SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings,
|
||||
bool needRefreshUI)
|
||||
{
|
||||
_settings = settings;
|
||||
if (needRefreshUI)
|
||||
{
|
||||
_RefreshUIForSettingsReload();
|
||||
}
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
// Update the command palette when settings reload
|
||||
auto commandsCollection = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
for (auto& nameAndCommand : _settings->GlobalSettings().GetCommands())
|
||||
{
|
||||
auto command = nameAndCommand.second;
|
||||
|
||||
// If there's a keybinding that's bound to exactly this command,
|
||||
// then get the string for that keychord and display it as a
|
||||
// part of the command in the UI. Each Command's KeyChordText is
|
||||
// unset by default, so we don't need to worry about clearing it
|
||||
// if there isn't a key associated with it.
|
||||
auto keyChord{ _settings->GetKeybindings().GetKeyBindingForActionWithArgs(command.Action()) };
|
||||
if (keyChord)
|
||||
{
|
||||
command.KeyChordText(KeyChordSerialization::ToString(keyChord));
|
||||
}
|
||||
commandsCollection.Append(command);
|
||||
}
|
||||
CommandPalette().SetActions(commandsCollection);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::Create()
|
||||
@@ -151,6 +178,17 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
_tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged });
|
||||
|
||||
CommandPalette().SetDispatch(*_actionDispatch);
|
||||
// When the visibility of the command palette changes to "collapsed",
|
||||
// the palette has been closed. Toss focus back to the currently active
|
||||
// control.
|
||||
CommandPalette().RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (CommandPalette().Visibility() == Visibility::Collapsed)
|
||||
{
|
||||
_CommandPaletteClosed(nullptr, nullptr);
|
||||
}
|
||||
});
|
||||
|
||||
// Once the page is actually laid out on the screen, trigger all our
|
||||
// startup actions. Things like Panes need to know at least how big the
|
||||
// window will be, so they can subdivide that space.
|
||||
@@ -192,7 +230,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
else
|
||||
{
|
||||
_ProcessStartupActions();
|
||||
_ProcessStartupActions(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,10 +239,11 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Process all the startup actions in our list of startup actions. We'll
|
||||
// do this all at once here.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// - initial: if true, we're parsing these args during startup, and we
|
||||
// should fire an Initialized event.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget TerminalPage::_ProcessStartupActions()
|
||||
winrt::fire_and_forget TerminalPage::_ProcessStartupActions(const bool initial)
|
||||
{
|
||||
// If there are no actions left, do nothing.
|
||||
if (_startupActions.empty())
|
||||
@@ -214,15 +253,24 @@ namespace winrt::TerminalApp::implementation
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
// Handle it on a subsequent pass of the UI thread.
|
||||
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
|
||||
if (auto page{ weakThis.get() })
|
||||
for (const auto& action : _startupActions)
|
||||
{
|
||||
for (const auto& action : _startupActions)
|
||||
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
_actionDispatch->DoAction(action);
|
||||
}
|
||||
|
||||
_CompleteInitialization();
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
if (initial)
|
||||
{
|
||||
_CompleteInitialization();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,6 +803,11 @@ namespace winrt::TerminalApp::implementation
|
||||
_actionDispatch->Find({ this, &TerminalPage::_HandleFind });
|
||||
_actionDispatch->ResetFontSize({ this, &TerminalPage::_HandleResetFontSize });
|
||||
_actionDispatch->ToggleFullscreen({ this, &TerminalPage::_HandleToggleFullscreen });
|
||||
_actionDispatch->ToggleCommandPalette({ this, &TerminalPage::_HandleToggleCommandPalette });
|
||||
_actionDispatch->ExecuteCommandline({ this, &TerminalPage::_HandleExecuteCommandline });
|
||||
_actionDispatch->SetTabColor({ this, &TerminalPage::_HandleSetTabColor });
|
||||
_actionDispatch->OpenTabColorPicker({ this, &TerminalPage::_HandleOpenTabColorPicker });
|
||||
_actionDispatch->RenameTab({ this, &TerminalPage::_HandleRenameTab });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1096,6 +1149,18 @@ namespace winrt::TerminalApp::implementation
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - returns a com_ptr to the currently focused tab. This might return null,
|
||||
// so make sure to check the result!
|
||||
winrt::com_ptr<Tab> TerminalPage::_GetFocusedTab()
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
return _GetStrongTabImpl(*index);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - An async method for changing the focused tab on the UI thread. This
|
||||
// method will _only_ set the selected item of the TabView, which will
|
||||
@@ -1250,19 +1315,20 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
|
||||
|
||||
const auto canSplit = focusedTab->CanSplitPane(splitType);
|
||||
|
||||
if (!canSplit && _startupState == StartupState::Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
float contentWidth = gsl::narrow_cast<float>(_tabContent.ActualWidth());
|
||||
float contentHeight = gsl::narrow_cast<float>(_tabContent.ActualHeight());
|
||||
winrt::Windows::Foundation::Size availableSpace{ contentWidth, contentHeight };
|
||||
|
||||
auto realSplitType = splitType;
|
||||
if (realSplitType == SplitState::Automatic && _startupState < StartupState::Initialized)
|
||||
if (realSplitType == SplitState::Automatic)
|
||||
{
|
||||
float contentWidth = gsl::narrow_cast<float>(_tabContent.ActualWidth());
|
||||
float contentHeight = gsl::narrow_cast<float>(_tabContent.ActualHeight());
|
||||
realSplitType = focusedTab->PreCalculateAutoSplit({ contentWidth, contentHeight });
|
||||
realSplitType = focusedTab->PreCalculateAutoSplit(availableSpace);
|
||||
}
|
||||
|
||||
const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, availableSpace);
|
||||
if (!canSplit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TermControl newControl{ controlSettings, controlConnection };
|
||||
@@ -2016,6 +2082,16 @@ namespace winrt::TerminalApp::implementation
|
||||
// TODO GH#3327: Look at what to do with the NC area when we have XAML theming
|
||||
}
|
||||
|
||||
void TerminalPage::_CommandPaletteClosed(const IInspectable& /*sender*/,
|
||||
const RoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
// Return focus to the active control
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
_GetStrongTabImpl(index.value())->SetFocused(true);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace winrt::TerminalApp::implementation
|
||||
public:
|
||||
TerminalPage();
|
||||
|
||||
void SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI);
|
||||
winrt::fire_and_forget SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI);
|
||||
|
||||
void Create();
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace winrt::TerminalApp::implementation
|
||||
StartupState _startupState{ StartupState::NotInitialized };
|
||||
|
||||
std::deque<winrt::TerminalApp::ActionAndArgs> _startupActions;
|
||||
winrt::fire_and_forget _ProcessStartupActions();
|
||||
winrt::fire_and_forget _ProcessStartupActions(const bool initial);
|
||||
|
||||
void _ShowAboutDialog();
|
||||
void _ShowCloseWarningDialog();
|
||||
@@ -129,6 +129,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl();
|
||||
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
|
||||
winrt::com_ptr<Tab> _GetFocusedTab();
|
||||
winrt::fire_and_forget _SetFocusedTabIndex(const uint32_t tabIndex);
|
||||
void _CloseFocusedTab();
|
||||
void _CloseFocusedPane();
|
||||
@@ -172,6 +173,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _CompleteInitialization();
|
||||
|
||||
void _CommandPaletteClosed(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
#pragma region ActionHandlers
|
||||
// These are all defined in AppActionHandlers.cpp
|
||||
void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
@@ -197,6 +200,12 @@ namespace winrt::TerminalApp::implementation
|
||||
void _HandleFind(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleResetFontSize(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleToggleFullscreen(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleSetTabColor(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleOpenTabColorPicker(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleRenameTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleToggleCommandPalette(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleExecuteCommandline(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
// Make sure to hook new actions up in _RegisterActionCallbacks!
|
||||
#pragma endregion
|
||||
|
||||
friend class TerminalAppLocalTests::TabTests;
|
||||
|
||||
@@ -53,5 +53,11 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="_CloseWarningPrimaryButtonOnClick">
|
||||
</ContentDialog>
|
||||
|
||||
<local:CommandPalette
|
||||
x:Name="CommandPalette"
|
||||
Grid.Row="1"
|
||||
Visibility="Collapsed"
|
||||
VerticalAlignment="Stretch" />
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -76,7 +76,7 @@ std::vector<TerminalApp::Profile> WslDistroGenerator::GenerateProfiles()
|
||||
THROW_HR(ERROR_UNHANDLED_EXCEPTION);
|
||||
}
|
||||
DWORD exitCode;
|
||||
if (GetExitCodeProcess(pi.hProcess, &exitCode) == false)
|
||||
if (!GetExitCodeProcess(pi.hProcess, &exitCode))
|
||||
{
|
||||
THROW_HR(E_INVALIDARG);
|
||||
}
|
||||
@@ -116,7 +116,7 @@ std::vector<TerminalApp::Profile> WslDistroGenerator::GenerateProfiles()
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t firstChar = distName.find_first_of(L"( ");
|
||||
const size_t firstChar = distName.find_first_of(L"( ");
|
||||
// Some localizations don't have a space between the name and "(Default)"
|
||||
// https://github.com/microsoft/terminal/issues/1168#issuecomment-500187109
|
||||
if (firstChar < distName.size())
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
<Page Include="../ColorPickupFlyout.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../CommandPalette.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
@@ -85,6 +88,12 @@
|
||||
<ClInclude Include="../ColorPickupFlyout.h">
|
||||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../CommandPalette.h">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../Command.h">
|
||||
<DependentUpon>../Command.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../Tab.h">
|
||||
<DependentUpon>../Tab.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -103,7 +112,7 @@
|
||||
<ClInclude Include="../WslDistroGenerator.h" />
|
||||
<ClInclude Include="../AzureCloudShellGenerator.h" />
|
||||
<ClInclude Include="../TelnetGenerator.h" />
|
||||
<ClInclude Include="..\ColorHelper.h" />
|
||||
<ClInclude Include="../ColorHelper.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="../ShortcutActionDispatch.h">
|
||||
<DependentUpon>../ShortcutActionDispatch.idl</DependentUpon>
|
||||
@@ -146,6 +155,12 @@
|
||||
<ClCompile Include="../ColorPickupFlyout.cpp">
|
||||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../CommandPalette.cpp">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../Command.cpp">
|
||||
<DependentUpon>../Command.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../Tab.cpp">
|
||||
<DependentUpon>../Tab.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -229,7 +244,12 @@
|
||||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../Tab.idl"/>
|
||||
<Midl Include="../CommandPalette.idl">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../Command.idl" />
|
||||
<Midl Include="../Tab.idl" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="../Resources/en-US/Resources.resw" />
|
||||
<PRIResource Include="..\Resources\en-US\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="../init.cpp" />
|
||||
@@ -59,7 +59,13 @@
|
||||
<ClCompile Include="../Pane.LayoutSizeNode.cpp">
|
||||
<Filter>pane</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ColorHelper.cpp" />
|
||||
<ClCompile Include="../AppCommandlineArgs.cpp" />
|
||||
<ClCompile Include="../Commandline.cpp" />
|
||||
<ClCompile Include="../ColorHelper.cpp" />
|
||||
<ClCompile Include="../DebugTapConnection.cpp" />
|
||||
<ClCompile Include="../CommandSerialization.cpp">
|
||||
<Filter>settings</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="../Utils.h" />
|
||||
@@ -107,8 +113,16 @@
|
||||
<ClInclude Include="../Tab.h">
|
||||
<Filter>tab</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../TelnetGenerator.h" />
|
||||
<ClInclude Include="..\ColorHelper.h" />
|
||||
<ClInclude Include="../AppCommandlineArgs.h" />
|
||||
<ClInclude Include="../Commandline.h" />
|
||||
<ClInclude Include="../DebugTapConnection.h" />
|
||||
<ClInclude Include="../ColorHelper.h" />
|
||||
<ClInclude Include="../TelnetGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../CommandSerialization.h">
|
||||
<Filter>settings</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="../AppLogic.idl">
|
||||
@@ -126,6 +140,10 @@
|
||||
<Midl Include="../Tab.idl">
|
||||
<Filter>tab</Filter>
|
||||
</Midl>
|
||||
<Midl Include="../IF7Listener.idl" />
|
||||
<Midl Include="../Command.idl">
|
||||
<Filter>commandPalette</Filter>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="../packages.config" />
|
||||
@@ -146,6 +164,9 @@
|
||||
<Page Include="../ColorPickupFlyout.xaml">
|
||||
<Filter>controls</Filter>
|
||||
</Page>
|
||||
<Page Include="../CommandPalette.xaml">
|
||||
<Filter>commandPalette</Filter>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="app">
|
||||
@@ -169,6 +190,9 @@
|
||||
<Filter Include="tab">
|
||||
<UniqueIdentifier>{6d40e12f-b83f-462e-8f93-fa421f87b27e}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="commandPalette">
|
||||
<UniqueIdentifier>{2ad498e1-d8ea-4381-9464-a74c141bd7dd}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="..\App.xaml">
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Foundation.Metadata.h>
|
||||
#include <winrt/Windows.Graphics.Display.h>
|
||||
#include <winrt/windows.ui.core.h>
|
||||
#include <winrt/Windows.ui.input.h>
|
||||
#include <winrt/Windows.UI.Text.h>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
using namespace ::Microsoft::Terminal::Core;
|
||||
using namespace winrt::Windows::Graphics::Display;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Input;
|
||||
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
|
||||
@@ -31,6 +32,9 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
// The updates are throttled to limit power usage.
|
||||
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
|
||||
|
||||
// The minimum delay between updating the TSF input control.
|
||||
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
|
||||
@@ -124,31 +128,36 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
});
|
||||
|
||||
_tsfTryRedrawCanvas = std::make_shared<ThrottledFunc<>>(
|
||||
[weakThis = get_weak()]() {
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
control->TSFInputControl().TryRedrawCanvas();
|
||||
}
|
||||
},
|
||||
TsfRedrawInterval,
|
||||
Dispatcher());
|
||||
|
||||
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
|
||||
[weakThis = get_weak()](const auto& update) {
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
control->Dispatcher()
|
||||
.RunAsync(CoreDispatcherPriority::Normal, [=]() {
|
||||
if (auto control2{ weakThis.get() })
|
||||
{
|
||||
control2->_isInternalScrollBarUpdate = true;
|
||||
control->_isInternalScrollBarUpdate = true;
|
||||
|
||||
auto scrollBar = control2->ScrollBar();
|
||||
if (update.newValue.has_value())
|
||||
{
|
||||
scrollBar.Value(update.newValue.value());
|
||||
}
|
||||
scrollBar.Maximum(update.newMaximum);
|
||||
scrollBar.Minimum(update.newMinimum);
|
||||
scrollBar.ViewportSize(update.newViewportSize);
|
||||
auto scrollBar = control->ScrollBar();
|
||||
if (update.newValue.has_value())
|
||||
{
|
||||
scrollBar.Value(update.newValue.value());
|
||||
}
|
||||
scrollBar.Maximum(update.newMaximum);
|
||||
scrollBar.Minimum(update.newMinimum);
|
||||
scrollBar.ViewportSize(update.newViewportSize);
|
||||
|
||||
control2->_isInternalScrollBarUpdate = false;
|
||||
}
|
||||
});
|
||||
control->_isInternalScrollBarUpdate = false;
|
||||
}
|
||||
},
|
||||
ScrollBarUpdateInterval);
|
||||
ScrollBarUpdateInterval,
|
||||
Dispatcher());
|
||||
|
||||
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
|
||||
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
|
||||
@@ -250,14 +259,32 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Update our control settings
|
||||
_ApplyUISettings();
|
||||
|
||||
// Update DxEngine's SelectionBackground
|
||||
_renderEngine->SetSelectionBackground(_settings.SelectionBackground());
|
||||
|
||||
// Update the terminal core with its new Core settings
|
||||
_terminal->UpdateSettings(_settings);
|
||||
|
||||
auto lock = _terminal->LockForWriting();
|
||||
|
||||
// Update DxEngine settings under the lock
|
||||
_renderEngine->SetSelectionBackground(_settings.SelectionBackground());
|
||||
|
||||
_renderEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
|
||||
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
|
||||
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
|
||||
|
||||
switch (_settings.AntialiasingMode())
|
||||
{
|
||||
case TextAntialiasingMode::Cleartype:
|
||||
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
|
||||
break;
|
||||
case TextAntialiasingMode::Aliased:
|
||||
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
|
||||
break;
|
||||
case TextAntialiasingMode::Grayscale:
|
||||
default:
|
||||
_renderEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
break;
|
||||
}
|
||||
|
||||
// Refresh our font with the renderer
|
||||
const auto actualFontOldSize = _actualFont.GetSize();
|
||||
_UpdateFont();
|
||||
@@ -595,7 +622,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Then, using the font, get the number of characters that can fit.
|
||||
// Resize our terminal connection to match that size, and initialize the terminal with that size.
|
||||
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
|
||||
THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
LOG_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
|
||||
// Update DxEngine's SelectionBackground
|
||||
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
|
||||
@@ -611,13 +638,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
_terminal->CreateFromSettings(_settings, renderTarget);
|
||||
|
||||
// TODO:GH#3927 - Make it possible to hot-reload these settings. Right
|
||||
// here, the setting will only be used when the Terminal is initialized.
|
||||
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
|
||||
dxEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
|
||||
dxEngine->SetSoftwareRendering(_settings.SoftwareRendering());
|
||||
|
||||
// TODO:GH#3927 - hot-reload this one too
|
||||
// Update DxEngine's AntialiasingMode
|
||||
switch (_settings.AntialiasingMode())
|
||||
{
|
||||
@@ -799,7 +823,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const auto modifiers = _GetPressedModifierKeys();
|
||||
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
|
||||
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
|
||||
bool handled = false;
|
||||
|
||||
// Alt-Numpad# input will send us a character once the user releases
|
||||
// Alt, so we should be ignoring the individual keydowns. The character
|
||||
@@ -822,34 +845,43 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// keybindings on the keyUp, then we'll still send the keydown to the
|
||||
// connected terminal application, and something like ctrl+shift+T will
|
||||
// emit a ^T to the pipe.
|
||||
if (!modifiers.IsAltGrPressed() && keyDown)
|
||||
if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, modifiers))
|
||||
{
|
||||
auto bindings = _settings.KeyBindings();
|
||||
if (bindings)
|
||||
{
|
||||
handled = bindings.TryKeyChord({
|
||||
modifiers.IsCtrlPressed(),
|
||||
modifiers.IsAltPressed(),
|
||||
modifiers.IsShiftPressed(),
|
||||
vkey,
|
||||
});
|
||||
}
|
||||
e.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
if (_TrySendKeyEvent(vkey, scanCode, modifiers, keyDown))
|
||||
{
|
||||
handled = _TrySendKeyEvent(vkey, scanCode, modifiers, keyDown);
|
||||
e.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Manually prevent keyboard navigation with tab. We want to send tab to
|
||||
// the terminal, and we don't want to be able to escape focus of the
|
||||
// control with tab.
|
||||
if (e.OriginalKey() == VirtualKey::Tab)
|
||||
e.Handled(e.OriginalKey() == VirtualKey::Tab);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempt to handle this key combination as a key binding
|
||||
// Arguments:
|
||||
// - vkey: The vkey of the key pressed.
|
||||
// - modifiers: The ControlKeyStates representing the modifier key states.
|
||||
bool TermControl::_TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const
|
||||
{
|
||||
auto bindings = _settings.KeyBindings();
|
||||
if (!bindings)
|
||||
{
|
||||
handled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
e.Handled(handled);
|
||||
return bindings.TryKeyChord({
|
||||
modifiers.IsCtrlPressed(),
|
||||
modifiers.IsAltPressed(),
|
||||
modifiers.IsShiftPressed(),
|
||||
vkey,
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1047,35 +1079,43 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const unsigned int MAX_CLICK_COUNT = 3;
|
||||
const auto multiClickMapper = clickCount > MAX_CLICK_COUNT ? ((clickCount + MAX_CLICK_COUNT - 1) % MAX_CLICK_COUNT) + 1 : clickCount;
|
||||
|
||||
if (multiClickMapper == 3)
|
||||
::Terminal::SelectionExpansionMode mode = ::Terminal::SelectionExpansionMode::Cell;
|
||||
if (multiClickMapper == 1)
|
||||
{
|
||||
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Line);
|
||||
_selectionNeedsToBeCopied = true;
|
||||
mode = ::Terminal::SelectionExpansionMode::Cell;
|
||||
}
|
||||
else if (multiClickMapper == 2)
|
||||
{
|
||||
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Word);
|
||||
mode = ::Terminal::SelectionExpansionMode::Word;
|
||||
}
|
||||
else if (multiClickMapper == 3)
|
||||
{
|
||||
mode = ::Terminal::SelectionExpansionMode::Line;
|
||||
}
|
||||
|
||||
// Update the selection appropriately
|
||||
if (shiftEnabled && _terminal->IsSelectionActive())
|
||||
{
|
||||
// Shift+Click: only set expand on the "end" selection point
|
||||
_terminal->SetSelectionEnd(terminalPosition, mode);
|
||||
_selectionNeedsToBeCopied = true;
|
||||
}
|
||||
else if (mode == ::Terminal::SelectionExpansionMode::Cell)
|
||||
{
|
||||
// Single Click: reset the selection and begin a new one
|
||||
_terminal->ClearSelection();
|
||||
_singleClickTouchdownPos = cursorPosition;
|
||||
_selectionNeedsToBeCopied = false; // there's no selection, so there's nothing to update
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shiftEnabled && _terminal->IsSelectionActive())
|
||||
{
|
||||
_terminal->SetSelectionEnd(terminalPosition, ::Terminal::SelectionExpansionMode::Cell);
|
||||
_selectionNeedsToBeCopied = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A single click down resets the selection and begins a new one.
|
||||
_terminal->ClearSelection();
|
||||
_singleClickTouchdownPos = cursorPosition;
|
||||
_selectionNeedsToBeCopied = false; // there's no selection, so there's nothing to update
|
||||
}
|
||||
|
||||
_lastMouseClickTimestamp = point.Timestamp();
|
||||
_lastMouseClickPos = cursorPosition;
|
||||
// Multi-Click Selection: expand both "start" and "end" selection points
|
||||
_terminal->MultiClickSelection(terminalPosition, mode);
|
||||
_selectionNeedsToBeCopied = true;
|
||||
}
|
||||
|
||||
_lastMouseClickTimestamp = point.Timestamp();
|
||||
_lastMouseClickPos = cursorPosition;
|
||||
_renderer->TriggerSelection();
|
||||
}
|
||||
else if (point.Properties().IsRightButtonPressed())
|
||||
@@ -2024,42 +2064,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// to be where the current cursor position is.
|
||||
// Arguments:
|
||||
// - N/A
|
||||
winrt::fire_and_forget TermControl::_TerminalCursorPositionChanged()
|
||||
void TermControl::_TerminalCursorPositionChanged()
|
||||
{
|
||||
bool expectedFalse{ false };
|
||||
if (!_coroutineDispatchStateUpdateInProgress.compare_exchange_weak(expectedFalse, true))
|
||||
{
|
||||
// somebody's already in here.
|
||||
return;
|
||||
}
|
||||
|
||||
if (_closing.load())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto dispatcher{ Dispatcher() }; // cache a strong ref to this in case TermControl dies
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
// Muffle 2: Muffle Harder
|
||||
// If we're the lucky coroutine who gets through, we'll still wait 100ms to clog
|
||||
// the atomic above so we don't service the cursor update too fast. If we get through
|
||||
// and finish processing the update quickly but similar requests are still beating
|
||||
// down the door above in the atomic, we may still update the cursor way more than
|
||||
// is visible to anyone's eye, which is a waste of effort.
|
||||
static constexpr auto CursorUpdateQuiesceTime{ std::chrono::milliseconds(100) };
|
||||
co_await winrt::resume_after(CursorUpdateQuiesceTime);
|
||||
|
||||
co_await winrt::resume_foreground(dispatcher);
|
||||
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
if (!_closing.load())
|
||||
{
|
||||
TSFInputControl().TryRedrawCanvas();
|
||||
}
|
||||
_coroutineDispatchStateUpdateInProgress.store(false);
|
||||
}
|
||||
_tsfTryRedrawCanvas->Run();
|
||||
}
|
||||
|
||||
hstring TermControl::Title()
|
||||
@@ -2236,28 +2243,68 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// as font size, scrollbar and other control scaling, etc. Make sure the
|
||||
// caller knows what monitor the control is about to appear on.
|
||||
// Return Value:
|
||||
// - a point containing the requested dimensions in pixels.
|
||||
winrt::Windows::Foundation::Point TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi)
|
||||
// - a size containing the requested dimensions in pixels.
|
||||
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi)
|
||||
{
|
||||
// If the settings have negative or zero row or column counts, ignore those counts.
|
||||
// (The lower TerminalCore layer also has upper bounds as well, but at this layer
|
||||
// we may eventually impose different ones depending on how many pixels we can address.)
|
||||
const auto cols = ::base::saturated_cast<float>(std::max(settings.InitialCols(), 1));
|
||||
const auto rows = ::base::saturated_cast<float>(std::max(settings.InitialRows(), 1));
|
||||
|
||||
const winrt::Windows::Foundation::Size initialSize{ cols, rows };
|
||||
|
||||
return GetProposedDimensions(initialSize,
|
||||
settings.FontSize(),
|
||||
settings.FontWeight(),
|
||||
settings.FontFace(),
|
||||
settings.ScrollState(),
|
||||
settings.Padding(),
|
||||
dpi);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Determines how much space (in pixels) an app would need to reserve to
|
||||
// create a control with the settings stored in the settings param. This
|
||||
// accounts for things like the font size and face, the initialRows and
|
||||
// initialCols, and scrollbar visibility. The returned sized is based upon
|
||||
// the provided DPI value
|
||||
// Arguments:
|
||||
// - initialSizeInChars: The size to get the proposed dimensions for.
|
||||
// - fontHeight: The font height to use to calculate the proposed size for.
|
||||
// - fontWeight: The font weight to use to calculate the proposed size for.
|
||||
// - fontFace: The font name to use to calculate the proposed size for.
|
||||
// - scrollState: The ScrollbarState to use to calculate the proposed size for.
|
||||
// - padding: The padding to use to calculate the proposed size for.
|
||||
// - dpi: The DPI we should create the terminal at. This affects things such
|
||||
// as font size, scrollbar and other control scaling, etc. Make sure the
|
||||
// caller knows what monitor the control is about to appear on.
|
||||
// Return Value:
|
||||
// - a size containing the requested dimensions in pixels.
|
||||
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
|
||||
const int32_t& fontHeight,
|
||||
const winrt::Windows::UI::Text::FontWeight& fontWeight,
|
||||
const winrt::hstring& fontFace,
|
||||
const Microsoft::Terminal::Settings::ScrollbarState& scrollState,
|
||||
const winrt::hstring& padding,
|
||||
const uint32_t dpi)
|
||||
{
|
||||
const auto cols = ::base::saturated_cast<int>(initialSizeInChars.Width);
|
||||
const auto rows = ::base::saturated_cast<int>(initialSizeInChars.Height);
|
||||
|
||||
// Initialize our font information.
|
||||
const auto fontFace = settings.FontFace();
|
||||
const short fontHeight = gsl::narrow_cast<short>(settings.FontSize());
|
||||
const auto fontWeight = settings.FontWeight();
|
||||
// const auto fontFace = settings.FontFace();
|
||||
// const short fontHeight = gsl::narrow_cast<short>(fontSize);
|
||||
// const auto fontWeight = settings.FontWeight();
|
||||
// The font width doesn't terribly matter, we'll only be using the
|
||||
// height to look it up
|
||||
// The other params here also largely don't matter.
|
||||
// The family is only used to determine if the font is truetype or
|
||||
// not, but DX doesn't use that info at all.
|
||||
// The Codepage is additionally not actually used by the DX engine at all.
|
||||
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, fontHeight }, CP_UTF8, false };
|
||||
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, gsl::narrow_cast<short>(fontHeight) }, CP_UTF8, false };
|
||||
FontInfoDesired desiredFont = { actualFont };
|
||||
|
||||
// If the settings have negative or zero row or column counts, ignore those counts.
|
||||
// (The lower TerminalCore layer also has upper bounds as well, but at this layer
|
||||
// we may eventually impose different ones depending on how many pixels we can address.)
|
||||
const auto cols = std::max(settings.InitialCols(), 1);
|
||||
const auto rows = std::max(settings.InitialRows(), 1);
|
||||
|
||||
// Create a DX engine and initialize it with our font and DPI. We'll
|
||||
// then use it to measure how much space the requested rows and columns
|
||||
// will take up.
|
||||
@@ -2277,13 +2324,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
double width = cols * fontSize.X;
|
||||
|
||||
// Reserve additional space if scrollbar is intended to be visible
|
||||
if (settings.ScrollState() == ScrollbarState::Visible)
|
||||
if (scrollState == ScrollbarState::Visible)
|
||||
{
|
||||
width += scrollbarSize;
|
||||
}
|
||||
|
||||
double height = rows * fontSize.Y;
|
||||
auto thickness = _ParseThicknessFromPadding(settings.Padding());
|
||||
auto thickness = _ParseThicknessFromPadding(padding);
|
||||
// GH#2061 - make sure to account for the size the padding _will be_ scaled to
|
||||
width += scale * (thickness.Left + thickness.Right);
|
||||
height += scale * (thickness.Top + thickness.Bottom);
|
||||
@@ -2316,21 +2363,41 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// have a visible character.
|
||||
winrt::Windows::Foundation::Size TermControl::MinimumSize()
|
||||
{
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
double width = fontSize.X;
|
||||
double height = fontSize.Y;
|
||||
// Reserve additional space if scrollbar is intended to be visible
|
||||
if (_settings.ScrollState() == ScrollbarState::Visible)
|
||||
if (_initializedTerminal)
|
||||
{
|
||||
width += ScrollBar().ActualWidth();
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
double width = fontSize.X;
|
||||
double height = fontSize.Y;
|
||||
// Reserve additional space if scrollbar is intended to be visible
|
||||
if (_settings.ScrollState() == ScrollbarState::Visible)
|
||||
{
|
||||
width += ScrollBar().ActualWidth();
|
||||
}
|
||||
|
||||
// Account for the size of any padding
|
||||
const auto padding = SwapChainPanel().Margin();
|
||||
width += padding.Left + padding.Right;
|
||||
height += padding.Top + padding.Bottom;
|
||||
|
||||
return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the terminal hasn't been initialized yet, then the font size will
|
||||
// have dimensions {1, fontSize.Y}, which can mess with consumers of
|
||||
// this method. In that case, we'll need to pre-calculate the font
|
||||
// width, before we actually have a renderer or swapchain.
|
||||
const winrt::Windows::Foundation::Size minSize{ 1, 1 };
|
||||
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
|
||||
const auto dpi = ::base::saturated_cast<uint32_t>(USER_DEFAULT_SCREEN_DPI * scaleFactor);
|
||||
return GetProposedDimensions(minSize,
|
||||
_settings.FontSize(),
|
||||
_settings.FontWeight(),
|
||||
_settings.FontFace(),
|
||||
_settings.ScrollState(),
|
||||
_settings.Padding(),
|
||||
dpi);
|
||||
}
|
||||
|
||||
// Account for the size of any padding
|
||||
const auto padding = SwapChainPanel().Margin();
|
||||
width += padding.Left + padding.Right;
|
||||
height += padding.Top + padding.Bottom;
|
||||
|
||||
return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -98,7 +98,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
TerminalConnection::ConnectionState ConnectionState() const;
|
||||
|
||||
static Windows::Foundation::Point GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi);
|
||||
static Windows::Foundation::Size GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi);
|
||||
static Windows::Foundation::Size GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
|
||||
const int32_t& fontSize,
|
||||
const winrt::Windows::UI::Text::FontWeight& fontWeight,
|
||||
const winrt::hstring& fontFace,
|
||||
const Microsoft::Terminal::Settings::ScrollbarState& scrollState,
|
||||
const winrt::hstring& padding,
|
||||
const uint32_t dpi);
|
||||
|
||||
// clang-format off
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
@@ -136,6 +143,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
FontInfoDesired _desiredFont;
|
||||
FontInfo _actualFont;
|
||||
|
||||
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
|
||||
|
||||
struct ScrollBarUpdate
|
||||
{
|
||||
std::optional<double> newValue;
|
||||
@@ -210,7 +219,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void _RefreshSizeUnderLock();
|
||||
void _TerminalTitleChanged(const std::wstring_view& wstr);
|
||||
void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
|
||||
winrt::fire_and_forget _TerminalCursorPositionChanged();
|
||||
void _TerminalCursorPositionChanged();
|
||||
|
||||
void _MouseScrollHandler(const double mouseDelta, const Windows::Foundation::Point point, const bool isLeftButtonPressed);
|
||||
void _MouseZoomHandler(const double delta);
|
||||
@@ -228,6 +237,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
void _KeyHandler(Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e, const bool keyDown);
|
||||
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
|
||||
bool _TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const;
|
||||
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
|
||||
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
|
||||
bool _CanSendVTMouseInput();
|
||||
@@ -245,12 +255,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs);
|
||||
|
||||
winrt::fire_and_forget _AsyncCloseConnection();
|
||||
|
||||
// this atomic is to be used as a guard against dispatching billions of coroutines for
|
||||
// routine state changes that might happen millions of times a second.
|
||||
// Unbounded main dispatcher use leads to massive memory leaks and intense slowdowns
|
||||
// on the UI thread.
|
||||
std::atomic<bool> _coroutineDispatchStateUpdateInProgress{ false };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
TermControl();
|
||||
TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
|
||||
static Windows.Foundation.Point GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi);
|
||||
static Windows.Foundation.Size GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi);
|
||||
|
||||
void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings);
|
||||
|
||||
|
||||