mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-22 14:11:26 +00:00
Compare commits
21 Commits
dev/duhowe
...
dev/miniks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7649fc8a06 | ||
|
|
343ff0913d | ||
|
|
39d67e3859 | ||
|
|
1ae4252a7b | ||
|
|
2872f147f8 | ||
|
|
28d108bf32 | ||
|
|
91b84f185e | ||
|
|
dfc15780c7 | ||
|
|
ef80f665d3 | ||
|
|
d7123d571b | ||
|
|
5e9adad2a8 | ||
|
|
8c4ca4683b | ||
|
|
5bcf0fc3de | ||
|
|
d47da2d617 | ||
|
|
31efd69149 | ||
|
|
2c55ca107f | ||
|
|
b3fa88eaed | ||
|
|
9e8a716f03 | ||
|
|
78c1bc10f7 | ||
|
|
b46b5d0f6f | ||
|
|
7e8f0398c3 |
479826
.github/actions/spell-check/dictionary/dictionary.txt
vendored
479826
.github/actions/spell-check/dictionary/dictionary.txt
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
powf
|
||||
sqrtf
|
||||
@@ -1,7 +0,0 @@
|
||||
mfcribbon
|
||||
microsoft
|
||||
microsoftonline
|
||||
osgvsowi
|
||||
powershell
|
||||
tdbuildteamid
|
||||
visualstudio
|
||||
61
.github/actions/spell-check/excludes.txt
vendored
61
.github/actions/spell-check/excludes.txt
vendored
@@ -1,61 +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$
|
||||
^dep/
|
||||
^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$
|
||||
@@ -1,7 +0,0 @@
|
||||
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
|
||||
(?:0[Xx]|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]?\b
|
||||
\{[0-9A-FA-F]{8}-(?:[0-9A-FA-F]{4}-){3}[0-9A-FA-F]{12}\}
|
||||
\d+x\d+Logo
|
||||
Scro\&ll
|
||||
# selectionInput.cpp
|
||||
:\\windows\\syste\b
|
||||
@@ -1,7 +0,0 @@
|
||||
The contents of each `.txt` file in this directory are merged together.
|
||||
|
||||
* [alphabet](alphabet.txt) is a sample for alphabet related items
|
||||
* [web](web.txt) is a sample for web/html related items
|
||||
* [whitelist](whitelist.txt) is the main whitelist -- there is nothing
|
||||
particularly special about the file name (beyond the extension which is
|
||||
important).
|
||||
@@ -1,3 +0,0 @@
|
||||
http
|
||||
td
|
||||
www
|
||||
15
.github/actions/spelling/README.md
vendored
Normal file
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
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
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
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
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
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
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,55 +1,91 @@
|
||||
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
|
||||
Pham
|
||||
Rincewind
|
||||
rprichard
|
||||
Schoonover
|
||||
shadertoy
|
||||
Shomnipotence
|
||||
simioni
|
||||
Somuah
|
||||
sonph
|
||||
sonpham
|
||||
stakx
|
||||
talo
|
||||
thereses
|
||||
Walisch
|
||||
WDX
|
||||
Wellons
|
||||
Wirt
|
||||
Wojciech
|
||||
zadjii
|
||||
Zamor
|
||||
Zamora
|
||||
zamora
|
||||
Zoey
|
||||
zorio
|
||||
Zverovich
|
||||
523
.github/actions/spelling/candidate.patterns
vendored
Normal file
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
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$
|
||||
13
.github/actions/spelling/expect/README.md
vendored
Normal file
13
.github/actions/spelling/expect/README.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
The contents of each `.txt` file in this directory are merged together.
|
||||
|
||||
* [alphabet](alphabet.txt) is a sample for alphabet related items
|
||||
* [web](web.txt) is a sample for web/html related items
|
||||
* [expect](expect.txt) is the main list of expected items -- there is nothing
|
||||
particularly special about the file name (beyond the extension which is
|
||||
important).
|
||||
|
||||
These terms are things which temporarily exist in the project, but which
|
||||
aren't necessarily words.
|
||||
|
||||
If something is a word that could come and go, it probably belongs in a
|
||||
[dictionary](../dictionary/README.md).
|
||||
@@ -1,27 +1,33 @@
|
||||
AAAa
|
||||
AAAAA
|
||||
AAAAAAAAAAAAA
|
||||
AAAAAABBBBBBCCC
|
||||
AAAAABBBBBBCCC
|
||||
abcd
|
||||
abcd
|
||||
abcde
|
||||
abcdef
|
||||
ABCDEFG
|
||||
ABCDEFGH
|
||||
ABCDEFGHIJ
|
||||
abcdefghijk
|
||||
ABCDEFGHIJKLMNO
|
||||
abcdefghijklmnop
|
||||
ABCDEFGHIJKLMNOPQRST
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
QQQQQ
|
||||
QQQQQQQQQ
|
||||
QQQQQQQQQQ
|
||||
ABCG
|
||||
ABE
|
||||
abf
|
||||
BBBBB
|
||||
BBBBBBBB
|
||||
BBBBBCCC
|
||||
BBBBCCCCC
|
||||
BBGGRR
|
||||
EFG
|
||||
EFGh
|
||||
QQQQQQQQQQABCDEFGHIJ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
|
||||
qrstuvwxyz
|
||||
qwerty
|
||||
QWERTYUIOP
|
||||
qwertyuiopasdfg
|
||||
TTTTTTTTTTTTTTTTTTTTTTTTTT
|
||||
VVVVVVVVVVVVVVVV
|
||||
yyyy
|
||||
YYYYYYYDDDDDDDDDDD
|
||||
ZAAZZ
|
||||
ZABBZ
|
||||
ZBAZZ
|
||||
File diff suppressed because it is too large
Load Diff
6
.github/actions/spelling/expect/web.txt
vendored
Normal file
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
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
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
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
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.12-alpha
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
bucket: .github/actions
|
||||
project: spell-check
|
||||
134
.github/workflows/spelling2.yml
vendored
Normal file
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 }}
|
||||
@@ -72,12 +72,25 @@ Assuming that you've installed Git Bash into `C:/Program Files/Git`:
|
||||
```json
|
||||
{
|
||||
"name" : "Git Bash",
|
||||
"commandline" : "C:/Program Files/Git/bin/bash.exe",
|
||||
"commandline" : "C:/Program Files/Git/bin/bash.exe -li",
|
||||
"icon" : "C:/Program Files/Git/mingw64/share/git/git-for-windows.ico",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
````
|
||||
|
||||
## Git Bash (WOW64)
|
||||
|
||||
Assuming that you've installed Git Bash into `C:/Program Files (x86)/Git`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name" : "Git Bash",
|
||||
"commandline" : "%ProgramFiles(x86)%/Git/bin/bash.exe -li",
|
||||
"icon" : "%ProgramFiles(x86)%/Git/mingw32/share/git/git-for-windows.ico",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
```
|
||||
|
||||
## MSYS2
|
||||
|
||||
Assuming that you've installed MSYS2 into `C:/msys64`:
|
||||
@@ -91,4 +104,16 @@ Assuming that you've installed MSYS2 into `C:/msys64`:
|
||||
}
|
||||
````
|
||||
|
||||
## Developer Command Prompt for Visual Studio
|
||||
|
||||
Assuming that you've installed VS 2019 Professional:
|
||||
|
||||
```json
|
||||
{
|
||||
"name" : "Developer Command Prompt for VS 2019",
|
||||
"commandline" : "cmd.exe /k \"C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/Tools/VsDevCmd.bat\"",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Adding a tool here? Make sure to add it in alphabetical order! -->
|
||||
|
||||
@@ -107,6 +107,22 @@ add the following to your keybindings:
|
||||
This will _unbind_ <kbd>Ctrl+Shift+6</kbd>, allowing vim to use the keystroke
|
||||
instead of the terminal.
|
||||
|
||||
### Binding multiple keys
|
||||
|
||||
You can have multiple key chords bound to the same action. To do this, simply
|
||||
add multiple bindings for the same action. For example:
|
||||
|
||||
```json
|
||||
"keybindings" :
|
||||
[
|
||||
{ "command": "copy", "keys": "ctrl+shift+c" },
|
||||
{ "command": "copy", "keys": "ctrl+c" },
|
||||
{ "command": "copy", "keys": "enter" }
|
||||
]
|
||||
```
|
||||
|
||||
In this snippet, all three of <kbd>ctrl+shift+c</kbd>, <kbd>ctrl+c</kbd> and <kbd>enter</kbd> are bound to `copy`.
|
||||
|
||||
## Profiles
|
||||
|
||||
A profile contains the settings applied when a new WT tab is opened. Each
|
||||
|
||||
@@ -70,6 +70,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TEST_METHOD(TestTerminalArgsForBinding);
|
||||
|
||||
TEST_METHOD(FindMissingProfile);
|
||||
TEST_METHOD(MakeSettingsForProfileThatDoesntExist);
|
||||
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
|
||||
|
||||
TEST_METHOD(TestLayerProfileOnColorScheme);
|
||||
|
||||
TEST_METHOD(ValidateKeybindingsWarnings);
|
||||
@@ -2094,6 +2098,146 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::FindMissingProfile()
|
||||
{
|
||||
// Test that CascadiaSettings::FindProfile returns null for a GUID that
|
||||
// doesn't exist
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
|
||||
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
const Profile* const profile1 = settings->FindProfile(guid1);
|
||||
const Profile* const profile2 = settings->FindProfile(guid2);
|
||||
const Profile* const profile3 = settings->FindProfile(guid3);
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile1);
|
||||
VERIFY_IS_NOT_NULL(profile2);
|
||||
VERIFY_IS_NULL(profile3);
|
||||
|
||||
VERIFY_ARE_EQUAL(L"profile0", profile1->GetName());
|
||||
VERIFY_ARE_EQUAL(L"profile1", profile2->GetName());
|
||||
}
|
||||
|
||||
void SettingsTests::MakeSettingsForProfileThatDoesntExist()
|
||||
{
|
||||
// Test that MakeSettings throws when the GUID doesn't exist
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
|
||||
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings = settings->BuildSettings(guid1);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(1, terminalSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings = settings->BuildSettings(guid2);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(2, terminalSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
|
||||
VERIFY_THROWS(auto terminalSettings = settings->BuildSettings(guid3), wil::ResultException, L"This call to BuildSettings should fail");
|
||||
|
||||
try
|
||||
{
|
||||
const auto [guid, termSettings] = settings->BuildSettings(nullptr);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::MakeSettingsForDefaultProfileThatDoesntExist()
|
||||
{
|
||||
// Test that MakeSettings _doesnt_ throw when we load settings with a
|
||||
// defaultProfile that's not in the list, we validate the settings, and
|
||||
// then call MakeSettings(nullopt). The validation should ensure that
|
||||
// the default profile is something reasonable
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
|
||||
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(2u, settings->_profiles.size());
|
||||
VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid());
|
||||
try
|
||||
{
|
||||
const auto [guid, termSettings] = settings->BuildSettings(nullptr);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::TestLayerProfileOnColorScheme()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
@@ -53,11 +55,20 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(CreateSimpleTerminalXamlType);
|
||||
TEST_METHOD(CreateTerminalMuxXamlType);
|
||||
|
||||
TEST_METHOD(CreateTerminalPage);
|
||||
|
||||
TEST_METHOD(TryDuplicateBadTab);
|
||||
TEST_METHOD(TryDuplicateBadPane);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
|
||||
std::shared_ptr<CascadiaSettings> initialSettings);
|
||||
};
|
||||
|
||||
void TabTests::EnsureTestsActivate()
|
||||
@@ -164,4 +175,321 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::CreateTerminalPage()
|
||||
{
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>();
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper to set up a TerminalPage for a unittest. This method
|
||||
// does a couple things:
|
||||
// * Create()'s a TerminalPage with the given settings. Constructing a
|
||||
// TerminalPage so that we can get at its implementation is wacky, so
|
||||
// this helper will do it correctly for you, even if this doesn't make a
|
||||
// ton of sense on the surface. This is also why you need to pass both a
|
||||
// projection and a com_ptr to this method.
|
||||
// * It will use the provided settings object to initialize the TerminalPage
|
||||
// * It will add the TerminalPage to the test Application, so that we can
|
||||
// get actual layout events. Much of the Terminal assumes there's a
|
||||
// non-zero ActualSize to the Terminal window, and adding the Page to
|
||||
// the Application will make it behave as expected.
|
||||
// * It will wait for the TerminalPage to finish initialization before
|
||||
// returning control to the caller. It does this by creating an event and
|
||||
// only setting the event when the TerminalPage raises its Initialized
|
||||
// event, to signal that startup is complete. At this point, there will
|
||||
// be one tab with the default profile in the page.
|
||||
// * It will also ensure that the first tab is focused, since that happens
|
||||
// asynchronously in the application typically.
|
||||
// Arguments:
|
||||
// - page: a TerminalPage implementation ptr that will receive the new TerminalPage instance
|
||||
// - initialSettings: a CascadiaSettings to initialize the TerminalPage with.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TabTests::_initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
|
||||
std::shared_ptr<CascadiaSettings> initialSettings)
|
||||
{
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::TerminalApp::TerminalPage projectedPage{ nullptr };
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Construct the TerminalPage"));
|
||||
auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() {
|
||||
projectedPage = winrt::TerminalApp::TerminalPage();
|
||||
page.copy_from(winrt::get_self<winrt::TerminalApp::implementation::TerminalPage>(projectedPage));
|
||||
page->_settings = initialSettings;
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
VERIFY_IS_NOT_NULL(page->_settings);
|
||||
|
||||
::details::Event waitForInitEvent;
|
||||
if (!waitForInitEvent.IsValid())
|
||||
{
|
||||
VERIFY_SUCCEEDED(HRESULT_FROM_WIN32(::GetLastError()));
|
||||
}
|
||||
page->Initialized([&waitForInitEvent](auto&&, auto&&) {
|
||||
waitForInitEvent.Set();
|
||||
});
|
||||
|
||||
Log::Comment(L"Create() the TerminalPage");
|
||||
|
||||
result = RunOnUIThread([&page]() {
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
VERIFY_IS_NOT_NULL(page->_settings);
|
||||
page->Create();
|
||||
Log::Comment(L"Create()'d the page successfully");
|
||||
|
||||
auto app = ::winrt::Windows::UI::Xaml::Application::Current();
|
||||
|
||||
winrt::TerminalApp::TerminalPage pp = *page;
|
||||
winrt::Windows::UI::Xaml::Window::Current().Content(pp);
|
||||
winrt::Windows::UI::Xaml::Window::Current().Activate();
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Wait for the page to finish initializing...");
|
||||
VERIFY_SUCCEEDED(waitForInitEvent.Wait());
|
||||
Log::Comment(L"...Done");
|
||||
|
||||
result = RunOnUIThread([&page]() {
|
||||
// In the real app, this isn't a problem, but doesn't happen
|
||||
// reliably in the unit tests.
|
||||
Log::Comment(L"Ensure we set the first tab as the selected one.");
|
||||
auto tab{ page->_GetStrongTabImpl(0) };
|
||||
page->_tabView.SelectedItem(tab->GetTabViewItem());
|
||||
page->_UpdatedSelectedTab(0);
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::TryDuplicateBadTab()
|
||||
{
|
||||
// * Create a tab with a profile with GUID 1
|
||||
// * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// * Try calling _DuplicateTabViewItem on tab 1
|
||||
// * No new tab should be created (and more importantly, the app should not crash)
|
||||
//
|
||||
// Created to test GH#2455
|
||||
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const std::string settingsJson1{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settingsJson0);
|
||||
auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
settings0->_ParseJsonString(settingsJson0, false);
|
||||
settings0->LayerJson(settings0->_userSettings);
|
||||
settings0->_ValidateSettings();
|
||||
|
||||
VerifyParseSucceeded(settingsJson1);
|
||||
auto settings1 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings1);
|
||||
settings1->_ParseJsonString(settingsJson1, false);
|
||||
settings1->LayerJson(settings1->_userSettings);
|
||||
settings1->_ValidateSettings();
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
_initializeTerminalPage(page, settings0);
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Duplicate the first tab");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateTabViewItem();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Change the settings of the TerminalPage so the first profile is "
|
||||
L"no longer in the list of profiles"));
|
||||
result = RunOnUIThread([&page, settings1]() {
|
||||
page->_settings = settings1;
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Duplicate the tab, and don't crash");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateTabViewItem();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::TryDuplicateBadPane()
|
||||
{
|
||||
// * Create a tab with a profile with GUID 1
|
||||
// * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// * Try calling _SplitPane(Duplicate) on tab 1
|
||||
// * No new pane should be created (and more importantly, the app should not crash)
|
||||
//
|
||||
// Created to test GH#2455
|
||||
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const std::string settingsJson1{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settingsJson0);
|
||||
auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
settings0->_ParseJsonString(settingsJson0, false);
|
||||
settings0->LayerJson(settings0->_userSettings);
|
||||
settings0->_ValidateSettings();
|
||||
|
||||
VerifyParseSucceeded(settingsJson1);
|
||||
auto settings1 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings1);
|
||||
settings1->_ParseJsonString(settingsJson1, false);
|
||||
settings1->LayerJson(settings1->_userSettings);
|
||||
settings1->_ValidateSettings();
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
_initializeTerminalPage(page, settings0);
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(1, tab->_GetLeafPaneCount());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2, tab->_GetLeafPaneCount());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Change the settings of the TerminalPage so the first profile is "
|
||||
L"no longer in the list of profiles"));
|
||||
result = RunOnUIThread([&page, settings1]() {
|
||||
page->_settings = settings1;
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2,
|
||||
tab->_GetLeafPaneCount(),
|
||||
L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
auto cleanup = wil::scope_exit([] {
|
||||
auto result = RunOnUIThread([]() {
|
||||
// There's something causing us to crash north of
|
||||
// TSFInputControl::NotifyEnter, or LayoutRequested. It's very
|
||||
// unclear what that issue is. Since these tests don't run in
|
||||
// CI, simply log a message so that the dev running these tests
|
||||
// knows it's expected.
|
||||
Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
147
src/cascadia/TerminalApp/DebugTapConnection.cpp
Normal file
147
src/cascadia/TerminalApp/DebugTapConnection.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "DebugTapConnection.h"
|
||||
|
||||
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
|
||||
using namespace ::winrt::Windows::Foundation;
|
||||
namespace winrt::Microsoft::TerminalApp::implementation
|
||||
{
|
||||
// DebugInputTapConnection is an implementation detail of DebugTapConnection.
|
||||
// It wraps the _actual_ connection so it can hook WriteInput and forward it
|
||||
// into the actual debug panel.
|
||||
class DebugInputTapConnection : public winrt::implements<DebugInputTapConnection, ITerminalConnection>
|
||||
{
|
||||
public:
|
||||
DebugInputTapConnection(winrt::com_ptr<DebugTapConnection> pairedTap, ITerminalConnection wrappedConnection) :
|
||||
_pairedTap{ pairedTap },
|
||||
_wrappedConnection{ std::move(wrappedConnection) }
|
||||
{
|
||||
}
|
||||
~DebugInputTapConnection() = default;
|
||||
void Start()
|
||||
{
|
||||
_wrappedConnection.Start();
|
||||
}
|
||||
void WriteInput(hstring const& data)
|
||||
{
|
||||
_pairedTap->_PrintInput(data);
|
||||
_wrappedConnection.WriteInput(data);
|
||||
}
|
||||
void Resize(uint32_t rows, uint32_t columns) { _wrappedConnection.Resize(rows, columns); }
|
||||
void Close() { _wrappedConnection.Close(); }
|
||||
winrt::event_token TerminalOutput(TerminalOutputHandler const& args) { return _wrappedConnection.TerminalOutput(args); };
|
||||
void TerminalOutput(winrt::event_token const& token) noexcept { _wrappedConnection.TerminalOutput(token); };
|
||||
winrt::event_token StateChanged(TypedEventHandler<ITerminalConnection, IInspectable> const& handler) { return _wrappedConnection.StateChanged(handler); };
|
||||
void StateChanged(winrt::event_token const& token) noexcept { _wrappedConnection.StateChanged(token); };
|
||||
ConnectionState State() const noexcept { return _wrappedConnection.State(); }
|
||||
|
||||
private:
|
||||
winrt::com_ptr<DebugTapConnection> _pairedTap;
|
||||
ITerminalConnection _wrappedConnection;
|
||||
};
|
||||
|
||||
DebugTapConnection::DebugTapConnection(ITerminalConnection wrappedConnection)
|
||||
{
|
||||
_outputRevoker = wrappedConnection.TerminalOutput(winrt::auto_revoke, { this, &DebugTapConnection::_OutputHandler });
|
||||
_stateChangedRevoker = wrappedConnection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*e*/) {
|
||||
_StateChangedHandlers(*this, nullptr);
|
||||
});
|
||||
_wrappedConnection = wrappedConnection;
|
||||
}
|
||||
|
||||
DebugTapConnection::~DebugTapConnection()
|
||||
{
|
||||
}
|
||||
|
||||
void DebugTapConnection::Start()
|
||||
{
|
||||
// presume the wrapped connection is started.
|
||||
}
|
||||
|
||||
void DebugTapConnection::WriteInput(hstring const& data)
|
||||
{
|
||||
// If the user types into the tap side, forward it to the input side
|
||||
if (auto strongInput{ _inputSide.get() })
|
||||
{
|
||||
auto inputAsTap{ winrt::get_self<DebugInputTapConnection>(strongInput) };
|
||||
inputAsTap->WriteInput(data);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugTapConnection::Resize(uint32_t /*rows*/, uint32_t /*columns*/)
|
||||
{
|
||||
// no resize events are propagated
|
||||
}
|
||||
|
||||
void DebugTapConnection::Close()
|
||||
{
|
||||
_outputRevoker.revoke();
|
||||
_stateChangedRevoker.revoke();
|
||||
_wrappedConnection = nullptr;
|
||||
}
|
||||
|
||||
ConnectionState DebugTapConnection::State() const noexcept
|
||||
{
|
||||
if (auto strongConnection{ _wrappedConnection.get() })
|
||||
{
|
||||
return strongConnection.State();
|
||||
}
|
||||
return ConnectionState::Failed;
|
||||
}
|
||||
|
||||
static std::wstring _sanitizeString(const std::wstring_view str)
|
||||
{
|
||||
std::wstring newString{ str.begin(), str.end() };
|
||||
for (auto& ch : newString)
|
||||
{
|
||||
if (ch < 0x20)
|
||||
{
|
||||
ch += 0x2400;
|
||||
}
|
||||
else if (ch == 0x20)
|
||||
{
|
||||
ch = 0x2423; // replace space with ␣
|
||||
}
|
||||
else if (ch == 0x7f)
|
||||
{
|
||||
ch = 0x2421; // replace del with ␡
|
||||
}
|
||||
}
|
||||
return newString;
|
||||
}
|
||||
|
||||
void DebugTapConnection::_OutputHandler(const hstring str)
|
||||
{
|
||||
_TerminalOutputHandlers(_sanitizeString(str));
|
||||
}
|
||||
|
||||
// Called by the DebugInputTapConnection to print user input
|
||||
void DebugTapConnection::_PrintInput(const hstring& str)
|
||||
{
|
||||
auto clean{ _sanitizeString(str) };
|
||||
auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
|
||||
_TerminalOutputHandlers(formatted);
|
||||
}
|
||||
|
||||
// Wire us up so that we can forward input through
|
||||
void DebugTapConnection::SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap)
|
||||
{
|
||||
_inputSide = inputTap;
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description
|
||||
// - Takes one connection and returns two connections:
|
||||
// 1. One that can be used in place of the original connection (wrapped)
|
||||
// 2. One that will print raw VT sequences sent into and received _from_ the original connection.
|
||||
std::tuple<ITerminalConnection, ITerminalConnection> OpenDebugTapConnection(ITerminalConnection baseConnection)
|
||||
{
|
||||
using namespace winrt::Microsoft::TerminalApp::implementation;
|
||||
auto debugSide{ winrt::make_self<DebugTapConnection>(baseConnection) };
|
||||
auto inputSide{ winrt::make_self<DebugInputTapConnection>(debugSide, baseConnection) };
|
||||
debugSide->SetInputTap(*inputSide);
|
||||
std::tuple<ITerminalConnection, ITerminalConnection> p{ *inputSide, *debugSide };
|
||||
return p;
|
||||
}
|
||||
42
src/cascadia/TerminalApp/DebugTapConnection.h
Normal file
42
src/cascadia/TerminalApp/DebugTapConnection.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
|
||||
#include "../../inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::TerminalApp::implementation
|
||||
{
|
||||
class DebugInputTapConnection;
|
||||
class DebugTapConnection : public winrt::implements<DebugTapConnection, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection>
|
||||
{
|
||||
public:
|
||||
DebugTapConnection(Microsoft::Terminal::TerminalConnection::ITerminalConnection wrappedConnection);
|
||||
~DebugTapConnection();
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
void Close();
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept;
|
||||
|
||||
void SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap);
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);
|
||||
|
||||
TYPED_EVENT(StateChanged, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
private:
|
||||
void _PrintInput(const hstring& data);
|
||||
void _OutputHandler(const hstring str);
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::TerminalOutput_revoker _outputRevoker;
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::StateChanged_revoker _stateChangedRevoker;
|
||||
winrt::weak_ref<Microsoft::Terminal::TerminalConnection::ITerminalConnection> _wrappedConnection;
|
||||
winrt::weak_ref<Microsoft::Terminal::TerminalConnection::ITerminalConnection> _inputSide;
|
||||
|
||||
friend class DebugInputTapConnection;
|
||||
};
|
||||
}
|
||||
|
||||
std::tuple<winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection> OpenDebugTapConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection baseConnection);
|
||||
@@ -41,6 +41,14 @@ static constexpr std::wstring_view LightThemeValue{ L"light" };
|
||||
static constexpr std::wstring_view DarkThemeValue{ L"dark" };
|
||||
static constexpr std::wstring_view SystemThemeValue{ L"system" };
|
||||
|
||||
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
|
||||
|
||||
#ifdef _DEBUG
|
||||
static constexpr bool debugFeaturesDefault{ true };
|
||||
#else
|
||||
static constexpr bool debugFeaturesDefault{ false };
|
||||
#endif
|
||||
|
||||
GlobalAppSettings::GlobalAppSettings() :
|
||||
_keybindings{ winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>() },
|
||||
_keybindingsWarnings{},
|
||||
@@ -59,7 +67,8 @@ GlobalAppSettings::GlobalAppSettings() :
|
||||
_tabWidthMode{ TabViewWidthMode::Equal },
|
||||
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||
_copyOnSelect{ false },
|
||||
_launchMode{ LaunchMode::DefaultMode }
|
||||
_launchMode{ LaunchMode::DefaultMode },
|
||||
_debugFeatures{ debugFeaturesDefault }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -171,6 +180,11 @@ void GlobalAppSettings::SetConfirmCloseAllTabs(const bool confirmCloseAllTabs) n
|
||||
_confirmCloseAllTabs = confirmCloseAllTabs;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::DebugFeaturesEnabled() const noexcept
|
||||
{
|
||||
return _debugFeatures;
|
||||
}
|
||||
|
||||
#pragma region ExperimentalSettings
|
||||
bool GlobalAppSettings::GetShowTabsInTitlebar() const noexcept
|
||||
{
|
||||
@@ -237,6 +251,7 @@ Json::Value GlobalAppSettings::ToJson() const
|
||||
jsonObject[JsonKey(KeybindingsKey)] = _keybindings->ToJson();
|
||||
jsonObject[JsonKey(ConfirmCloseAllKey)] = _confirmCloseAllTabs;
|
||||
jsonObject[JsonKey(SnapToGridOnResizeKey)] = _SnapToGridOnResize;
|
||||
jsonObject[JsonKey(DebugFeaturesKey)] = _debugFeatures;
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
@@ -324,6 +339,9 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
}
|
||||
|
||||
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
|
||||
|
||||
// GetBool will only override the current value if the key exists
|
||||
JsonUtils::GetBool(json, DebugFeaturesKey, _debugFeatures);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -77,6 +77,8 @@ public:
|
||||
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode GetTabWidthMode() const noexcept;
|
||||
|
||||
bool DebugFeaturesEnabled() const noexcept;
|
||||
|
||||
Json::Value ToJson() const;
|
||||
static GlobalAppSettings FromJson(const Json::Value& json);
|
||||
void LayerJson(const Json::Value& json);
|
||||
@@ -115,6 +117,8 @@ private:
|
||||
|
||||
winrt::TerminalApp::LaunchMode _launchMode;
|
||||
|
||||
bool _debugFeatures;
|
||||
|
||||
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
|
||||
static std::wstring_view _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ void Pane::ResizeContent(const Size& newSize)
|
||||
const auto width = newSize.Width;
|
||||
const auto height = newSize.Height;
|
||||
|
||||
_CreateRowColDefinitions(newSize);
|
||||
_CreateRowColDefinitions();
|
||||
|
||||
if (_splitState == SplitState::Vertical)
|
||||
{
|
||||
@@ -790,23 +790,24 @@ void Pane::_SetupChildCloseHandlers()
|
||||
// which is stored in _desiredSplitPosition
|
||||
// - Does nothing if our split state is currently set to SplitState::None
|
||||
// Arguments:
|
||||
// - rootSize: The dimensions in pixels that this pane (and its children should consume.)
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_CreateRowColDefinitions(const Size& rootSize)
|
||||
void Pane::_CreateRowColDefinitions()
|
||||
{
|
||||
const auto first = _desiredSplitPosition * 100.0f;
|
||||
const auto second = 100.0f - first;
|
||||
if (_splitState == SplitState::Vertical)
|
||||
{
|
||||
_root.ColumnDefinitions().Clear();
|
||||
|
||||
// Create two columns in this grid: one for each pane
|
||||
const auto paneSizes = _CalcChildrenSizes(rootSize.Width);
|
||||
|
||||
auto firstColDef = Controls::ColumnDefinition();
|
||||
firstColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
|
||||
firstColDef.Width(GridLengthHelper::FromValueAndType(first, GridUnitType::Star));
|
||||
|
||||
auto secondColDef = Controls::ColumnDefinition();
|
||||
secondColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
|
||||
secondColDef.Width(GridLengthHelper::FromValueAndType(second, GridUnitType::Star));
|
||||
|
||||
_root.ColumnDefinitions().Append(firstColDef);
|
||||
_root.ColumnDefinitions().Append(secondColDef);
|
||||
@@ -816,35 +817,18 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
|
||||
_root.RowDefinitions().Clear();
|
||||
|
||||
// Create two rows in this grid: one for each pane
|
||||
const auto paneSizes = _CalcChildrenSizes(rootSize.Height);
|
||||
|
||||
auto firstRowDef = Controls::RowDefinition();
|
||||
firstRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
|
||||
firstRowDef.Height(GridLengthHelper::FromValueAndType(first, GridUnitType::Star));
|
||||
|
||||
auto secondRowDef = Controls::RowDefinition();
|
||||
secondRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
|
||||
secondRowDef.Height(GridLengthHelper::FromValueAndType(second, GridUnitType::Star));
|
||||
|
||||
_root.RowDefinitions().Append(firstRowDef);
|
||||
_root.RowDefinitions().Append(secondRowDef);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initializes our UI for a new split in this pane. Sets up row/column
|
||||
// definitions, and initializes the separator grid. Does nothing if our split
|
||||
// state is currently set to SplitState::None
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_CreateSplitContent()
|
||||
{
|
||||
Size actualSize{ gsl::narrow_cast<float>(_root.ActualWidth()),
|
||||
gsl::narrow_cast<float>(_root.ActualHeight()) };
|
||||
|
||||
_CreateRowColDefinitions(actualSize);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the thickness of each side of our borders to match our _borders state.
|
||||
// Arguments:
|
||||
@@ -1077,7 +1061,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
|
||||
_control = { nullptr };
|
||||
_secondChild = std::make_shared<Pane>(profile, control);
|
||||
|
||||
_CreateSplitContent();
|
||||
_CreateRowColDefinitions();
|
||||
|
||||
_root.Children().Append(_firstChild->GetRootElement());
|
||||
_root.Children().Append(_secondChild->GetRootElement());
|
||||
@@ -1522,4 +1506,74 @@ void Pane::_SetupResources()
|
||||
}
|
||||
}
|
||||
|
||||
int Pane::GetLeafPaneCount() const noexcept
|
||||
{
|
||||
return _IsLeaf() ? 1 : (_firstChild->GetLeafPaneCount() + _secondChild->GetLeafPaneCount());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper to determine which direction an "Automatic" split should
|
||||
// happen in for a given pane, but without using the ActualWidth() and
|
||||
// ActualHeight() methods. This is used during the initialization of the
|
||||
// Terminal, when we could be processing many "split-pane" commands _before_
|
||||
// we've ever laid out the Terminal 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 how to do an "auto" split when these Panes are 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.
|
||||
// - 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 the
|
||||
// SplitState that `target` would use for an `Automatic` split given
|
||||
// `availableSpace`
|
||||
std::optional<winrt::TerminalApp::SplitState> Pane::PreCalculateAutoSplit(const std::shared_ptr<Pane> target,
|
||||
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.
|
||||
return availableSpace.Width > availableSpace.Height ? SplitState::Vertical : SplitState::Horizontal;
|
||||
}
|
||||
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) : availableSpace.Width;
|
||||
const float secondWidth = isVerticalSplit ? (availableSpace.Width - firstWidth) : availableSpace.Width;
|
||||
const float firstHeight = !isVerticalSplit ? (availableSpace.Height * _desiredSplitPosition) : availableSpace.Height;
|
||||
const float secondHeight = !isVerticalSplit ? (availableSpace.Height - firstHeight) : availableSpace.Height;
|
||||
|
||||
const auto firstResult = _firstChild->PreCalculateAutoSplit(target, { firstWidth, firstHeight });
|
||||
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateAutoSplit(target, { secondWidth, secondHeight });
|
||||
}
|
||||
|
||||
// We should not possibly be getting here - both the above branches should
|
||||
// return a value.
|
||||
FAIL_FAST();
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
|
||||
@@ -63,10 +63,13 @@ public:
|
||||
const GUID& profile,
|
||||
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;
|
||||
|
||||
void Shutdown();
|
||||
void Close();
|
||||
|
||||
int GetLeafPaneCount() const noexcept;
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
|
||||
@@ -107,8 +110,7 @@ private:
|
||||
const GUID& profile,
|
||||
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize);
|
||||
void _CreateSplitContent();
|
||||
void _CreateRowColDefinitions();
|
||||
void _ApplySplitDefinitions();
|
||||
void _UpdateBorders();
|
||||
|
||||
@@ -133,6 +135,9 @@ private:
|
||||
float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const;
|
||||
|
||||
winrt::TerminalApp::SplitState _convertAutomaticSplitState(const winrt::TerminalApp::SplitState& splitType) const;
|
||||
|
||||
std::optional<winrt::TerminalApp::SplitState> _preCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
|
||||
|
||||
// Function Description:
|
||||
// - Returns true if the given direction can be used with the given split
|
||||
// type.
|
||||
|
||||
@@ -162,40 +162,6 @@ Temporarily using the Windows Terminal default settings.
|
||||
<data name="ReloadJsonParseErrorTitle" xml:space="preserve">
|
||||
<value>Failed to reload settings</value>
|
||||
</data>
|
||||
<data name="AboutTitleText" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="VersionLabelText" xml:space="preserve">
|
||||
<value>Version:</value>
|
||||
</data>
|
||||
<data name="DocumentationLabelText" xml:space="preserve">
|
||||
<value>Documentation
|
||||
</value>
|
||||
</data>
|
||||
<data name="GettingStartedLabelText" xml:space="preserve">
|
||||
<value>Getting Started
|
||||
</value>
|
||||
</data>
|
||||
<data name="ReleaseNotesLabelText" xml:space="preserve">
|
||||
<value>Release Notes
|
||||
</value>
|
||||
</data>
|
||||
<data name="PrivacyPolicyLabelText" xml:space="preserve">
|
||||
<value>Privacy Policy
|
||||
</value>
|
||||
</data>
|
||||
<data name="DocumentationUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-documentation</value>
|
||||
</data>
|
||||
<data name="GettingStartedUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-getting-started</value>
|
||||
</data>
|
||||
<data name="ReleaseNotesUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-release-notes</value>
|
||||
</data>
|
||||
<data name="PrivacyPolicyUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-privacy-policy</value>
|
||||
</data>
|
||||
<data name="FeedbackUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-feedback</value>
|
||||
</data>
|
||||
@@ -208,15 +174,6 @@ Temporarily using the Windows Terminal default settings.
|
||||
<data name="SettingsMenuItem" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="CloseAll" xml:space="preserve">
|
||||
<value>Close all</value>
|
||||
</data>
|
||||
<data name="CloseWindowWarningTitle" xml:space="preserve">
|
||||
<value>Do you want to close all tabs?</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>Found a profile with an invalid "backgroundImage". Defaulting that profile to have no background image. Make sure that when setting a "backgroundImage", the value is a valid file path to an image.</value>
|
||||
</data>
|
||||
@@ -283,4 +240,47 @@ Temporarily using the Windows Terminal default settings.
|
||||
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Minimize</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="AboutDialog.Title" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="AboutDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="AboutDialog_VersionLabel.Text" xml:space="preserve">
|
||||
<value>Version:</value>
|
||||
<comment>This is the heading for a version number label</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_GettingStartedLink.Content" xml:space="preserve">
|
||||
<value>Getting Started</value>
|
||||
<comment>A hyperlink name for a guide on how to get started using Terminal</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_DocumentationLink.Content" xml:space="preserve">
|
||||
<value>Documentation</value>
|
||||
<comment>A hyperlink name for user documentation</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_ReleaseNotesLink.Content" xml:space="preserve">
|
||||
<value>Release Notes</value>
|
||||
<comment>A hyperlink name for the Terminal's release notes</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_PrivacyPolicyLink.Content" xml:space="preserve">
|
||||
<value>Privacy Policy</value>
|
||||
<comment>A hyperlink name for the Terminal's privacy policy</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_DisplayNameUnpackaged" xml:space="preserve">
|
||||
<value>Windows Terminal (Unpackaged)</value>
|
||||
<comment>This display name is used when the application's name cannot be determined</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_VersionUnknown" xml:space="preserve">
|
||||
<value>Unknown</value>
|
||||
<comment>This is displayed when the version of the application cannot be determined</comment>
|
||||
</data>
|
||||
<data name="CloseAllDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="CloseAllDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Close all</value>
|
||||
</data>
|
||||
<data name="CloseAllDialog.Title" xml:space="preserve">
|
||||
<value>Do you want to close all tabs?</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -277,6 +277,12 @@ namespace winrt::TerminalApp::implementation
|
||||
// gains focus, we'll mark it as the new active pane.
|
||||
_AttachEventHandlersToPane(first);
|
||||
_AttachEventHandlersToPane(second);
|
||||
|
||||
// Immediately update our tracker of the focused pane now. If we're
|
||||
// splitting panes during startup (from a commandline), then it's
|
||||
// possible that the focus events won't propagate immediately. Updating
|
||||
// the focus here will give the same effect though.
|
||||
_UpdateActivePane(second);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -387,6 +393,28 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Mark the given pane as the active pane in this tab. All other panes
|
||||
// will be marked as inactive. We'll also update our own UI state to
|
||||
// reflect this newly active pane.
|
||||
// Arguments:
|
||||
// - pane: a Pane to mark as active.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::_UpdateActivePane(std::shared_ptr<Pane> pane)
|
||||
{
|
||||
// Clear the active state of the entire tree, and mark only the pane as active.
|
||||
_rootPane->ClearActive();
|
||||
_activePane = pane;
|
||||
_activePane->SetActive();
|
||||
|
||||
// Update our own title text to match the newly-active pane.
|
||||
SetTabText(GetActiveTitle());
|
||||
|
||||
// Raise our own ActivePaneChanged event.
|
||||
_ActivePaneChangedHandlers();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Add an event handler to this pane's GotFocus event. When that pane gains
|
||||
// focus, we'll mark it as the new active pane. We'll also query the title of
|
||||
@@ -406,19 +434,37 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (tab && sender != tab->_activePane)
|
||||
{
|
||||
// Clear the active state of the entire tree, and mark only the sender as active.
|
||||
tab->_rootPane->ClearActive();
|
||||
tab->_activePane = sender;
|
||||
tab->_activePane->SetActive();
|
||||
|
||||
// Update our own title text to match the newly-active pane.
|
||||
tab->SetTabText(tab->GetActiveTitle());
|
||||
|
||||
// Raise our own ActivePaneChanged event.
|
||||
tab->_ActivePaneChangedHandlers();
|
||||
tab->_UpdateActivePane(sender);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the total number of leaf panes in this tab. This will be the number
|
||||
// of actual controls hosted by this tab.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The total number of leaf panes hosted by this tab.
|
||||
int Tab::_GetLeafPaneCount() const noexcept
|
||||
{
|
||||
return _rootPane->GetLeafPaneCount();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper to determine which direction an "Automatic" split should
|
||||
// happen in for the active pane of this tab, but without using the ActualWidth() and
|
||||
// ActualHeight() methods.
|
||||
// - See Pane::PreCalculateAutoSplit
|
||||
// Arguments:
|
||||
// - availableSpace: The theoretical space that's available for this Tab's content
|
||||
// Return Value:
|
||||
// - The SplitState that we should use for an `Automatic` split given
|
||||
// `availableSpace`
|
||||
SplitState Tab::PreCalculateAutoSplit(winrt::Windows::Foundation::Size availableSpace) const
|
||||
{
|
||||
return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical);
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
#include "Pane.h"
|
||||
#include "Tab.g.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
class TabTests;
|
||||
};
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct Tab : public TabT<Tab>
|
||||
@@ -32,6 +38,7 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
|
||||
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const;
|
||||
|
||||
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
|
||||
void ResizePane(const winrt::TerminalApp::Direction& direction);
|
||||
@@ -64,5 +71,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
|
||||
|
||||
int _GetLeafPaneCount() const noexcept;
|
||||
void _UpdateActivePane(std::shared_ptr<Pane> pane);
|
||||
|
||||
friend class ::TerminalAppLocalTests::TabTests;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "AzureCloudShellGenerator.h" // For AzureConnectionType
|
||||
#include "TelnetGenerator.h" // For TelnetConnectionType
|
||||
#include "TabRowControl.h"
|
||||
#include "DebugTapConnection.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@@ -63,11 +64,20 @@ namespace winrt::TerminalApp::implementation
|
||||
_tabView = _tabRow.TabView();
|
||||
_rearranging = false;
|
||||
|
||||
// GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs.
|
||||
// Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process,
|
||||
// but that process is running at a different IL than us.
|
||||
// For now, we're disabling elevated drag.
|
||||
const auto isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated();
|
||||
// GH#2455 - Make sure to try/catch calls to Application::Current,
|
||||
// because that _won't_ be an instance of TerminalApp::App in the
|
||||
// LocalTests
|
||||
auto isElevated = false;
|
||||
try
|
||||
{
|
||||
// GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs.
|
||||
// Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process,
|
||||
// but that process is running at a different IL than us.
|
||||
// For now, we're disabling elevated drag.
|
||||
isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
_tabView.CanReorderTabs(!isElevated);
|
||||
_tabView.CanDragTabs(!isElevated);
|
||||
|
||||
@@ -137,161 +147,115 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
_tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged });
|
||||
|
||||
// Actually start the terminal.
|
||||
if (_appArgs.GetStartupActions().empty())
|
||||
{
|
||||
_OpenNewTab(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
_appArgs.ValidateStartupCommands();
|
||||
// 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.
|
||||
//
|
||||
// _OnFirstLayout will remove this handler so it doesn't get called more than once.
|
||||
_layoutUpdatedRevoker = _tabContent.LayoutUpdated(winrt::auto_revoke, { this, &TerminalPage::_OnFirstLayout });
|
||||
}
|
||||
|
||||
// This will kick off a chain of events to perform each startup
|
||||
// action. As each startup action is completed, the next will be
|
||||
// fired.
|
||||
_ProcessNextStartupAction();
|
||||
// Method Description:
|
||||
// - This method is called once on startup, on the first LayoutUpdated event.
|
||||
// We'll use this event to know that we have an ActualWidth and
|
||||
// ActualHeight, so we can now attempt to process our list of startup
|
||||
// actions.
|
||||
// - We'll remove this event handler when the event is first handled.
|
||||
// - If there are no startup actions, we'll open a single tab with the
|
||||
// default profile.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_OnFirstLayout(const IInspectable& /*sender*/, const IInspectable& /*eventArgs*/)
|
||||
{
|
||||
// Only let this succeed once.
|
||||
_layoutUpdatedRevoker.revoke();
|
||||
|
||||
// This event fires every time the layout changes, but it is always the
|
||||
// last one to fire in any layout change chain. That gives us great
|
||||
// flexibility in finding the right point at which to initialize our
|
||||
// renderer (and our terminal). Any earlier than the last layout update
|
||||
// and we may not know the terminal's starting size.
|
||||
if (_startupState == StartupState::NotInitialized)
|
||||
{
|
||||
_startupState = StartupState::InStartup;
|
||||
_appArgs.ValidateStartupCommands();
|
||||
if (_appArgs.GetStartupActions().empty())
|
||||
{
|
||||
_OpenNewTab(nullptr);
|
||||
_startupState = StartupState::Initialized;
|
||||
_InitializedHandlers(*this, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ProcessStartupActions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Process the next startup action in our list of startup actions. When
|
||||
// that action is complete, fire the next (if there are any more).
|
||||
// - Process all the startup actions in our list of startup actions. We'll
|
||||
// do this all at once here.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
fire_and_forget TerminalPage::_ProcessNextStartupAction()
|
||||
winrt::fire_and_forget TerminalPage::_ProcessStartupActions()
|
||||
{
|
||||
// If there are no actions left, do nothing.
|
||||
if (_appArgs.GetStartupActions().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the next action to be processed
|
||||
auto nextAction = _appArgs.GetStartupActions().front();
|
||||
_appArgs.GetStartupActions().pop_front();
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
// Handle it on the UI thread.
|
||||
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Low);
|
||||
// Handle it on a subsequent pass of the UI thread.
|
||||
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->_actionDispatch->DoAction(nextAction);
|
||||
|
||||
// Kick off the next action to be handled (if necessary)
|
||||
page->_ProcessNextStartupAction();
|
||||
for (const auto& action : _appArgs.GetStartupActions())
|
||||
{
|
||||
_actionDispatch->DoAction(action);
|
||||
}
|
||||
_startupState = StartupState::Initialized;
|
||||
_InitializedHandlers(*this, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Show a ContentDialog with a single "Ok" button to dismiss. Looks up the
|
||||
// the title and text from our Resources using the provided keys.
|
||||
// - Only one dialog can be visible at a time. If another dialog is visible
|
||||
// when this is called, nothing happens. See _ShowDialog for details
|
||||
// Arguments:
|
||||
// - titleKey: The key to use to lookup the title text from our resources.
|
||||
// - contentKey: The key to use to lookup the content text from our resources.
|
||||
void TerminalPage::ShowOkDialog(const winrt::hstring& titleKey,
|
||||
const winrt::hstring& contentKey)
|
||||
{
|
||||
auto title = GetLibraryResourceString(titleKey);
|
||||
auto message = GetLibraryResourceString(contentKey);
|
||||
auto buttonText = RS_(L"Ok");
|
||||
|
||||
WUX::Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(winrt::box_value(message));
|
||||
dialog.CloseButtonText(buttonText);
|
||||
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Close);
|
||||
|
||||
_showDialogHandlers(*this, dialog);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Show a dialog with "About" information. Displays the app's Display
|
||||
// Name, version, getting started link, documentation link, release
|
||||
// Notes link, and privacy policy link.
|
||||
void TerminalPage::_ShowAboutDialog()
|
||||
{
|
||||
const auto title = RS_(L"AboutTitleText");
|
||||
const auto versionLabel = RS_(L"VersionLabelText");
|
||||
const auto gettingStartedLabel = RS_(L"GettingStartedLabelText");
|
||||
const auto documentationLabel = RS_(L"DocumentationLabelText");
|
||||
const auto releaseNotesLabel = RS_(L"ReleaseNotesLabelText");
|
||||
const auto privacyPolicyLabel = RS_(L"PrivacyPolicyLabelText");
|
||||
const auto gettingStartedUriValue = RS_(L"GettingStartedUriValue");
|
||||
const auto documentationUriValue = RS_(L"DocumentationUriValue");
|
||||
const auto releaseNotesUriValue = RS_(L"ReleaseNotesUriValue");
|
||||
const auto privacyPolicyUriValue = RS_(L"PrivacyPolicyUriValue");
|
||||
const auto package = winrt::Windows::ApplicationModel::Package::Current();
|
||||
const auto packageName = package.DisplayName();
|
||||
const auto version = package.Id().Version();
|
||||
winrt::Windows::UI::Xaml::Documents::Run about;
|
||||
winrt::Windows::UI::Xaml::Documents::Run gettingStarted;
|
||||
winrt::Windows::UI::Xaml::Documents::Run documentation;
|
||||
winrt::Windows::UI::Xaml::Documents::Run releaseNotes;
|
||||
winrt::Windows::UI::Xaml::Documents::Run privacyPolicy;
|
||||
winrt::Windows::UI::Xaml::Documents::Hyperlink gettingStartedLink;
|
||||
winrt::Windows::UI::Xaml::Documents::Hyperlink documentationLink;
|
||||
winrt::Windows::UI::Xaml::Documents::Hyperlink releaseNotesLink;
|
||||
winrt::Windows::UI::Xaml::Documents::Hyperlink privacyPolicyLink;
|
||||
std::wstringstream aboutTextStream;
|
||||
_showDialogHandlers(*this, FindName(L"AboutDialog").try_as<WUX::Controls::ContentDialog>());
|
||||
}
|
||||
|
||||
gettingStarted.Text(gettingStartedLabel);
|
||||
documentation.Text(documentationLabel);
|
||||
releaseNotes.Text(releaseNotesLabel);
|
||||
privacyPolicy.Text(privacyPolicyLabel);
|
||||
winrt::hstring TerminalPage::ApplicationDisplayName()
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
return package.DisplayName();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
winrt::Windows::Foundation::Uri gettingStartedUri{ gettingStartedUriValue };
|
||||
winrt::Windows::Foundation::Uri documentationUri{ documentationUriValue };
|
||||
winrt::Windows::Foundation::Uri releaseNotesUri{ releaseNotesUriValue };
|
||||
winrt::Windows::Foundation::Uri privacyPolicyUri{ privacyPolicyUriValue };
|
||||
return RS_(L"AboutDialog_DisplayNameUnpackaged");
|
||||
}
|
||||
|
||||
gettingStartedLink.NavigateUri(gettingStartedUri);
|
||||
documentationLink.NavigateUri(documentationUri);
|
||||
releaseNotesLink.NavigateUri(releaseNotesUri);
|
||||
privacyPolicyLink.NavigateUri(privacyPolicyUri);
|
||||
winrt::hstring TerminalPage::ApplicationVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
const auto version{ package.Id().Version() };
|
||||
winrt::hstring formatted{ wil::str_printf<std::wstring>(L"%u.%u.%u.%u", version.Major, version.Minor, version.Build, version.Revision) };
|
||||
return formatted;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
gettingStartedLink.Inlines().Append(gettingStarted);
|
||||
documentationLink.Inlines().Append(documentation);
|
||||
releaseNotesLink.Inlines().Append(releaseNotes);
|
||||
privacyPolicyLink.Inlines().Append(privacyPolicy);
|
||||
|
||||
// Format our about text. It will look like the following:
|
||||
// <Display Name>
|
||||
// Version: <Major>.<Minor>.<Build>.<Revision>
|
||||
// Getting Started
|
||||
// Documentation
|
||||
// Release Notes
|
||||
// Privacy Policy
|
||||
|
||||
aboutTextStream << packageName.c_str() << L"\n";
|
||||
|
||||
aboutTextStream << versionLabel.c_str() << L" ";
|
||||
aboutTextStream << version.Major << L"." << version.Minor << L"." << version.Build << L"." << version.Revision << L"\n";
|
||||
|
||||
winrt::hstring aboutText{ aboutTextStream.str() };
|
||||
about.Text(aboutText);
|
||||
|
||||
const auto buttonText = RS_(L"Ok");
|
||||
|
||||
WUX::Controls::TextBlock aboutTextBlock;
|
||||
aboutTextBlock.Inlines().Append(about);
|
||||
aboutTextBlock.Inlines().Append(gettingStartedLink);
|
||||
aboutTextBlock.Inlines().Append(documentationLink);
|
||||
aboutTextBlock.Inlines().Append(releaseNotesLink);
|
||||
aboutTextBlock.Inlines().Append(privacyPolicyLink);
|
||||
aboutTextBlock.IsTextSelectionEnabled(true);
|
||||
|
||||
WUX::Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(aboutTextBlock);
|
||||
dialog.CloseButtonText(buttonText);
|
||||
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Close);
|
||||
|
||||
_showDialogHandlers(*this, dialog);
|
||||
return RS_(L"AboutDialog_VersionUnknown");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -303,19 +267,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// when this is called, nothing happens. See _ShowDialog for details
|
||||
void TerminalPage::_ShowCloseWarningDialog()
|
||||
{
|
||||
auto title = RS_(L"CloseWindowWarningTitle");
|
||||
auto primaryButtonText = RS_(L"CloseAll");
|
||||
auto closeButtonText = RS_(L"Cancel");
|
||||
|
||||
WUX::Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
|
||||
dialog.CloseButtonText(closeButtonText);
|
||||
dialog.PrimaryButtonText(primaryButtonText);
|
||||
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Primary);
|
||||
auto token = dialog.PrimaryButtonClick({ this, &TerminalPage::_CloseWarningPrimaryButtonOnClick });
|
||||
|
||||
_showDialogHandlers(*this, dialog);
|
||||
_showDialogHandlers(*this, FindName(L"CloseAllDialog").try_as<WUX::Controls::ContentDialog>());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -406,7 +358,15 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// add static items
|
||||
{
|
||||
const auto isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
|
||||
// GH#2455 - Make sure to try/catch calls to Application::Current,
|
||||
// because that _won't_ be an instance of TerminalApp::App in the
|
||||
// LocalTests
|
||||
auto isUwp = false;
|
||||
try
|
||||
{
|
||||
isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
if (!isUwp)
|
||||
{
|
||||
@@ -476,6 +436,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// control which profile is created and with possible other
|
||||
// configurations. See CascadiaSettings::BuildSettings for more details.
|
||||
void TerminalPage::_OpenNewTab(const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs)
|
||||
try
|
||||
{
|
||||
const auto [profileGuid, settings] = _settings->BuildSettings(newTerminalArgs);
|
||||
|
||||
@@ -499,6 +460,7 @@ namespace winrt::TerminalApp::implementation
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
winrt::fire_and_forget TerminalPage::_RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page)
|
||||
{
|
||||
@@ -514,11 +476,25 @@ namespace winrt::TerminalApp::implementation
|
||||
// - settings: the TerminalSettings object to use to create the TerminalControl with.
|
||||
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings)
|
||||
{
|
||||
const bool isFirstTab = _tabs.Size() == 0;
|
||||
// Initialize the new tab
|
||||
|
||||
// Create a connection based on the values in our settings object.
|
||||
const auto connection = _CreateConnectionFromSettings(profileGuid, settings);
|
||||
auto connection = _CreateConnectionFromSettings(profileGuid, settings);
|
||||
|
||||
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
|
||||
if (_settings->GlobalSettings().DebugFeaturesEnabled())
|
||||
{
|
||||
const CoreWindow window = CoreWindow::GetForCurrentThread();
|
||||
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
|
||||
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
|
||||
const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
|
||||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
||||
if (bothAltsPressed)
|
||||
{
|
||||
std::tie(connection, debugConnection) = OpenDebugTapConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
TermControl term{ settings, connection };
|
||||
|
||||
// Add the new tab to the list of our tabs.
|
||||
@@ -568,20 +544,17 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
// If this is the first tab, we don't need to kick off the event to get
|
||||
// the tab's content added to the root of the page. just do it
|
||||
// immediately.
|
||||
if (isFirstTab)
|
||||
if (debugConnection) // this will only be set if global debugging is on and tap is active
|
||||
{
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(newTabImpl->GetRootElement());
|
||||
}
|
||||
else
|
||||
{
|
||||
// This kicks off TabView::SelectionChanged, in response to which
|
||||
// we'll attach the terminal's Xaml control to the Xaml root.
|
||||
_tabView.SelectedItem(tabViewItem);
|
||||
TermControl newControl{ settings, debugConnection };
|
||||
_RegisterTerminalEvents(newControl, *newTabImpl);
|
||||
// Split (auto) with the debug tap.
|
||||
newTabImpl->SplitPane(SplitState::Automatic, profileGuid, newControl);
|
||||
}
|
||||
|
||||
// This kicks off TabView::SelectionChanged, in response to which
|
||||
// we'll attach the terminal's Xaml control to the Xaml root.
|
||||
_tabView.SelectedItem(tabViewItem);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -810,13 +783,30 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
auto focusedTab = _GetStrongTabImpl(*index);
|
||||
const auto& profileGuid = focusedTab->GetFocusedProfile();
|
||||
if (profileGuid.has_value())
|
||||
try
|
||||
{
|
||||
const auto settings = _settings->BuildSettings(profileGuid.value());
|
||||
_CreateNewTabFromSettings(profileGuid.value(), settings);
|
||||
auto focusedTab = _GetStrongTabImpl(*index);
|
||||
// TODO: GH#5047 - In the future, we should get the Profile of
|
||||
// the focused pane, and use that to build a new instance of the
|
||||
// settings so we can duplicate this tab/pane.
|
||||
//
|
||||
// Currently, if the profile doesn't exist anymore in our
|
||||
// settings, we'll silently do nothing.
|
||||
//
|
||||
// In the future, it will be preferable to just duplicate the
|
||||
// current control's settings, but we can't do that currently,
|
||||
// because we won't be able to create a new instance of the
|
||||
// connection without keeping an instance of the original Profile
|
||||
// object around.
|
||||
|
||||
const auto& profileGuid = focusedTab->GetFocusedProfile();
|
||||
if (profileGuid.has_value())
|
||||
{
|
||||
const auto settings = _settings->BuildSettings(profileGuid.value());
|
||||
_CreateNewTabFromSettings(profileGuid.value(), settings);
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -901,20 +891,36 @@ namespace winrt::TerminalApp::implementation
|
||||
// Wraparound math. By adding tabCount and then calculating modulo tabCount,
|
||||
// we clamp the values to the range [0, tabCount) while still supporting moving
|
||||
// leftward from 0 to tabCount - 1.
|
||||
_SetFocusedTabIndex(((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount));
|
||||
const auto newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
|
||||
_SelectTab(newTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets focus to the desired tab. Returns false if the provided tabIndex
|
||||
// is greater than the number of tabs we have.
|
||||
// - During startup, we'll immediately set the selected tab as focused.
|
||||
// - After startup, we'll dispatch an async method to set the the selected
|
||||
// item of the TabView, which will then also trigger a
|
||||
// TabView::SelectionChanged, handled in
|
||||
// TerminalPage::_OnTabSelectionChanged
|
||||
// Return Value:
|
||||
// true iff we were able to select that tab index, false otherwise
|
||||
bool TerminalPage::_SelectTab(const uint32_t tabIndex)
|
||||
{
|
||||
if (tabIndex >= 0 && tabIndex < _tabs.Size())
|
||||
{
|
||||
_SetFocusedTabIndex(tabIndex);
|
||||
if (_startupState == StartupState::InStartup)
|
||||
{
|
||||
auto tab{ _GetStrongTabImpl(tabIndex) };
|
||||
_tabView.SelectedItem(tab->GetTabViewItem());
|
||||
_UpdatedSelectedTab(tabIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_SetFocusedTabIndex(tabIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -967,6 +973,16 @@ namespace winrt::TerminalApp::implementation
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 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
|
||||
// then also trigger a TabView::SelectionChanged event, which we'll handle
|
||||
// in TerminalPage::_OnTabSelectionChanged, where we'll mark the new tab
|
||||
// as focused.
|
||||
// Arguments:
|
||||
// - tabIndex: the index in the list of tabs to focus.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget TerminalPage::_SetFocusedTabIndex(const uint32_t tabIndex)
|
||||
{
|
||||
// GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex)
|
||||
@@ -1075,42 +1091,65 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
auto focusedTab = _GetStrongTabImpl(*indexOpt);
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::TerminalSettings controlSettings;
|
||||
GUID realGuid;
|
||||
bool profileFound = false;
|
||||
|
||||
if (splitMode == TerminalApp::SplitType::Duplicate)
|
||||
try
|
||||
{
|
||||
std::optional<GUID> current_guid = focusedTab->GetFocusedProfile();
|
||||
if (current_guid)
|
||||
auto focusedTab = _GetStrongTabImpl(*indexOpt);
|
||||
winrt::Microsoft::Terminal::Settings::TerminalSettings controlSettings;
|
||||
GUID realGuid;
|
||||
bool profileFound = false;
|
||||
|
||||
if (splitMode == TerminalApp::SplitType::Duplicate)
|
||||
{
|
||||
profileFound = true;
|
||||
controlSettings = _settings->BuildSettings(current_guid.value());
|
||||
realGuid = current_guid.value();
|
||||
std::optional<GUID> current_guid = focusedTab->GetFocusedProfile();
|
||||
if (current_guid)
|
||||
{
|
||||
profileFound = true;
|
||||
controlSettings = _settings->BuildSettings(current_guid.value());
|
||||
realGuid = current_guid.value();
|
||||
}
|
||||
// TODO: GH#5047 - In the future, we should get the Profile of
|
||||
// the focused pane, and use that to build a new instance of the
|
||||
// settings so we can duplicate this tab/pane.
|
||||
//
|
||||
// Currently, if the profile doesn't exist anymore in our
|
||||
// settings, we'll silently do nothing.
|
||||
//
|
||||
// In the future, it will be preferable to just duplicate the
|
||||
// current control's settings, but we can't do that currently,
|
||||
// because we won't be able to create a new instance of the
|
||||
// connection without keeping an instance of the original Profile
|
||||
// object around.
|
||||
}
|
||||
if (!profileFound)
|
||||
{
|
||||
std::tie(realGuid, controlSettings) = _settings->BuildSettings(newTerminalArgs);
|
||||
}
|
||||
|
||||
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
|
||||
|
||||
const auto canSplit = focusedTab->CanSplitPane(splitType);
|
||||
|
||||
if (!canSplit && _startupState == StartupState::Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto realSplitType = splitType;
|
||||
if (realSplitType == SplitState::Automatic && _startupState < StartupState::Initialized)
|
||||
{
|
||||
float contentWidth = gsl::narrow_cast<float>(_tabContent.ActualWidth());
|
||||
float contentHeight = gsl::narrow_cast<float>(_tabContent.ActualHeight());
|
||||
realSplitType = focusedTab->PreCalculateAutoSplit({ contentWidth, contentHeight });
|
||||
}
|
||||
|
||||
TermControl newControl{ controlSettings, controlConnection };
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl, *focusedTab);
|
||||
|
||||
focusedTab->SplitPane(realSplitType, realGuid, newControl);
|
||||
}
|
||||
if (!profileFound)
|
||||
{
|
||||
std::tie(realGuid, controlSettings) = _settings->BuildSettings(newTerminalArgs);
|
||||
}
|
||||
|
||||
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
|
||||
|
||||
const auto canSplit = focusedTab->CanSplitPane(splitType);
|
||||
|
||||
if (!canSplit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TermControl newControl{ controlSettings, controlConnection };
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl, *focusedTab);
|
||||
|
||||
focusedTab->SplitPane(splitType, realGuid, newControl);
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1426,6 +1465,31 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_UpdatedSelectedTab(const int32_t index)
|
||||
{
|
||||
// Unfocus all the tabs.
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
tabImpl->SetFocused(false);
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto tab{ _GetStrongTabImpl(index) };
|
||||
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(tab->GetRootElement());
|
||||
|
||||
tab->SetFocused(true);
|
||||
_titleChangeHandlers(*this, Title());
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Responds to the TabView control's Selection Changed event (to move a
|
||||
// new terminal control into focus) when not in in the middle of a tab rearrangement.
|
||||
@@ -1438,28 +1502,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
auto tabView = sender.as<MUX::Controls::TabView>();
|
||||
auto selectedIndex = tabView.SelectedIndex();
|
||||
|
||||
// Unfocus all the tabs.
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
tabImpl->SetFocused(false);
|
||||
}
|
||||
|
||||
if (selectedIndex >= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto tab{ _GetStrongTabImpl(selectedIndex) };
|
||||
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(tab->GetRootElement());
|
||||
|
||||
tab->SetFocused(true);
|
||||
_titleChangeHandlers(*this, Title());
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
_UpdatedSelectedTab(selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1525,16 +1568,28 @@ namespace winrt::TerminalApp::implementation
|
||||
for (auto& profile : profiles)
|
||||
{
|
||||
const GUID profileGuid = profile.GetGuid();
|
||||
const auto settings = _settings->BuildSettings(profileGuid);
|
||||
|
||||
for (auto tab : _tabs)
|
||||
try
|
||||
{
|
||||
// Attempt to reload the settings of any panes with this profile
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
tabImpl->UpdateSettings(settings, profileGuid);
|
||||
// BuildSettings can throw an exception if the profileGuid does
|
||||
// not belong to an actual profile in the list of profiles.
|
||||
const auto settings = _settings->BuildSettings(profileGuid);
|
||||
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
// Attempt to reload the settings of any panes with this profile
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
tabImpl->UpdateSettings(settings, profileGuid);
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// GH#2455: If there are any panes with controls that had been
|
||||
// initialized with a Profile that no longer exists in our list of
|
||||
// profiles, we'll leave it unmodified. The profile doesn't exist
|
||||
// anymore, so we can't possibly update its settings.
|
||||
|
||||
// Update the icon of the tab for the currently focused profile in that tab.
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,13 @@ namespace TerminalAppLocalTests
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
enum StartupState : int
|
||||
{
|
||||
NotInitialized = 0,
|
||||
InStartup = 1,
|
||||
Initialized = 2
|
||||
};
|
||||
|
||||
struct TerminalPage : TerminalPageT<TerminalPage>
|
||||
{
|
||||
public:
|
||||
@@ -31,12 +38,13 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
hstring Title();
|
||||
|
||||
void ShowOkDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey);
|
||||
|
||||
void TitlebarClicked();
|
||||
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
|
||||
winrt::hstring ApplicationDisplayName();
|
||||
winrt::hstring ApplicationVersion();
|
||||
|
||||
void CloseWindow();
|
||||
|
||||
int32_t SetStartupCommandline(winrt::array_view<const hstring> args);
|
||||
@@ -48,8 +56,11 @@ namespace winrt::TerminalApp::implementation
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarContent, _setTitleBarContentHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement);
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ShowDialog, _showDialogHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Controls::ContentDialog);
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ToggleFullscreen, _toggleFullscreenHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs);
|
||||
TYPED_EVENT(Initialized, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs);
|
||||
|
||||
private:
|
||||
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
|
||||
|
||||
// If you add controls here, but forget to null them either here or in
|
||||
// the ctor, you're going to have a bad time. It'll mysteriously fail to
|
||||
// activate the app.
|
||||
@@ -75,9 +86,12 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::com_ptr<ShortcutActionDispatch> _actionDispatch{ winrt::make_self<ShortcutActionDispatch>() };
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::Grid::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
StartupState _startupState{ StartupState::NotInitialized };
|
||||
|
||||
::TerminalApp::AppCommandlineArgs _appArgs;
|
||||
int _ParseArgs(winrt::array_view<const hstring>& args);
|
||||
fire_and_forget _ProcessNextStartupAction();
|
||||
winrt::fire_and_forget _ProcessStartupActions();
|
||||
|
||||
void _ShowAboutDialog();
|
||||
void _ShowCloseWarningDialog();
|
||||
@@ -141,6 +155,8 @@ namespace winrt::TerminalApp::implementation
|
||||
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);
|
||||
void _OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e);
|
||||
void _OnTabCloseRequested(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabCloseRequestedEventArgs& eventArgs);
|
||||
void _OnFirstLayout(const IInspectable& sender, const IInspectable& eventArgs);
|
||||
void _UpdatedSelectedTab(const int32_t index);
|
||||
|
||||
void _Find();
|
||||
|
||||
|
||||
@@ -13,10 +13,15 @@ namespace TerminalApp
|
||||
Int32 SetStartupCommandline(String[] commands);
|
||||
String EarlyExitMessage { get; };
|
||||
|
||||
// XAML bound properties
|
||||
String ApplicationDisplayName { get; };
|
||||
String ApplicationVersion { get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.Controls.ContentDialog> ShowDialog;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ToggleFullscreenEventArgs> ToggleFullscreen;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.RoutedEventArgs> Initialized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,39 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<local:TabRowControl x:Name="TabRow" Grid.Row="0" />
|
||||
|
||||
<Grid x:Name="TabContent" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
||||
|
||||
<ContentDialog
|
||||
x:Load="False"
|
||||
x:Name="AboutDialog"
|
||||
x:Uid="AboutDialog"
|
||||
DefaultButton="Close">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock IsTextSelectionEnabled="True">
|
||||
<Run Text="{x:Bind ApplicationDisplayName}" /> <LineBreak />
|
||||
<Run x:Uid="AboutDialog_VersionLabel" />
|
||||
<Run Text="{x:Bind ApplicationVersion}" />
|
||||
</TextBlock>
|
||||
<HyperlinkButton
|
||||
x:Uid="AboutDialog_GettingStartedLink"
|
||||
NavigateUri="https://aka.ms/terminal-getting-started" />
|
||||
<HyperlinkButton
|
||||
x:Uid="AboutDialog_DocumentationLink"
|
||||
NavigateUri="https://aka.ms/terminal-documentation" />
|
||||
<HyperlinkButton
|
||||
x:Uid="AboutDialog_ReleaseNotesLink"
|
||||
NavigateUri="https://aka.ms/terminal-release-notes" />
|
||||
<HyperlinkButton
|
||||
x:Uid="AboutDialog_PrivacyPolicyLink"
|
||||
NavigateUri="https://aka.ms/terminal-privacy-policy" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
x:Load="False"
|
||||
x:Name="CloseAllDialog"
|
||||
x:Uid="CloseAllDialog"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="_CloseWarningPrimaryButtonOnClick">
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
<ClInclude Include="../ActionAndArgs.h">
|
||||
<DependentUpon>../ActionArgs.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../DebugTapConnection.h" />
|
||||
<ClInclude Include="../AppKeyBindings.h">
|
||||
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -153,6 +154,7 @@
|
||||
<ClCompile Include="../WslDistroGenerator.cpp" />
|
||||
<ClCompile Include="../AzureCloudShellGenerator.cpp" />
|
||||
<ClCompile Include="../Pane.LayoutSizeNode.cpp" />
|
||||
<ClCompile Include="../DebugTapConnection.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
||||
@@ -172,8 +172,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
TextBlock().FontSize(fontSizePx);
|
||||
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
|
||||
|
||||
const auto widthToTerminalEnd = Canvas().ActualWidth() - ::base::ClampedNumeric<double>(clientCursorPos.X);
|
||||
TextBlock().MaxWidth(widthToTerminalEnd);
|
||||
const auto canvasActualWidth = Canvas().ActualWidth();
|
||||
const auto widthToTerminalEnd = canvasActualWidth - ::base::ClampedNumeric<double>(clientCursorPos.X);
|
||||
// Make sure that we're setting the MaxWidth to a positive number - a
|
||||
// negative number here will crash us in mysterious ways with a useless
|
||||
// stack trace
|
||||
const auto newMaxWidth = std::max<double>(0.0, widthToTerminalEnd);
|
||||
TextBlock().MaxWidth(newMaxWidth);
|
||||
|
||||
// Set the text block bounds
|
||||
const auto yOffset = ::base::ClampedNumeric<float>(TextBlock().ActualHeight()) - fontHeight;
|
||||
|
||||
@@ -75,6 +75,31 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_EnsureStaticInitialization();
|
||||
InitializeComponent();
|
||||
|
||||
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
|
||||
|
||||
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
|
||||
_terminal->SetTitleChangedCallback(pfnTitleChanged);
|
||||
|
||||
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
|
||||
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
|
||||
|
||||
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
|
||||
|
||||
// This event is explicitly revoked in the destructor: does not need weak_ref
|
||||
auto onReceiveOutputFn = [this](const hstring str) {
|
||||
_terminal->Write(str);
|
||||
};
|
||||
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
|
||||
|
||||
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
|
||||
_terminal->SetWriteInputCallback(inputFn);
|
||||
|
||||
// Subscribe to the connection's disconnected event and call our connection closed handlers.
|
||||
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
|
||||
_ConnectionStateChangedHandlers(*this, nullptr);
|
||||
});
|
||||
|
||||
// Initialize the terminal only once the swapchainpanel is loaded - that
|
||||
// way, we'll be able to query the real pixel size it got on layout
|
||||
_layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
|
||||
@@ -90,10 +115,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to the connection's disconnected event and call our connection closed handlers.
|
||||
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
|
||||
_ConnectionStateChangedHandlers(*this, nullptr);
|
||||
});
|
||||
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
|
||||
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
|
||||
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
|
||||
|
||||
_ApplyUISettings();
|
||||
}
|
||||
@@ -126,7 +150,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive)
|
||||
{
|
||||
if (text.size() == 0)
|
||||
if (text.size() == 0 || _closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -183,6 +207,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// If 'weakThis' is locked, then we can safely work with 'this'
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update our control settings
|
||||
_ApplyUISettings();
|
||||
|
||||
@@ -412,7 +441,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer()
|
||||
try
|
||||
{
|
||||
if (GetUiaData())
|
||||
if (_initializedTerminal && !_closing) // only set up the automation peer if we're ready to go live
|
||||
{
|
||||
// create a custom automation peer with this code pattern:
|
||||
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
|
||||
@@ -450,11 +479,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return _connection.State();
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TermControl::SwapChainChanged()
|
||||
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged()
|
||||
{
|
||||
if (!_initializedTerminal)
|
||||
{
|
||||
return;
|
||||
{ // lock scope
|
||||
auto terminalLock = _terminal->LockForReading();
|
||||
if (!_initializedTerminal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto chain = _renderEngine->GetSwapChain();
|
||||
@@ -462,186 +494,153 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
// If 'weakThis' is locked, then we can safely work with 'this'
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
if (_terminal)
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
|
||||
nativePanel->SetSwapChain(chain.Get());
|
||||
}
|
||||
auto terminalLock = _terminal->LockForWriting();
|
||||
|
||||
_AttachDxgiSwapChainToXaml(chain.Get());
|
||||
}
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TermControl::_SwapChainRoutine()
|
||||
void TermControl::_AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain)
|
||||
{
|
||||
auto chain = _renderEngine->GetSwapChain();
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
if (_terminal)
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
|
||||
nativePanel->SetSwapChain(chain.Get());
|
||||
}
|
||||
}
|
||||
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
|
||||
nativePanel->SetSwapChain(swapChain);
|
||||
}
|
||||
|
||||
bool TermControl::_InitializeTerminal()
|
||||
{
|
||||
if (_initializedTerminal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
{ // scope for terminalLock
|
||||
auto terminalLock = _terminal->LockForWriting();
|
||||
|
||||
const auto windowWidth = SwapChainPanel().ActualWidth(); // Width() and Height() are NaN?
|
||||
const auto windowHeight = SwapChainPanel().ActualHeight();
|
||||
if (_initializedTerminal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (windowWidth == 0 || windowHeight == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto windowWidth = SwapChainPanel().ActualWidth(); // Width() and Height() are NaN?
|
||||
const auto windowHeight = SwapChainPanel().ActualHeight();
|
||||
|
||||
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
|
||||
if (windowWidth == 0 || windowHeight == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// First create the render thread.
|
||||
// Then stash a local pointer to the render thread so we can initialize it and enable it
|
||||
// to paint itself *after* we hand off its ownership to the renderer.
|
||||
// We split up construction and initialization of the render thread object this way
|
||||
// because the renderer and render thread have circular references to each other.
|
||||
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
|
||||
auto* const localPointerToThread = renderThread.get();
|
||||
// First create the render thread.
|
||||
// Then stash a local pointer to the render thread so we can initialize it and enable it
|
||||
// to paint itself *after* we hand off its ownership to the renderer.
|
||||
// We split up construction and initialization of the render thread object this way
|
||||
// because the renderer and render thread have circular references to each other.
|
||||
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
|
||||
auto* const localPointerToThread = renderThread.get();
|
||||
|
||||
// Now create the renderer and initialize the render thread.
|
||||
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
|
||||
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
|
||||
// Now create the renderer and initialize the render thread.
|
||||
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
|
||||
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
|
||||
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
|
||||
|
||||
// Set up the DX Engine
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
// Set up the DX Engine
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
|
||||
// Initialize our font with the renderer
|
||||
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
|
||||
// and react accordingly.
|
||||
_UpdateFont(true);
|
||||
// Initialize our font with the renderer
|
||||
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
|
||||
// and react accordingly.
|
||||
_UpdateFont(true);
|
||||
|
||||
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
|
||||
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
|
||||
|
||||
// Fist set up the dx engine with the window size in pixels.
|
||||
// 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() }));
|
||||
// Fist set up the dx engine with the window size in pixels.
|
||||
// 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() }));
|
||||
|
||||
// Update DxEngine's SelectionBackground
|
||||
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
|
||||
// Update DxEngine's SelectionBackground
|
||||
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
|
||||
|
||||
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
|
||||
const auto width = vp.Width();
|
||||
const auto height = vp.Height();
|
||||
_connection.Resize(height, width);
|
||||
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
|
||||
const auto width = vp.Width();
|
||||
const auto height = vp.Height();
|
||||
_connection.Resize(height, width);
|
||||
|
||||
// Override the default width and height to match the size of the swapChainPanel
|
||||
_settings.InitialCols(width);
|
||||
_settings.InitialRows(height);
|
||||
// Override the default width and height to match the size of the swapChainPanel
|
||||
_settings.InitialCols(width);
|
||||
_settings.InitialRows(height);
|
||||
|
||||
_terminal->CreateFromSettings(_settings, renderTarget);
|
||||
_terminal->CreateFromSettings(_settings, renderTarget);
|
||||
|
||||
// Tell the DX Engine to notify us when the swap chain changes.
|
||||
dxEngine->SetCallback(std::bind(&TermControl::SwapChainChanged, this));
|
||||
// TODO:GH#3927 - Make it possible to hot-reload this setting. Right
|
||||
// here, the setting will only be used when the Terminal is initialized.
|
||||
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
|
||||
|
||||
// TODO:GH#3927 - Make it possible to hot-reload this setting. Right
|
||||
// here, the setting will only be used when the Terminal is initialized.
|
||||
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
|
||||
// TODO:GH#3927 - hot-reload this one too
|
||||
// Update DxEngine's AntialiasingMode
|
||||
switch (_settings.AntialiasingMode())
|
||||
{
|
||||
case TextAntialiasingMode::Cleartype:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
|
||||
break;
|
||||
case TextAntialiasingMode::Aliased:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
|
||||
break;
|
||||
case TextAntialiasingMode::Grayscale:
|
||||
default:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO:GH#3927 - hot-reload this one too
|
||||
// Update DxEngine's AntialiasingMode
|
||||
switch (_settings.AntialiasingMode())
|
||||
{
|
||||
case TextAntialiasingMode::Cleartype:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
|
||||
break;
|
||||
case TextAntialiasingMode::Aliased:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
|
||||
break;
|
||||
case TextAntialiasingMode::Grayscale:
|
||||
default:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
break;
|
||||
}
|
||||
THROW_IF_FAILED(dxEngine->Enable());
|
||||
_renderEngine = std::move(dxEngine);
|
||||
|
||||
THROW_IF_FAILED(dxEngine->Enable());
|
||||
_renderEngine = std::move(dxEngine);
|
||||
_AttachDxgiSwapChainToXaml(_renderEngine->GetSwapChain().Get());
|
||||
|
||||
// This event is explicitly revoked in the destructor: does not need weak_ref
|
||||
auto onReceiveOutputFn = [this](const hstring str) {
|
||||
_terminal->Write(str);
|
||||
};
|
||||
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
|
||||
// Tell the DX Engine to notify us when the swap chain changes.
|
||||
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
|
||||
_renderEngine->SetCallback(std::bind(&TermControl::RenderEngineSwapChainChanged, this));
|
||||
|
||||
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
|
||||
_terminal->SetWriteInputCallback(inputFn);
|
||||
auto bottom = _terminal->GetViewport().BottomExclusive();
|
||||
auto bufferHeight = bottom;
|
||||
|
||||
_SwapChainRoutine();
|
||||
ScrollBar().Maximum(bufferHeight - bufferHeight);
|
||||
ScrollBar().Minimum(0);
|
||||
ScrollBar().Value(0);
|
||||
ScrollBar().ViewportSize(bufferHeight);
|
||||
|
||||
// Set up the height of the ScrollViewer and the grid we're using to fake our scrolling height
|
||||
auto bottom = _terminal->GetViewport().BottomExclusive();
|
||||
auto bufferHeight = bottom;
|
||||
localPointerToThread->EnablePainting();
|
||||
|
||||
ScrollBar().Maximum(bufferHeight - bufferHeight);
|
||||
ScrollBar().Minimum(0);
|
||||
ScrollBar().Value(0);
|
||||
ScrollBar().ViewportSize(bufferHeight);
|
||||
// Set up blinking cursor
|
||||
int blinkTime = GetCaretBlinkTime();
|
||||
if (blinkTime != INFINITE)
|
||||
{
|
||||
// Create a timer
|
||||
DispatcherTimer cursorTimer;
|
||||
cursorTimer.Interval(std::chrono::milliseconds(blinkTime));
|
||||
cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick });
|
||||
cursorTimer.Start();
|
||||
_cursorTimer.emplace(std::move(cursorTimer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The user has disabled cursor blinking
|
||||
_cursorTimer = std::nullopt;
|
||||
}
|
||||
|
||||
localPointerToThread->EnablePainting();
|
||||
// import value from WinUser (convert from milli-seconds to micro-seconds)
|
||||
_multiClickTimer = GetDoubleClickTime() * 1000;
|
||||
|
||||
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
|
||||
_terminal->SetTitleChangedCallback(pfnTitleChanged);
|
||||
// Focus the control here. If we do it during control initialization, then
|
||||
// focus won't actually get passed to us. I believe this is because
|
||||
// we're not technically a part of the UI tree yet, so focusing us
|
||||
// becomes a no-op.
|
||||
this->Focus(FocusState::Programmatic);
|
||||
|
||||
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
|
||||
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
|
||||
_connection.Start();
|
||||
_initializedTerminal = true;
|
||||
} // scope for TerminalLock
|
||||
// call this event dispatcher outside of lock
|
||||
|
||||
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
|
||||
|
||||
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
|
||||
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
|
||||
_autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll });
|
||||
|
||||
// Set up blinking cursor
|
||||
int blinkTime = GetCaretBlinkTime();
|
||||
if (blinkTime != INFINITE)
|
||||
{
|
||||
// Create a timer
|
||||
_cursorTimer = std::make_optional(DispatcherTimer());
|
||||
_cursorTimer.value().Interval(std::chrono::milliseconds(blinkTime));
|
||||
_cursorTimer.value().Tick({ get_weak(), &TermControl::_BlinkCursor });
|
||||
_cursorTimer.value().Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The user has disabled cursor blinking
|
||||
_cursorTimer = std::nullopt;
|
||||
}
|
||||
|
||||
// import value from WinUser (convert from milli-seconds to micro-seconds)
|
||||
_multiClickTimer = GetDoubleClickTime() * 1000;
|
||||
|
||||
// Focus the control here. If we do it during control initialization, then
|
||||
// focus won't actually get passed to us. I believe this is because
|
||||
// we're not technically a part of the UI tree yet, so focusing us
|
||||
// becomes a no-op.
|
||||
this->Focus(FocusState::Programmatic);
|
||||
|
||||
_connection.Start();
|
||||
_initializedTerminal = true;
|
||||
_InitializedHandlers(*this, nullptr);
|
||||
return true;
|
||||
}
|
||||
@@ -878,6 +877,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - point: the PointerPoint object representing a mouse event from our XAML input handler
|
||||
bool TermControl::_CanSendVTMouseInput()
|
||||
{
|
||||
if (!_terminal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If the user is holding down Shift, suppress mouse events
|
||||
// TODO GH#4875: disable/customize this functionality
|
||||
const auto modifiers = _GetPressedModifierKeys();
|
||||
@@ -896,6 +899,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_PointerPressedHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_CapturePointer(sender, args);
|
||||
|
||||
const auto ptr = args.Pointer();
|
||||
@@ -1003,6 +1011,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_PointerMovedHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
@@ -1106,6 +1119,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
@@ -1156,6 +1174,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
if (_CanSendVTMouseInput())
|
||||
@@ -1286,7 +1309,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Controls::Primitives::RangeBaseValueChangedEventArgs const& args)
|
||||
{
|
||||
if (_isTerminalInitiatedScroll)
|
||||
if (_isTerminalInitiatedScroll || _closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1471,6 +1494,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_focused = false;
|
||||
|
||||
if (_uiaEngine.get())
|
||||
@@ -1542,8 +1566,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// concerned with initialization process. Value forwarded to event handler.
|
||||
void TermControl::_UpdateFont(const bool initialUpdate)
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
|
||||
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * SwapChainPanel().CompositionScaleX());
|
||||
|
||||
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
|
||||
@@ -1570,11 +1592,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
// Refresh our font with the renderer
|
||||
_UpdateFont();
|
||||
|
||||
auto lock = _terminal->LockForWriting();
|
||||
// Resize the terminal's BUFFER to match the new font size. This does
|
||||
// NOT change the size of the window, because that can lead to more
|
||||
// problems (like what happens when you change the font size while the
|
||||
// window is maximized?)
|
||||
auto lock = _terminal->LockForWriting();
|
||||
_DoResize(SwapChainPanel().ActualWidth(), SwapChainPanel().ActualHeight());
|
||||
}
|
||||
CATCH_LOG();
|
||||
@@ -1588,7 +1611,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_SwapChainSizeChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/,
|
||||
SizeChangedEventArgs const& e)
|
||||
{
|
||||
if (!_initializedTerminal)
|
||||
if (!_initializedTerminal || _closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1619,8 +1642,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Arguments:
|
||||
// - sender: not used
|
||||
// - e: not used
|
||||
void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */,
|
||||
Windows::Foundation::IInspectable const& /* e */)
|
||||
void TermControl::_CursorTimerTick(Windows::Foundation::IInspectable const& /* sender */,
|
||||
Windows::Foundation::IInspectable const& /* e */)
|
||||
{
|
||||
if ((_closing) || (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible()))
|
||||
{
|
||||
@@ -1772,10 +1795,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
hstring TermControl::Title()
|
||||
{
|
||||
if (!_initializedTerminal)
|
||||
return L"";
|
||||
|
||||
hstring hstr(_terminal->GetConsoleTitle());
|
||||
hstring hstr{ _terminal->GetConsoleTitle() };
|
||||
return hstr;
|
||||
}
|
||||
|
||||
@@ -1792,8 +1812,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - collapseText: collapse all of the text to one line
|
||||
bool TermControl::CopySelectionToClipboard(bool collapseText)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// no selection --> nothing to copy
|
||||
if (_terminal == nullptr || !_terminal->IsSelectionActive())
|
||||
if (!_terminal->IsSelectionActive())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1860,6 +1885,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_connectionStateChangedRevoker.revoke();
|
||||
|
||||
TSFInputControl().Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
|
||||
_autoScrollTimer.Stop();
|
||||
|
||||
if (auto localConnection{ std::exchange(_connection, nullptr) })
|
||||
{
|
||||
@@ -1877,11 +1903,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// renderEngine is destroyed
|
||||
}
|
||||
|
||||
if (auto localTerminal{ std::exchange(_terminal, nullptr) })
|
||||
{
|
||||
_initializedTerminal = false;
|
||||
// terminal is destroyed.
|
||||
}
|
||||
// we don't destroy _terminal here; it now has the same lifetime as the
|
||||
// control.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2195,6 +2218,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControl::_CompositionCompleted(winrt::hstring text)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connection.WriteInput(text);
|
||||
}
|
||||
|
||||
@@ -2207,11 +2235,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs)
|
||||
{
|
||||
// If we haven't initialized yet, just quick return.
|
||||
if (!_terminal)
|
||||
auto lock = _terminal->LockForReading();
|
||||
if (!_initializedTerminal)
|
||||
{
|
||||
// fake it
|
||||
eventArgs.CurrentPosition({ 0, 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
const COORD cursorPos = _terminal->GetCursorPosition();
|
||||
Windows::Foundation::Point p = { gsl::narrow_cast<float>(cursorPos.X), gsl::narrow_cast<float>(cursorPos.Y) };
|
||||
eventArgs.CurrentPosition(p);
|
||||
@@ -2281,8 +2312,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - e: The DragEventArgs from the Drop event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget TermControl::_DoDragDrop(DragEventArgs const e)
|
||||
winrt::fire_and_forget TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
DragEventArgs const e)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.DataView().Contains(StandardDataFormats::StorageItems()))
|
||||
{
|
||||
auto items = co_await e.DataView().GetStorageItemsAsync();
|
||||
@@ -2317,22 +2354,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Synchronous handler for the "Drop" event. We'll dispatch the async
|
||||
// _DoDragDrop method to handle this, because getting information about
|
||||
// the file that was potentially dropped onto us must be done off the UI
|
||||
// thread.
|
||||
// Arguments:
|
||||
// - e: The DragEventArgs from the Drop event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
DragEventArgs const& e)
|
||||
{
|
||||
// Dispatch an async method to handle the drop event.
|
||||
_DoDragDrop(e);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handle the DragOver event. We'll signal that the drag operation we
|
||||
// support is the "copy" operation, and we'll also customize the
|
||||
@@ -2346,6 +2367,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_DragOverHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
DragEventArgs const& e)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.DataView().Contains(StandardDataFormats::StorageItems()))
|
||||
{
|
||||
// We can't do anything for non-storageitems right now.
|
||||
|
||||
@@ -77,7 +77,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void AdjustFontSize(int fontSizeDelta);
|
||||
void ResetFontSize();
|
||||
|
||||
winrt::fire_and_forget SwapChainChanged();
|
||||
winrt::fire_and_forget RenderEngineSwapChainChanged();
|
||||
void _AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain);
|
||||
|
||||
void CreateSearchBoxControl();
|
||||
|
||||
@@ -183,15 +184,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void _ScrollbarChangeHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e);
|
||||
void _GotFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _LostFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _DragDropHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const& e);
|
||||
winrt::fire_and_forget _DragDropHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const e);
|
||||
void _DragOverHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const& e);
|
||||
winrt::fire_and_forget _DoDragDrop(Windows::UI::Xaml::DragEventArgs const e);
|
||||
|
||||
void _BlinkCursor(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition);
|
||||
void _SendInputToConnection(const std::wstring& wstr);
|
||||
void _SendPastedTextToConnection(const std::wstring& wstr);
|
||||
winrt::fire_and_forget _SwapChainRoutine();
|
||||
void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
|
||||
void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args);
|
||||
void _DoResize(const double newWidth, const double newHeight);
|
||||
|
||||
@@ -701,6 +701,7 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
|
||||
|
||||
if (notifyScroll)
|
||||
{
|
||||
// TODO: don't do this, thanks migrie
|
||||
_buffer->GetRenderTarget().TriggerRedrawAll();
|
||||
_NotifyScrollEvent();
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
static const SHORT TerminalViewWidth = 80;
|
||||
static const SHORT TerminalViewHeight = 32;
|
||||
|
||||
// This test class is for tests that are supposed to emit something in the PTY layer
|
||||
// and then check that they've been staged for presentation correctly inside
|
||||
// the Terminal application. Which sequences were used to get here don't matter.
|
||||
TEST_CLASS(ConptyRoundtripTests);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
@@ -171,6 +174,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
|
||||
TEST_METHOD(TestResizeHeight);
|
||||
|
||||
TEST_METHOD(ScrollWithMargins);
|
||||
|
||||
private:
|
||||
bool _writeCallback(const char* const pch, size_t const cch);
|
||||
void _flushFirstFrame();
|
||||
@@ -1055,3 +1060,245 @@ void ConptyRoundtripTests::PassthroughHardReset()
|
||||
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
|
||||
}
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::ScrollWithMargins()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
Log::Comment(L"Flush first frame.");
|
||||
_flushFirstFrame();
|
||||
|
||||
// Fill up the buffer with some text.
|
||||
// We're going to write something like this:
|
||||
// AAAA
|
||||
// BBBB
|
||||
// CCCC
|
||||
// ........
|
||||
// QQQQ
|
||||
// ****************
|
||||
// The letters represent the data in the TMUX pane.
|
||||
// The final *** line represents the mode line which we will
|
||||
// attempt to hold in place and not scroll.
|
||||
|
||||
Log::Comment(L"Fill host with text pattern by feeding it into VT parser.");
|
||||
const auto rowsToWrite = initialTermView.Height() - 1;
|
||||
|
||||
// For all lines but the last one, write out a few of a letter.
|
||||
for (auto i = 0; i < rowsToWrite; ++i)
|
||||
{
|
||||
const wchar_t wch = static_cast<wchar_t>(L'A' + i);
|
||||
hostSm.ProcessCharacter(wch);
|
||||
hostSm.ProcessCharacter(wch);
|
||||
hostSm.ProcessCharacter(wch);
|
||||
hostSm.ProcessCharacter(wch);
|
||||
hostSm.ProcessCharacter('\n');
|
||||
}
|
||||
|
||||
// For the last one, write out the asterisks for the mode line.
|
||||
for (auto i = 0; i < initialTermView.Width(); ++i)
|
||||
{
|
||||
hostSm.ProcessCharacter('*');
|
||||
}
|
||||
|
||||
// no newline in the bottom right corner or it will move unexpectedly.
|
||||
|
||||
// Now set up the verification that the buffers are full of the pattern we expect.
|
||||
// This function will verify the text backing buffers.
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor is waiting in the bottom right corner
|
||||
VERIFY_ARE_EQUAL(initialTermView.Height() - 1, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(initialTermView.Width() - 1, cursor.GetPosition().X);
|
||||
|
||||
// For all rows except the last one, verify that we have a run of four letters.
|
||||
for (auto i = 0; i < rowsToWrite; ++i)
|
||||
{
|
||||
const std::wstring expectedString(4, static_cast<wchar_t>(L'A' + i));
|
||||
const COORD expectedPos{ 0, gsl::narrow<SHORT>(i) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedString, expectedPos);
|
||||
}
|
||||
|
||||
// For the last row, verify we have an entire row of asterisks for the mode line.
|
||||
const std::wstring expectedModeLine(initialTermView.Width(), L'*');
|
||||
const COORD expectedPos{ 0, gsl::narrow<SHORT>(rowsToWrite) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedModeLine, expectedPos);
|
||||
};
|
||||
|
||||
// This will verify the text emitted from the PTY.
|
||||
for (auto i = 0; i < rowsToWrite; ++i)
|
||||
{
|
||||
const std::string expectedString(4, static_cast<char>('A' + i));
|
||||
expectedOutput.push_back(expectedString);
|
||||
expectedOutput.push_back("\r\n");
|
||||
}
|
||||
{
|
||||
const std::string expectedString(initialTermView.Width(), '*');
|
||||
expectedOutput.push_back(expectedString);
|
||||
|
||||
// Cursor gets reset into bottom right corner as we're writing all the way into that corner.
|
||||
std::stringstream ss;
|
||||
ss << "\x1b[" << initialTermView.Height() << ";" << initialTermView.Width() << "H";
|
||||
expectedOutput.push_back(ss.str());
|
||||
}
|
||||
|
||||
Log::Comment(L"Verify host buffer contains pattern.");
|
||||
// Verify the host side.
|
||||
verifyBuffer(hostTb);
|
||||
|
||||
Log::Comment(L"Emit PTY frame and validate it transmits the right data.");
|
||||
// Paint the frame
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
Log::Comment(L"Verify terminal buffer contains pattern.");
|
||||
// Verify the terminal side.
|
||||
verifyBuffer(termTb);
|
||||
|
||||
Log::Comment(L"!!! OK. Set up the scroll region and let's get scrolling!");
|
||||
// This is a simulation of what TMUX does to scroll everything except the mode line.
|
||||
// First build up our VT strings...
|
||||
std::wstring reducedScrollRegion;
|
||||
{
|
||||
std::wstringstream wss;
|
||||
// For 20 tall buffer...
|
||||
// ESC[1;19r
|
||||
// Set scroll region to lines 1-19.
|
||||
wss << L"\x1b[1;" << initialTermView.Height() - 1 << L"r";
|
||||
reducedScrollRegion = wss.str();
|
||||
}
|
||||
std::wstring completeScrollRegion;
|
||||
{
|
||||
std::wstringstream wss;
|
||||
// For 20 tall buffer...
|
||||
// ESC[1;20r
|
||||
// Set scroll region to lines 1-20. (or the whole buffer)
|
||||
wss << L"\x1b[1;" << initialTermView.Height() << L"r";
|
||||
completeScrollRegion = wss.str();
|
||||
}
|
||||
std::wstring reducedCursorBottomRight;
|
||||
{
|
||||
std::wstringstream wss;
|
||||
// For 20 tall and 100 wide buffer
|
||||
// ESC[19;100H
|
||||
// Put cursor on line 19 (1 before last) and the right most column 100.
|
||||
// (Remember that in VT, we start counting from 1 not 0.)
|
||||
wss << L"\x1b[" << initialTermView.Height() - 1 << L";" << initialTermView.Width() << "H";
|
||||
reducedCursorBottomRight = wss.str();
|
||||
}
|
||||
std::wstring completeCursorAtPromptLine;
|
||||
{
|
||||
std::wstringstream wss;
|
||||
// For 20 tall and 100 wide buffer
|
||||
// ESC[19;1H
|
||||
// Put cursor on line 19 (1 before last) and the left most column 1.
|
||||
// (Remember that in VT, we start counting from 1 not 0.)
|
||||
wss << L"\x1b[" << initialTermView.Height() - 1 << L";1H";
|
||||
completeCursorAtPromptLine = wss.str();
|
||||
}
|
||||
|
||||
Log::Comment(L"Perform all the operations on the buffer.");
|
||||
|
||||
// OK this is what TMUX does.
|
||||
// 1. Mark off the scroll area as everything but the mode line.
|
||||
hostSm.ProcessString(reducedScrollRegion);
|
||||
// 2. Put the cursor in the bottom-right corner of the scroll region.
|
||||
hostSm.ProcessString(reducedCursorBottomRight);
|
||||
// 3. Send a single newline which should do the heavy lifting
|
||||
// of pushing everything in the scroll region up by 1 line and
|
||||
// leave everything outside the region alone.
|
||||
|
||||
// This entire block is subject to change in the future with optimizations.
|
||||
{
|
||||
// Cursor gets redrawn in the bottom right of the scroll region with the repaint that is forced
|
||||
// early while the screen is rotated.
|
||||
std::stringstream ss;
|
||||
ss << "\x1b[" << initialTermView.Height() - 1 << ";" << initialTermView.Width() << "H";
|
||||
expectedOutput.push_back(ss.str());
|
||||
|
||||
expectedOutput.push_back("\x1b[?25h"); // turn the cursor back on too.
|
||||
}
|
||||
|
||||
hostSm.ProcessString(L"\n");
|
||||
// 4. Remove the scroll area by setting it to the entire size of the viewport.
|
||||
hostSm.ProcessString(completeScrollRegion);
|
||||
// 5. Put the cursor back at the beginning of the new line that was just revealed.
|
||||
hostSm.ProcessString(completeCursorAtPromptLine);
|
||||
|
||||
// Set up the verifications like above.
|
||||
auto verifyBufferAfter = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor is waiting on the freshly revealed line (1 above mode line)
|
||||
// and in the left most column.
|
||||
VERIFY_ARE_EQUAL(initialTermView.Height() - 2, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(0, cursor.GetPosition().X);
|
||||
|
||||
// For all rows except the last two, verify that we have a run of four letters.
|
||||
for (auto i = 0; i < rowsToWrite - 1; ++i)
|
||||
{
|
||||
// Start with B this time because the A line got scrolled off the top.
|
||||
const std::wstring expectedString(4, static_cast<wchar_t>(L'B' + i));
|
||||
const COORD expectedPos{ 0, gsl::narrow<SHORT>(i) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedString, expectedPos);
|
||||
}
|
||||
|
||||
// For the second to last row, verify that it is blank.
|
||||
{
|
||||
const std::wstring expectedBlankLine(initialTermView.Width(), L' ');
|
||||
const COORD blankLinePos{ 0, gsl::narrow<SHORT>(rowsToWrite - 1) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedBlankLine, blankLinePos);
|
||||
}
|
||||
|
||||
// For the last row, verify we have an entire row of asterisks for the mode line.
|
||||
{
|
||||
const std::wstring expectedModeLine(initialTermView.Width(), L'*');
|
||||
const COORD modeLinePos{ 0, gsl::narrow<SHORT>(rowsToWrite) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedModeLine, modeLinePos);
|
||||
}
|
||||
};
|
||||
|
||||
// This will verify the text emitted from the PTY.
|
||||
|
||||
expectedOutput.push_back("\x1b[H"); // cursor returns to top left corner.
|
||||
for (auto i = 0; i < rowsToWrite - 1; ++i)
|
||||
{
|
||||
const std::string expectedString(4, static_cast<char>('B' + i));
|
||||
expectedOutput.push_back(expectedString);
|
||||
expectedOutput.push_back("\x1b[K"); // erase the rest of the line.
|
||||
expectedOutput.push_back("\r\n");
|
||||
}
|
||||
{
|
||||
expectedOutput.push_back(""); // nothing for the empty line
|
||||
expectedOutput.push_back("\x1b[K"); // erase the rest of the line.
|
||||
expectedOutput.push_back("\r\n");
|
||||
}
|
||||
{
|
||||
const std::string expectedString(initialTermView.Width(), '*');
|
||||
expectedOutput.push_back(expectedString);
|
||||
}
|
||||
{
|
||||
// Cursor gets reset into second line from bottom, left most column
|
||||
std::stringstream ss;
|
||||
ss << "\x1b[" << initialTermView.Height() - 1 << ";1H";
|
||||
expectedOutput.push_back(ss.str());
|
||||
}
|
||||
expectedOutput.push_back("\x1b[?25h"); // turn the cursor back on too.
|
||||
|
||||
Log::Comment(L"Verify host buffer contains pattern moved up one and mode line still in place.");
|
||||
// Verify the host side.
|
||||
verifyBufferAfter(hostTb);
|
||||
|
||||
Log::Comment(L"Emit PTY frame and validate it transmits the right data.");
|
||||
// Paint the frame
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
Log::Comment(L"Verify terminal buffer contains pattern moved up one and mode line still in place.");
|
||||
// Verify the terminal side.
|
||||
verifyBufferAfter(termTb);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
WM_MOUSEACTIVATE = 0x0021,
|
||||
|
||||
WM_GETOBJECT = 0x003D,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place in the Z order has changed as a result of a call to the SetWindowPos function or another window-management function.
|
||||
/// </summary>
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
private DispatcherTimer blinkTimer;
|
||||
private NativeMethods.ScrollCallback scrollCallback;
|
||||
private NativeMethods.WriteCallback writeCallback;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TerminalContainer"/> class.
|
||||
/// </summary>
|
||||
@@ -35,7 +35,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
this.MessageHook += this.TerminalContainer_MessageHook;
|
||||
this.GotFocus += this.TerminalContainer_GotFocus;
|
||||
this.Focusable = true;
|
||||
|
||||
|
||||
var blinkTime = NativeMethods.GetCaretBlinkTime();
|
||||
|
||||
if (blinkTime != uint.MaxValue)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A basic terminal control. This control can receive and render standard VT100 sequences.
|
||||
/// </summary>
|
||||
|
||||
@@ -171,7 +171,33 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
||||
// displays the correct text.
|
||||
if (newViewOrigin == viewport.Origin())
|
||||
{
|
||||
Viewport invalid = Viewport::FromDimensions(viewport.Origin(), { viewport.Width(), delta });
|
||||
// Inside this block, we're shifting down at the bottom.
|
||||
// This means that we had something like this:
|
||||
// AAAA
|
||||
// BBBB
|
||||
// CCCC
|
||||
// DDDD
|
||||
// EEEE
|
||||
//
|
||||
// Our margins were set for lines A-D, but not on line E.
|
||||
// So we circled the whole buffer up by one:
|
||||
// BBBB
|
||||
// CCCC
|
||||
// DDDD
|
||||
// EEEE
|
||||
// <blank, was AAAA>
|
||||
//
|
||||
// Then we scrolled the contents of everything OUTSIDE the margin frame down.
|
||||
// BBBB
|
||||
// CCCC
|
||||
// DDDD
|
||||
// <blank, filled during scroll down of EEEE>
|
||||
// EEEE
|
||||
//
|
||||
// And now we need to report that only the bottom line didn't "move" as we put the EEEE
|
||||
// back where it started, but everything else moved.
|
||||
// In this case, delta was 1. So the amount that moved is the entire viewport height minus the delta.
|
||||
Viewport invalid = Viewport::FromDimensions(viewport.Origin(), { viewport.Width(), viewport.Height() - delta });
|
||||
screenInfo.GetRenderTarget().TriggerRedraw(invalid);
|
||||
}
|
||||
|
||||
|
||||
@@ -481,7 +481,7 @@ namespace Conhost.UIA.Tests
|
||||
TextPatternRange testRange = visibleRanges.First().Clone();
|
||||
|
||||
// assumes that range is a line range at the top of the screen buffer
|
||||
Action<TextPatternRange> testTopBoundary = delegate(TextPatternRange range)
|
||||
Action<TextPatternRange> testTopBoundary = delegate (TextPatternRange range)
|
||||
{
|
||||
// the first visible range is at the top of the screen
|
||||
// buffer, we shouldn't be able to move the starting endpoint up
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Conhost.UIA.Tests.Common
|
||||
|
||||
~ShortcutHelper()
|
||||
{
|
||||
this.Dispose(false);
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Conhost.UIA.Tests.Elements
|
||||
|
||||
tab.Click();
|
||||
Globals.WaitForTimeout();
|
||||
|
||||
|
||||
this.PopulateItemsOnNavigate(this.propDialog.PropWindow);
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Conhost.UIA.Tests.Elements
|
||||
this.tabs.Add(new FontTab(this.propDialog));
|
||||
this.tabs.Add(new LayoutTab(this.propDialog));
|
||||
this.tabs.Add(new ColorsTab(this.propDialog));
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Tabs()
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Conhost.UIA.Tests.Elements
|
||||
app.UIRoot.SendKeys(Keys.Escape);
|
||||
this.state = ViewportStates.Normal;
|
||||
}
|
||||
|
||||
|
||||
public void EnterMode(ViewportStates state)
|
||||
{
|
||||
if (state == ViewportStates.Normal)
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Conhost.UIA.Tests
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
|
||||
afterScroll = app.GetScreenBufferInfo();
|
||||
|
||||
switch (dir)
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Conhost.UIA.Tests
|
||||
reg.BackupRegistry(); // we're going to modify the virtual terminal state for this, so back it up first.
|
||||
VersionSelector.SetConsoleVersion(reg, ConsoleVersion.V2);
|
||||
reg.SetDefaultValue(VIRTUAL_TERMINAL_KEY_NAME, VIRTUAL_TERMINAL_ON_VALUE);
|
||||
|
||||
|
||||
bool haveVtAppPath = !string.IsNullOrEmpty(vtAppLocation);
|
||||
|
||||
Verify.IsTrue(haveVtAppPath, "Ensure that we passed in the location to VtApp.exe");
|
||||
@@ -114,12 +114,12 @@ namespace Conhost.UIA.Tests
|
||||
Log.Comment("Move cursor to the middle-ish");
|
||||
Point cursorExpected = new Point();
|
||||
// H is at 5, 1. VT coords are 1-based and buffer is 0-based so adjust.
|
||||
cursorExpected.Y = 5 - 1;
|
||||
cursorExpected.Y = 5 - 1;
|
||||
cursorExpected.X = 1 - 1;
|
||||
app.UIRoot.SendKeys("H");
|
||||
|
||||
// Move to middle-ish from here. 10 Bs and 10 Cs should about do it.
|
||||
for (int i=0; i < 10; i++)
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
app.UIRoot.SendKeys("BC");
|
||||
cursorExpected.Y++;
|
||||
@@ -715,7 +715,7 @@ namespace Conhost.UIA.Tests
|
||||
}
|
||||
|
||||
delegate char GetExpectedChar(int rowId, int colId, int height, int width);
|
||||
|
||||
|
||||
private static void ScreenFillHelper(CmdApp app, ViewportArea area, IntPtr hConsole)
|
||||
{
|
||||
Log.Comment("Fill screen with junk");
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Conhost.UIA.Tests
|
||||
[TestMethod]
|
||||
public void TestSelection()
|
||||
{
|
||||
RunTest(TestSelectionImpl);
|
||||
RunTest(TestSelectionImpl);
|
||||
}
|
||||
|
||||
private void TestSelectionImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
|
||||
@@ -295,7 +295,7 @@ namespace Conhost.UIA.Tests
|
||||
[TestMethod]
|
||||
public void TestLaunchAndExitChild()
|
||||
{
|
||||
RunTest(TestLaunchAndExitChildImpl);
|
||||
RunTest(TestLaunchAndExitChildImpl);
|
||||
}
|
||||
|
||||
private void TestLaunchAndExitChildImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
|
||||
@@ -357,11 +357,11 @@ namespace Conhost.UIA.Tests
|
||||
{
|
||||
RunTest(TestScrollByWheelImpl);
|
||||
}
|
||||
|
||||
|
||||
private void TestScrollByWheelImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
|
||||
{
|
||||
int rowsPerScroll = app.GetRowsPerScroll();
|
||||
int scrollDelta;
|
||||
int scrollDelta;
|
||||
|
||||
// A. Scroll down.
|
||||
{
|
||||
|
||||
@@ -27,6 +27,9 @@ using namespace Microsoft::Console::Types;
|
||||
|
||||
class ConptyOutputTests
|
||||
{
|
||||
// This test class is to write some things into the PTY and then check that
|
||||
// the rendering that is coming out of the VT-sequence generator is exactly
|
||||
// as we expect it to be.
|
||||
BEGIN_TEST_CLASS(ConptyOutputTests)
|
||||
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
|
||||
END_TEST_CLASS()
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
|
||||
#include "til/at.h"
|
||||
#include "til/color.h"
|
||||
#include "til/math.h"
|
||||
#include "til/some.h"
|
||||
#include "til/size.h"
|
||||
#include "til/point.h"
|
||||
#include "til/rectangle.h"
|
||||
#include "til/operators.h"
|
||||
#include "til/rectangle.h"
|
||||
#include "til/bitmap.h"
|
||||
#include "til/u8u16convert.h"
|
||||
|
||||
|
||||
@@ -285,7 +285,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
|
||||
for (const auto pt : rc)
|
||||
{
|
||||
til::at(_bits, _rc.index_of(pt)) = true;
|
||||
auto idx = _rc.index_of(pt);
|
||||
til::at(_bits, idx) = true;
|
||||
}
|
||||
|
||||
_dirty |= rc;
|
||||
|
||||
85
src/inc/til/math.h
Normal file
85
src/inc/til/math.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace til
|
||||
{
|
||||
// The til::math namespace contains TIL math guidance casts;
|
||||
// they are intended to be used as the first argument to
|
||||
// floating-point universal converters elsewhere in the til namespace.
|
||||
namespace math
|
||||
{
|
||||
namespace details
|
||||
{
|
||||
struct ceiling_t
|
||||
{
|
||||
template<typename O, typename T>
|
||||
static O cast(T val)
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, ::std::isnan(val));
|
||||
return ::base::saturated_cast<O>(::std::ceil(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ::base::saturated_cast<O>(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct flooring_t
|
||||
{
|
||||
template<typename O, typename T>
|
||||
static O cast(T val)
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, ::std::isnan(val));
|
||||
return ::base::saturated_cast<O>(::std::floor(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ::base::saturated_cast<O>(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct rounding_t
|
||||
{
|
||||
template<typename O, typename T>
|
||||
static O cast(T val)
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, ::std::isnan(val));
|
||||
return ::base::saturated_cast<O>(::std::round(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ::base::saturated_cast<O>(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct truncating_t
|
||||
{
|
||||
template<typename O, typename T>
|
||||
static O cast(T val)
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, ::std::isnan(val));
|
||||
}
|
||||
return ::base::saturated_cast<O>(val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr details::ceiling_t ceiling; // positives become more positive, negatives become less negative
|
||||
static constexpr details::flooring_t flooring; // positives become less positive, negatives become more negative
|
||||
static constexpr details::rounding_t rounding; // it's rounding, from math class
|
||||
static constexpr details::truncating_t truncating; // drop the decimal point, regardless of how close it is to the next value
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rectangle.h"
|
||||
#include "size.h"
|
||||
#include "bitmap.h"
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
// Operators go here when they involve two headers that can't/don't include each other.
|
||||
|
||||
@@ -53,6 +53,22 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a X and a Y field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr point(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().X)> && std::is_floating_point_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
|
||||
point(TilMath::template cast<ptrdiff_t>(other.X), TilMath::template cast<ptrdiff_t>(other.Y))
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a x and a y field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr point(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().x)> && std::is_floating_point_v<decltype(std::declval<TOther>().y)>, int> /*sentinel*/ = 0) :
|
||||
point(TilMath::template cast<ptrdiff_t>(other.x), TilMath::template cast<ptrdiff_t>(other.y))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr bool operator==(const point& other) const noexcept
|
||||
{
|
||||
return _x == other._x &&
|
||||
@@ -107,6 +123,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point& operator+=(const point& other)
|
||||
{
|
||||
*this = *this + other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
point operator-(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
@@ -118,6 +140,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point& operator-=(const point& other)
|
||||
{
|
||||
*this = *this - other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
point operator*(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
@@ -129,6 +157,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point& operator*=(const point& other)
|
||||
{
|
||||
*this = *this * other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
point operator/(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
@@ -140,6 +174,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point& operator/=(const point& other)
|
||||
{
|
||||
*this = *this / other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t x() const noexcept
|
||||
{
|
||||
return _x;
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "point.h"
|
||||
#include "size.h"
|
||||
#include "some.h"
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class RectangleTests;
|
||||
#endif
|
||||
@@ -636,6 +632,32 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return *this;
|
||||
}
|
||||
|
||||
// MUL will scale the entire rectangle up by the size factor
|
||||
rectangle operator*(const size& size)
|
||||
{
|
||||
auto topLeft = _topLeft;
|
||||
auto bottomRight = _bottomRight;
|
||||
topLeft = topLeft * size;
|
||||
bottomRight = bottomRight * size;
|
||||
return til::rectangle{ topLeft, bottomRight };
|
||||
}
|
||||
|
||||
// DIV will scale the entire rectangle down by the size factor,
|
||||
// but rounds the bottom-right corner out.
|
||||
rectangle operator/(const size& size)
|
||||
{
|
||||
auto topLeft = _topLeft;
|
||||
auto bottomRight = _bottomRight;
|
||||
topLeft = topLeft / size;
|
||||
|
||||
// Move bottom right point into a size
|
||||
// Use size specialization of divide_ceil to round up against the size given.
|
||||
// Add leading addition to point to convert it back into a point.
|
||||
bottomRight = til::point{} + til::size{ right(), bottom() }.divide_ceil(size);
|
||||
|
||||
return til::rectangle{ topLeft, bottomRight };
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
constexpr ptrdiff_t top() const noexcept
|
||||
|
||||
@@ -53,6 +53,30 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a X and a Y field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().X)> && std::is_floating_point_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
|
||||
size(TilMath::template cast<ptrdiff_t>(other.X), TilMath::template cast<ptrdiff_t>(other.Y))
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a cx and a cy field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().cx)> && std::is_floating_point_v<decltype(std::declval<TOther>().cy)>, int> /*sentinel*/ = 0) :
|
||||
size(TilMath::template cast<ptrdiff_t>(other.cx), TilMath::template cast<ptrdiff_t>(other.cy))
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a Width and a Height field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().Width)> && std::is_floating_point_v<decltype(std::declval<TOther>().Height)>, int> /*sentinel*/ = 0) :
|
||||
size(TilMath::template cast<ptrdiff_t>(other.Width), TilMath::template cast<ptrdiff_t>(other.Height))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr bool operator==(const size& other) const noexcept
|
||||
{
|
||||
return _width == other._width &&
|
||||
|
||||
@@ -273,7 +273,12 @@ using namespace Microsoft::Console::Render;
|
||||
rect.right = std::accumulate(advancesSpan.cbegin(), advancesSpan.cend(), rect.right);
|
||||
|
||||
// Clip all drawing in this glyph run to where we expect.
|
||||
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||
// We need the AntialiasMode here to be Aliased to ensure
|
||||
// that background boxes line up with each other and don't leave behind
|
||||
// stray colors.
|
||||
// See GH#3626 for more details.
|
||||
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
|
||||
// Ensure we pop it on the way out
|
||||
auto popclip = wil::scope_exit([&d2dContext]() noexcept {
|
||||
d2dContext->PopAxisAlignedClip();
|
||||
|
||||
@@ -65,9 +65,8 @@ using namespace Microsoft::Console::Types;
|
||||
// TODO GH 2683: The default constructor should not throw.
|
||||
DxEngine::DxEngine() :
|
||||
RenderEngineBase(),
|
||||
_isInvalidUsed{ false },
|
||||
_invalidRect{ 0 },
|
||||
_invalidScroll{ 0 },
|
||||
_invalidMap{},
|
||||
_invalidScroll{},
|
||||
_presentParams{ 0 },
|
||||
_presentReady{ false },
|
||||
_presentScroll{ 0 },
|
||||
@@ -75,16 +74,16 @@ DxEngine::DxEngine() :
|
||||
_presentOffset{ 0 },
|
||||
_isEnabled{ false },
|
||||
_isPainting{ false },
|
||||
_displaySizePixels{ 0 },
|
||||
_displaySizePixels{},
|
||||
_foregroundColor{ 0 },
|
||||
_backgroundColor{ 0 },
|
||||
_selectionBackground{},
|
||||
_glyphCell{ 0 },
|
||||
_glyphCell{},
|
||||
_haveDeviceResources{ false },
|
||||
_retroTerminalEffects{ false },
|
||||
_antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE },
|
||||
_hwndTarget{ static_cast<HWND>(INVALID_HANDLE_VALUE) },
|
||||
_sizeTarget{ 0 },
|
||||
_sizeTarget{},
|
||||
_dpi{ USER_DEFAULT_SCREEN_DPI },
|
||||
_scale{ 1.0f },
|
||||
_chainMode{ SwapChainMode::ForComposition },
|
||||
@@ -238,8 +237,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
|
||||
|
||||
// Setup the viewport.
|
||||
D3D11_VIEWPORT vp;
|
||||
vp.Width = static_cast<FLOAT>(_displaySizePixels.cx);
|
||||
vp.Height = static_cast<FLOAT>(_displaySizePixels.cy);
|
||||
vp.Width = _displaySizePixels.width<float>();
|
||||
vp.Height = _displaySizePixels.height<float>();
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
vp.TopLeftX = 0;
|
||||
@@ -370,7 +369,7 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
// You can find out how to install it here:
|
||||
// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features
|
||||
// clang-format on
|
||||
// D3D11_CREATE_DEVICE_DEBUG |
|
||||
D3D11_CREATE_DEVICE_DEBUG |
|
||||
D3D11_CREATE_DEVICE_SINGLETHREADED;
|
||||
|
||||
const std::array<D3D_FEATURE_LEVEL, 5> FeatureLevels{ D3D_FEATURE_LEVEL_11_1,
|
||||
@@ -424,8 +423,7 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
{
|
||||
switch (_chainMode)
|
||||
{
|
||||
case SwapChainMode::ForHwnd:
|
||||
{
|
||||
case SwapChainMode::ForHwnd: {
|
||||
// use the HWND's dimensions for the swap chain dimensions.
|
||||
RECT rect = { 0 };
|
||||
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
|
||||
@@ -454,11 +452,10 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
|
||||
break;
|
||||
}
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
case SwapChainMode::ForComposition: {
|
||||
// Use the given target size for compositions.
|
||||
SwapChainDesc.Width = _displaySizePixels.cx;
|
||||
SwapChainDesc.Height = _displaySizePixels.cy;
|
||||
SwapChainDesc.Width = _displaySizePixels.width<UINT>();
|
||||
SwapChainDesc.Height = _displaySizePixels.height<UINT>();
|
||||
|
||||
// We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on.
|
||||
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
|
||||
@@ -533,6 +530,11 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
&props,
|
||||
&_d2dRenderTarget));
|
||||
|
||||
// We need the AntialiasMode for non-text object to be Aliased to ensure
|
||||
// that background boxes line up with each other and don't leave behind
|
||||
// stray colors.
|
||||
// See GH#3626 for more details.
|
||||
_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
_d2dRenderTarget->SetTextAntialiasMode(_antialiasingMode);
|
||||
|
||||
RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed),
|
||||
@@ -628,8 +630,8 @@ void DxEngine::_ReleaseDeviceResources() noexcept
|
||||
return _dwriteFactory->CreateTextLayout(string,
|
||||
gsl::narrow<UINT32>(stringLength),
|
||||
_dwriteTextFormat.Get(),
|
||||
gsl::narrow<float>(_displaySizePixels.cx),
|
||||
_glyphCell.cy != 0 ? _glyphCell.cy : gsl::narrow<float>(_displaySizePixels.cy),
|
||||
_displaySizePixels.width<float>(),
|
||||
_glyphCell.height() != 0 ? _glyphCell.height<float>() : _displaySizePixels.height<float>(),
|
||||
ppTextLayout);
|
||||
}
|
||||
|
||||
@@ -650,9 +652,7 @@ void DxEngine::_ReleaseDeviceResources() noexcept
|
||||
[[nodiscard]] HRESULT DxEngine::SetWindowSize(const SIZE Pixels) noexcept
|
||||
{
|
||||
_sizeTarget = Pixels;
|
||||
|
||||
RETURN_IF_FAILED(InvalidateAll());
|
||||
|
||||
_invalidMap.resize(_sizeTarget / _glyphCell, true);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -686,7 +686,8 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion);
|
||||
|
||||
_InvalidOr(*psrRegion);
|
||||
_invalidMap.set(Viewport::FromExclusive(*psrRegion).ToInclusive());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -700,8 +701,9 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
|
||||
|
||||
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToInclusive();
|
||||
return Invalidate(&sr);
|
||||
_invalidMap.set(*pcoordCursor);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -711,13 +713,17 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateSystem(const RECT* const prcDirtyClient) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, prcDirtyClient);
|
||||
|
||||
_InvalidOr(*prcDirtyClient);
|
||||
// Dirty client is in pixels. Use divide specialization against glyph factor to make conversion
|
||||
// to cells.
|
||||
_invalidMap.set(til::rectangle{ *prcDirtyClient } / _glyphCell);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Invalidates a series of character rectangles
|
||||
@@ -743,50 +749,24 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
|
||||
try
|
||||
{
|
||||
if (pcoordDelta->X != 0 || pcoordDelta->Y != 0)
|
||||
const til::point deltaCells{ *pcoordDelta };
|
||||
|
||||
if (deltaCells != til::point{ 0, 0 })
|
||||
{
|
||||
try
|
||||
{
|
||||
POINT delta = { 0 };
|
||||
delta.x = pcoordDelta->X * _glyphCell.cx;
|
||||
delta.y = pcoordDelta->Y * _glyphCell.cy;
|
||||
const til::point deltaPixels = deltaCells * _glyphCell;
|
||||
|
||||
_InvalidOffset(delta);
|
||||
// Shift the contents of the map and fill in revealed area.
|
||||
_invalidMap.translate(deltaCells, true);
|
||||
|
||||
_invalidScroll.cx += delta.x;
|
||||
_invalidScroll.cy += delta.y;
|
||||
|
||||
// Add the revealed portion of the screen from the scroll to the invalid area.
|
||||
const RECT display = _GetDisplayRect();
|
||||
RECT reveal = display;
|
||||
|
||||
// X delta first
|
||||
OffsetRect(&reveal, delta.x, 0);
|
||||
IntersectRect(&reveal, &reveal, &display);
|
||||
SubtractRect(&reveal, &display, &reveal);
|
||||
|
||||
if (!IsRectEmpty(&reveal))
|
||||
{
|
||||
_InvalidOr(reveal);
|
||||
}
|
||||
|
||||
// Y delta second (subtract rect won't work if you move both)
|
||||
reveal = display;
|
||||
OffsetRect(&reveal, 0, delta.y);
|
||||
IntersectRect(&reveal, &reveal, &display);
|
||||
SubtractRect(&reveal, &display, &reveal);
|
||||
|
||||
if (!IsRectEmpty(&reveal))
|
||||
{
|
||||
_InvalidOr(reveal);
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
// TODO: should we just maintain it all in cells?
|
||||
_invalidScroll += deltaPixels;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Invalidates the entire window area
|
||||
@@ -795,12 +775,12 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateAll() noexcept
|
||||
try
|
||||
{
|
||||
const RECT screen = _GetDisplayRect();
|
||||
_InvalidOr(screen);
|
||||
|
||||
_invalidMap.set_all();
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - This currently has no effect in this renderer.
|
||||
@@ -822,23 +802,17 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - X by Y area in pixels of the surface
|
||||
[[nodiscard]] SIZE DxEngine::_GetClientSize() const noexcept
|
||||
[[nodiscard]] til::size DxEngine::_GetClientSize() const noexcept
|
||||
{
|
||||
switch (_chainMode)
|
||||
{
|
||||
case SwapChainMode::ForHwnd:
|
||||
{
|
||||
case SwapChainMode::ForHwnd: {
|
||||
RECT clientRect = { 0 };
|
||||
LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect));
|
||||
|
||||
SIZE clientSize = { 0 };
|
||||
clientSize.cx = clientRect.right - clientRect.left;
|
||||
clientSize.cy = clientRect.bottom - clientRect.top;
|
||||
|
||||
return clientSize;
|
||||
return til::rectangle{ clientRect }.size();
|
||||
}
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
case SwapChainMode::ForComposition: {
|
||||
SIZE size = _sizeTarget;
|
||||
size.cx = static_cast<LONG>(size.cx * _scale);
|
||||
size.cy = static_cast<LONG>(size.cy * _scale);
|
||||
@@ -865,90 +839,6 @@ void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
|
||||
cellsToPixels.bottom *= fontSize.cy;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves a rectangle representation of the pixel size of the
|
||||
// surface we are drawing on
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value;
|
||||
// - Origin-placed rectangle representing the pixel size of the surface
|
||||
[[nodiscard]] RECT DxEngine::_GetDisplayRect() const noexcept
|
||||
{
|
||||
return { 0, 0, _displaySizePixels.cx, _displaySizePixels.cy };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to shift the existing dirty rectangle by a pixel offset
|
||||
// and crop it to still be within the bounds of the display surface
|
||||
// Arguments:
|
||||
// - delta - Adjustment distance in pixels
|
||||
// - -Y is up, Y is down, -X is left, X is right.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOffset(POINT delta)
|
||||
{
|
||||
if (_isInvalidUsed)
|
||||
{
|
||||
// Copy the existing invalid rect
|
||||
RECT invalidNew = _invalidRect;
|
||||
|
||||
// Offset it to the new position
|
||||
THROW_IF_WIN32_BOOL_FALSE(OffsetRect(&invalidNew, delta.x, delta.y));
|
||||
|
||||
// Get the rect representing the display
|
||||
const RECT rectScreen = _GetDisplayRect();
|
||||
|
||||
// Ensure that the new invalid rectangle is still on the display
|
||||
IntersectRect(&invalidNew, &invalidNew, &rectScreen);
|
||||
|
||||
_invalidRect = invalidNew;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine description:
|
||||
// - Adds the given character rectangle to the total dirty region
|
||||
// - Will scale internally to pixels based on the current font.
|
||||
// Arguments:
|
||||
// - sr - character rectangle
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
|
||||
{
|
||||
RECT region;
|
||||
region.left = sr.Left;
|
||||
region.top = sr.Top;
|
||||
region.right = sr.Right;
|
||||
region.bottom = sr.Bottom;
|
||||
_ScaleByFont(region, _glyphCell);
|
||||
|
||||
region.right += _glyphCell.cx;
|
||||
region.bottom += _glyphCell.cy;
|
||||
|
||||
_InvalidOr(region);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Adds the given pixel rectangle to the total dirty region
|
||||
// Arguments:
|
||||
// - rc - Dirty pixel rectangle
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
{
|
||||
if (_isInvalidUsed)
|
||||
{
|
||||
UnionRect(&_invalidRect, &_invalidRect, &rc);
|
||||
|
||||
const RECT rcScreen = _GetDisplayRect();
|
||||
IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
_invalidRect = rc;
|
||||
_isInvalidUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is unused by this renderer.
|
||||
// Arguments:
|
||||
@@ -971,24 +861,19 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
// - Any DirectX error, a memory error, etc.
|
||||
[[nodiscard]] HRESULT DxEngine::StartPaint() noexcept
|
||||
{
|
||||
FAIL_FAST_IF_FAILED(InvalidateAll());
|
||||
RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting.
|
||||
|
||||
if (TraceLoggingProviderEnabled(g_hDxRenderProvider, WINEVENT_LEVEL_VERBOSE, 0))
|
||||
{
|
||||
const auto invalidatedStr = _invalidMap.to_string();
|
||||
const auto invalidated = invalidatedStr.c_str();
|
||||
|
||||
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
|
||||
TraceLoggingWrite(g_hDxRenderProvider,
|
||||
"Invalid",
|
||||
TraceLoggingInt32(_invalidRect.bottom - _invalidRect.top, "InvalidHeight"),
|
||||
TraceLoggingInt32((_invalidRect.bottom - _invalidRect.top) / _glyphCell.cy, "InvalidHeightChars"),
|
||||
TraceLoggingInt32(_invalidRect.right - _invalidRect.left, "InvalidWidth"),
|
||||
TraceLoggingInt32((_invalidRect.right - _invalidRect.left) / _glyphCell.cx, "InvalidWidthChars"),
|
||||
TraceLoggingInt32(_invalidRect.left, "InvalidX"),
|
||||
TraceLoggingInt32(_invalidRect.left / _glyphCell.cx, "InvalidXChars"),
|
||||
TraceLoggingInt32(_invalidRect.top, "InvalidY"),
|
||||
TraceLoggingInt32(_invalidRect.top / _glyphCell.cy, "InvalidYChars"),
|
||||
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidth"),
|
||||
TraceLoggingInt32(_invalidScroll.cx / _glyphCell.cx, "ScrollWidthChars"),
|
||||
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeight"),
|
||||
TraceLoggingInt32(_invalidScroll.cy / _glyphCell.cy, "ScrollHeightChars"));
|
||||
TraceLoggingWrite(g_hDxRenderProvider,
|
||||
"Invalid",
|
||||
TraceLoggingWideString(invalidated),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
if (_isEnabled)
|
||||
{
|
||||
@@ -999,8 +884,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(_CreateDeviceResources(true));
|
||||
}
|
||||
else if (_displaySizePixels.cy != clientSize.cy ||
|
||||
_displaySizePixels.cx != clientSize.cx)
|
||||
else if (_displaySizePixels != clientSize)
|
||||
{
|
||||
// OK, we're going to play a dangerous game here for the sake of optimizing resize
|
||||
// First, set up a complete clear of all device resources if something goes terribly wrong.
|
||||
@@ -1013,7 +897,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
_d2dRenderTarget.Reset();
|
||||
|
||||
// Change the buffer size and recreate the render target (and surface)
|
||||
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0));
|
||||
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.width<UINT>(), clientSize.height<UINT>(), DXGI_FORMAT_B8G8R8A8_UNORM, 0));
|
||||
RETURN_IF_FAILED(_PrepareRenderTarget());
|
||||
|
||||
// OK we made it past the parts that can cause errors. We can release our failure handler.
|
||||
@@ -1039,6 +923,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
// Return Value:
|
||||
// - Any DirectX error, a memory error, etc.
|
||||
[[nodiscard]] HRESULT DxEngine::EndPaint() noexcept
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF(E_INVALIDARG, !_isPainting); // invalid to end paint when we're not painting
|
||||
|
||||
@@ -1052,21 +937,33 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (_invalidScroll.cy != 0 || _invalidScroll.cx != 0)
|
||||
if (_invalidScroll != til::point{ 0, 0 })
|
||||
{
|
||||
_presentDirty = _invalidRect;
|
||||
// Copy `til::rectangles` into RECT map.
|
||||
_presentDirty.assign(_invalidMap.begin(), _invalidMap.end());
|
||||
|
||||
const RECT display = _GetDisplayRect();
|
||||
SubtractRect(&_presentScroll, &display, &_presentDirty);
|
||||
_presentOffset.x = _invalidScroll.cx;
|
||||
_presentOffset.y = _invalidScroll.cy;
|
||||
// The scroll rect is the entire screen minus the revealed areas.
|
||||
// Get the entire screen into a rectangle.
|
||||
til::rectangle scrollArea{ _displaySizePixels };
|
||||
|
||||
_presentParams.DirtyRectsCount = 1;
|
||||
_presentParams.pDirtyRects = &_presentDirty;
|
||||
// Reduce the size of the rectangle by the scroll.
|
||||
scrollArea -= til::size{} - _invalidScroll;
|
||||
|
||||
// Assign the area to the present storage
|
||||
_presentScroll = scrollArea;
|
||||
|
||||
// Pass the offset.
|
||||
_presentOffset = _invalidScroll;
|
||||
|
||||
// Now fill up the parameters structure from the member variables.
|
||||
_presentParams.DirtyRectsCount = gsl::narrow<UINT>(_presentDirty.size());
|
||||
_presentParams.pDirtyRects = _presentDirty.data();
|
||||
|
||||
_presentParams.pScrollOffset = &_presentOffset;
|
||||
_presentParams.pScrollRect = &_presentScroll;
|
||||
|
||||
// The scroll rect will be empty if we scrolled >= 1 full screen size.
|
||||
// Present1 doesn't like that. So clear it out. Everything will be dirty anyway.
|
||||
if (IsRectEmpty(&_presentScroll))
|
||||
{
|
||||
_presentParams.pScrollRect = nullptr;
|
||||
@@ -1083,13 +980,13 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
_invalidRect = { 0 };
|
||||
_isInvalidUsed = false;
|
||||
_invalidMap.reset_all();
|
||||
|
||||
_invalidScroll = { 0 };
|
||||
_invalidScroll = {};
|
||||
|
||||
return hr;
|
||||
}
|
||||
CATCH_RETURN()
|
||||
|
||||
// Routine Description:
|
||||
// - Copies the front surface of the swap chain (the one being displayed)
|
||||
@@ -1141,8 +1038,8 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
hr = _dxgiSwapChain->Present(1, 0);
|
||||
/*hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);*/
|
||||
/*hr = _dxgiSwapChain->Present(1, 0);*/
|
||||
hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
@@ -1161,7 +1058,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
RETURN_IF_FAILED(_CopyFrontToBack());
|
||||
_presentReady = false;
|
||||
|
||||
_presentDirty = { 0 };
|
||||
_presentDirty.clear();
|
||||
_presentOffset = { 0 };
|
||||
_presentScroll = { 0 };
|
||||
_presentParams = { 0 };
|
||||
@@ -1191,21 +1088,19 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept
|
||||
{
|
||||
switch (_chainMode)
|
||||
{
|
||||
case SwapChainMode::ForHwnd:
|
||||
_d2dRenderTarget->FillRectangle(D2D1::RectF(static_cast<float>(_invalidRect.left),
|
||||
static_cast<float>(_invalidRect.top),
|
||||
static_cast<float>(_invalidRect.right),
|
||||
static_cast<float>(_invalidRect.bottom)),
|
||||
_d2dBrushBackground.Get());
|
||||
break;
|
||||
case SwapChainMode::ForComposition:
|
||||
D2D1_COLOR_F nothing = { 0 };
|
||||
D2D1_COLOR_F nothing = { 0 };
|
||||
|
||||
// Runs are counts of cells.
|
||||
// Use a transform by the size of one cell to convert cells-to-pixels
|
||||
// as we clear.
|
||||
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Scale(_glyphCell));
|
||||
for (const auto rect : _invalidMap.runs())
|
||||
{
|
||||
_d2dRenderTarget->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
_d2dRenderTarget->Clear(nothing);
|
||||
break;
|
||||
_d2dRenderTarget->PopAxisAlignedClip();
|
||||
}
|
||||
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1226,9 +1121,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
try
|
||||
{
|
||||
// Calculate positioning of our origin.
|
||||
D2D1_POINT_2F origin;
|
||||
origin.x = static_cast<float>(coord.X * _glyphCell.cx);
|
||||
origin.y = static_cast<float>(coord.Y * _glyphCell.cy);
|
||||
D2D1_POINT_2F origin = til::point{ coord } * _glyphCell;
|
||||
|
||||
// Create the text layout
|
||||
CustomTextLayout layout(_dwriteFactory.Get(),
|
||||
@@ -1236,7 +1129,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
_dwriteTextFormat.Get(),
|
||||
_dwriteFontFace.Get(),
|
||||
clusters,
|
||||
_glyphCell.cx);
|
||||
_glyphCell.width());
|
||||
|
||||
// Get the baseline for this font as that's where we draw from
|
||||
DWRITE_LINE_SPACING spacing;
|
||||
@@ -1248,7 +1141,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
_d2dBrushBackground.Get(),
|
||||
_dwriteFactory.Get(),
|
||||
spacing,
|
||||
D2D1::SizeF(gsl::narrow<FLOAT>(_glyphCell.cx), gsl::narrow<FLOAT>(_glyphCell.cy)),
|
||||
_glyphCell,
|
||||
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
|
||||
|
||||
// Layout then render the text
|
||||
@@ -1279,10 +1172,8 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
|
||||
_d2dBrushForeground->SetColor(_ColorFFromColorRef(color));
|
||||
|
||||
const auto font = _GetFontSize();
|
||||
D2D_POINT_2F target;
|
||||
target.x = static_cast<float>(coordTarget.X) * font.X;
|
||||
target.y = static_cast<float>(coordTarget.Y) * font.Y;
|
||||
const auto font = _glyphCell;
|
||||
D2D_POINT_2F target = til::point{ coordTarget } * font;
|
||||
|
||||
D2D_POINT_2F start = { 0 };
|
||||
D2D_POINT_2F end = { 0 };
|
||||
@@ -1295,7 +1186,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
if (lines & GridLines::Top)
|
||||
{
|
||||
end = start;
|
||||
end.x += font.X;
|
||||
end.x += font.width();
|
||||
|
||||
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
|
||||
}
|
||||
@@ -1303,7 +1194,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
if (lines & GridLines::Left)
|
||||
{
|
||||
end = start;
|
||||
end.y += font.Y;
|
||||
end.y += font.height();
|
||||
|
||||
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
|
||||
}
|
||||
@@ -1316,28 +1207,28 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
// The top right corner inclusive is at 7,0 which is X (0) + Font Height (8) - 1 = 7.
|
||||
|
||||
// 0.5 pixel offset for crisp lines; -0.5 on the Y to fit _in_ the cell, not outside it.
|
||||
start = { target.x + 0.5f, target.y + font.Y - 0.5f };
|
||||
start = { target.x + 0.5f, target.y + font.height() - 0.5f };
|
||||
|
||||
if (lines & GridLines::Bottom)
|
||||
{
|
||||
end = start;
|
||||
end.x += font.X - 1.f;
|
||||
end.x += font.width() - 1.f;
|
||||
|
||||
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
|
||||
}
|
||||
|
||||
start = { target.x + font.X - 0.5f, target.y + 0.5f };
|
||||
start = { target.x + font.width() - 0.5f, target.y + 0.5f };
|
||||
|
||||
if (lines & GridLines::Right)
|
||||
{
|
||||
end = start;
|
||||
end.y += font.Y - 1.f;
|
||||
end.y += font.height() - 1.f;
|
||||
|
||||
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
|
||||
}
|
||||
|
||||
// Move to the next character in this run.
|
||||
target.x += font.X;
|
||||
target.x += font.width();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
@@ -1356,17 +1247,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
_d2dBrushForeground->SetColor(_selectionBackground);
|
||||
const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); });
|
||||
|
||||
RECT pixels;
|
||||
pixels.left = rect.Left * _glyphCell.cx;
|
||||
pixels.top = rect.Top * _glyphCell.cy;
|
||||
pixels.right = rect.Right * _glyphCell.cx;
|
||||
pixels.bottom = rect.Bottom * _glyphCell.cy;
|
||||
|
||||
D2D1_RECT_F draw = { 0 };
|
||||
draw.left = static_cast<float>(pixels.left);
|
||||
draw.top = static_cast<float>(pixels.top);
|
||||
draw.right = static_cast<float>(pixels.right);
|
||||
draw.bottom = static_cast<float>(pixels.bottom);
|
||||
const D2D1_RECT_F draw = til::rectangle{ rect } *_glyphCell;
|
||||
|
||||
_d2dRenderTarget->FillRectangle(draw, _d2dBrushForeground.Get());
|
||||
|
||||
@@ -1394,52 +1275,42 @@ enum class CursorPaintType
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
// Create rectangular block representing where the cursor can fill.
|
||||
D2D1_RECT_F rect = { 0 };
|
||||
rect.left = static_cast<float>(options.coordCursor.X * _glyphCell.cx);
|
||||
rect.top = static_cast<float>(options.coordCursor.Y * _glyphCell.cy);
|
||||
rect.right = static_cast<float>(rect.left + _glyphCell.cx);
|
||||
rect.bottom = static_cast<float>(rect.top + _glyphCell.cy);
|
||||
// Create rectangular block representing where the cursor can fill.s
|
||||
D2D1_RECT_F rect = til::rectangle{ til::point{options.coordCursor} } *_glyphCell;
|
||||
|
||||
// If we're double-width, make it one extra glyph wider
|
||||
if (options.fIsDoubleWidth)
|
||||
{
|
||||
rect.right += _glyphCell.cx;
|
||||
rect.right += _glyphCell.width();
|
||||
}
|
||||
|
||||
CursorPaintType paintType = CursorPaintType::Fill;
|
||||
|
||||
switch (options.cursorType)
|
||||
{
|
||||
case CursorType::Legacy:
|
||||
{
|
||||
case CursorType::Legacy: {
|
||||
// Enforce min/max cursor height
|
||||
ULONG ulHeight = std::clamp(options.ulCursorHeightPercent, s_ulMinCursorHeightPercent, s_ulMaxCursorHeightPercent);
|
||||
|
||||
ulHeight = gsl::narrow<ULONG>((_glyphCell.cy * ulHeight) / 100);
|
||||
ulHeight = (_glyphCell.height<ULONG>() * ulHeight) / 100;
|
||||
rect.top = rect.bottom - ulHeight;
|
||||
break;
|
||||
}
|
||||
case CursorType::VerticalBar:
|
||||
{
|
||||
case CursorType::VerticalBar: {
|
||||
// It can't be wider than one cell or we'll have problems in invalidation, so restrict here.
|
||||
// It's either the left + the proposed width from the ease of access setting, or
|
||||
// it's the right edge of the block cursor as a maximum.
|
||||
rect.right = std::min(rect.right, rect.left + options.cursorPixelWidth);
|
||||
break;
|
||||
}
|
||||
case CursorType::Underscore:
|
||||
{
|
||||
case CursorType::Underscore: {
|
||||
rect.top = rect.bottom - 1;
|
||||
break;
|
||||
}
|
||||
case CursorType::EmptyBox:
|
||||
{
|
||||
case CursorType::EmptyBox: {
|
||||
paintType = CursorPaintType::Outline;
|
||||
break;
|
||||
}
|
||||
case CursorType::FullBox:
|
||||
{
|
||||
case CursorType::FullBox: {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -1456,13 +1327,11 @@ enum class CursorPaintType
|
||||
|
||||
switch (paintType)
|
||||
{
|
||||
case CursorPaintType::Fill:
|
||||
{
|
||||
case CursorPaintType::Fill: {
|
||||
_d2dRenderTarget->FillRectangle(rect, brush.Get());
|
||||
break;
|
||||
}
|
||||
case CursorPaintType::Outline:
|
||||
{
|
||||
case CursorPaintType::Outline: {
|
||||
// DrawRectangle in straddles physical pixels in an attempt to draw a line
|
||||
// between them. To avoid this, bump the rectangle around by half the stroke width.
|
||||
rect.top += 0.5f;
|
||||
@@ -1576,6 +1445,7 @@ CATCH_RETURN()
|
||||
// Return Value:
|
||||
// - S_OK or relevant DirectX error
|
||||
[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_IF_FAILED(_GetProposedFont(pfiFontInfoDesired,
|
||||
fiFontInfo,
|
||||
@@ -1584,22 +1454,16 @@ CATCH_RETURN()
|
||||
_dwriteTextAnalyzer,
|
||||
_dwriteFontFace));
|
||||
|
||||
try
|
||||
{
|
||||
const auto size = fiFontInfo.GetSize();
|
||||
|
||||
_glyphCell.cx = size.X;
|
||||
_glyphCell.cy = size.Y;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
_glyphCell = fiFontInfo.GetSize();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept
|
||||
{
|
||||
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.cx);
|
||||
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.cy);
|
||||
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.width());
|
||||
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.height());
|
||||
|
||||
return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars });
|
||||
}
|
||||
@@ -1688,29 +1552,7 @@ float DxEngine::GetScaling() const noexcept
|
||||
// - Rectangle describing dirty area in characters.
|
||||
[[nodiscard]] std::vector<til::rectangle> DxEngine::GetDirtyArea()
|
||||
{
|
||||
SMALL_RECT r;
|
||||
r.Top = gsl::narrow<SHORT>(floor(_invalidRect.top / _glyphCell.cy));
|
||||
r.Left = gsl::narrow<SHORT>(floor(_invalidRect.left / _glyphCell.cx));
|
||||
r.Bottom = gsl::narrow<SHORT>(floor(_invalidRect.bottom / _glyphCell.cy));
|
||||
r.Right = gsl::narrow<SHORT>(floor(_invalidRect.right / _glyphCell.cx));
|
||||
|
||||
// Exclusive to inclusive
|
||||
r.Bottom--;
|
||||
r.Right--;
|
||||
|
||||
return { r };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets COORD packed with shorts of each glyph (character) cell's
|
||||
// height and width.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - Nearest integer short x and y values for each cell.
|
||||
[[nodiscard]] COORD DxEngine::_GetFontSize() const noexcept
|
||||
{
|
||||
return { gsl::narrow<SHORT>(_glyphCell.cx), gsl::narrow<SHORT>(_glyphCell.cy) };
|
||||
return _invalidMap.runs();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1720,10 +1562,12 @@ float DxEngine::GetScaling() const noexcept
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::GetFontSize(_Out_ COORD* const pFontSize) noexcept
|
||||
try
|
||||
{
|
||||
*pFontSize = _GetFontSize();
|
||||
*pFontSize = _glyphCell;
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Currently unused by this renderer.
|
||||
@@ -1733,30 +1577,28 @@ float DxEngine::GetScaling() const noexcept
|
||||
// Return Value:
|
||||
// - S_OK or relevant DirectWrite error.
|
||||
[[nodiscard]] HRESULT DxEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pResult);
|
||||
|
||||
try
|
||||
{
|
||||
const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout.
|
||||
const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout.
|
||||
|
||||
// Create the text layout
|
||||
CustomTextLayout layout(_dwriteFactory.Get(),
|
||||
_dwriteTextAnalyzer.Get(),
|
||||
_dwriteTextFormat.Get(),
|
||||
_dwriteFontFace.Get(),
|
||||
{ &cluster, 1 },
|
||||
_glyphCell.cx);
|
||||
// Create the text layout
|
||||
CustomTextLayout layout(_dwriteFactory.Get(),
|
||||
_dwriteTextAnalyzer.Get(),
|
||||
_dwriteTextFormat.Get(),
|
||||
_dwriteFontFace.Get(),
|
||||
{ &cluster, 1 },
|
||||
_glyphCell.width());
|
||||
|
||||
UINT32 columns = 0;
|
||||
RETURN_IF_FAILED(layout.GetColumns(&columns));
|
||||
UINT32 columns = 0;
|
||||
RETURN_IF_FAILED(layout.GetColumns(&columns));
|
||||
|
||||
*pResult = columns != 1;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
*pResult = columns != 1;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Method Description:
|
||||
// - Updates the window's title string.
|
||||
@@ -2130,12 +1972,10 @@ float DxEngine::GetScaling() const noexcept
|
||||
|
||||
switch (_chainMode)
|
||||
{
|
||||
case SwapChainMode::ForHwnd:
|
||||
{
|
||||
case SwapChainMode::ForHwnd: {
|
||||
return D2D1::ColorF(rgb);
|
||||
}
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
case SwapChainMode::ForComposition: {
|
||||
// Get the A value we've snuck into the highest byte
|
||||
const BYTE a = ((color >> 24) & 0xFF);
|
||||
const float aFloat = a / 255.0f;
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Microsoft::Console::Render
|
||||
SwapChainMode _chainMode;
|
||||
|
||||
HWND _hwndTarget;
|
||||
SIZE _sizeTarget;
|
||||
til::size _sizeTarget;
|
||||
int _dpi;
|
||||
float _scale;
|
||||
|
||||
@@ -130,8 +130,8 @@ namespace Microsoft::Console::Render
|
||||
bool _isEnabled;
|
||||
bool _isPainting;
|
||||
|
||||
SIZE _displaySizePixels;
|
||||
SIZE _glyphCell;
|
||||
til::size _displaySizePixels;
|
||||
til::size _glyphCell;
|
||||
|
||||
D2D1_COLOR_F _defaultForegroundColor;
|
||||
D2D1_COLOR_F _defaultBackgroundColor;
|
||||
@@ -140,19 +140,11 @@ namespace Microsoft::Console::Render
|
||||
D2D1_COLOR_F _backgroundColor;
|
||||
D2D1_COLOR_F _selectionBackground;
|
||||
|
||||
[[nodiscard]] RECT _GetDisplayRect() const noexcept;
|
||||
|
||||
bool _isInvalidUsed;
|
||||
RECT _invalidRect;
|
||||
SIZE _invalidScroll;
|
||||
|
||||
void _InvalidOr(SMALL_RECT sr) noexcept;
|
||||
void _InvalidOr(RECT rc) noexcept;
|
||||
|
||||
void _InvalidOffset(POINT pt);
|
||||
til::bitmap _invalidMap;
|
||||
til::point _invalidScroll;
|
||||
|
||||
bool _presentReady;
|
||||
RECT _presentDirty;
|
||||
std::vector<RECT> _presentDirty;
|
||||
RECT _presentScroll;
|
||||
POINT _presentOffset;
|
||||
DXGI_PRESENT_PARAMETERS _presentParams;
|
||||
@@ -244,9 +236,7 @@ namespace Microsoft::Console::Render
|
||||
::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1>& textAnalyzer,
|
||||
::Microsoft::WRL::ComPtr<IDWriteFontFace1>& fontFace) const noexcept;
|
||||
|
||||
[[nodiscard]] COORD _GetFontSize() const noexcept;
|
||||
|
||||
[[nodiscard]] SIZE _GetClientSize() const noexcept;
|
||||
[[nodiscard]] til::size _GetClientSize() const noexcept;
|
||||
|
||||
[[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept;
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
#pragma once
|
||||
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#define BLOCK_TIL // We want to include it later, after DX.
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <winmeta.h>
|
||||
|
||||
#include "..\host\conddkrefs.h"
|
||||
#include <condrv.h>
|
||||
@@ -34,4 +36,7 @@
|
||||
#include <dwrite_2.h>
|
||||
#include <dwrite_3.h>
|
||||
|
||||
// Re-include TIL at the bottom to gain DX superpowers.
|
||||
#include "til.h"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -343,19 +343,20 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT XtermEngine::ScrollFrame() noexcept
|
||||
try
|
||||
{
|
||||
if (_scrollDelta.X != 0)
|
||||
if (_scrollDelta.x() != 0)
|
||||
{
|
||||
// No easy way to shift left-right. Everything needs repainting.
|
||||
return InvalidateAll();
|
||||
}
|
||||
if (_scrollDelta.Y == 0)
|
||||
if (_scrollDelta.y() == 0)
|
||||
{
|
||||
// There's nothing to do here. Do nothing.
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const short dy = _scrollDelta.Y;
|
||||
const short dy = _scrollDelta.y<SHORT>();
|
||||
const short absDy = static_cast<short>(abs(dy));
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
@@ -391,6 +392,7 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
|
||||
return hr;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies us that the console is attempting to scroll the existing screen
|
||||
@@ -402,25 +404,23 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for safemath failure
|
||||
[[nodiscard]] HRESULT XtermEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
|
||||
try
|
||||
{
|
||||
const short dx = pcoordDelta->X;
|
||||
const short dy = pcoordDelta->Y;
|
||||
const til::point delta{ *pcoordDelta };
|
||||
|
||||
if (dx != 0 || dy != 0)
|
||||
if (delta != til::point{ 0, 0 })
|
||||
{
|
||||
_trace.TraceInvalidateScroll(delta);
|
||||
|
||||
// Scroll the current offset and invalidate the revealed area
|
||||
_invalidMap.translate(til::point(*pcoordDelta), true);
|
||||
_invalidMap.translate(delta, true);
|
||||
|
||||
COORD invalidScrollNew;
|
||||
RETURN_IF_FAILED(ShortAdd(_scrollDelta.X, dx, &invalidScrollNew.X));
|
||||
RETURN_IF_FAILED(ShortAdd(_scrollDelta.Y, dy, &invalidScrollNew.Y));
|
||||
|
||||
// Store if safemath succeeded
|
||||
_scrollDelta = invalidScrollNew;
|
||||
_scrollDelta += delta;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Draws one line of the buffer to the screen. Writes the characters to the
|
||||
|
||||
@@ -121,6 +121,8 @@ CATCH_RETURN();
|
||||
_circled = true;
|
||||
}
|
||||
|
||||
_trace.TraceTriggerCircling(*pForcePaint);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ void VtEngine::_OrRect(_Inout_ SMALL_RECT* const pRectExisting, const SMALL_RECT
|
||||
// - true iff only the next character is invalid
|
||||
bool VtEngine::_WillWriteSingleChar() const
|
||||
{
|
||||
// If there is scroll delta, return false.
|
||||
if (til::point{ 0, 0 } != til::point{ _scrollDelta })
|
||||
// If there is no scroll delta, return false.
|
||||
if (til::point{ 0, 0 } != _scrollDelta)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ using namespace Microsoft::Console::Types;
|
||||
|
||||
// If there's nothing to do, quick return
|
||||
bool somethingToDo = _invalidMap.any() ||
|
||||
(_scrollDelta.X != 0 || _scrollDelta.Y != 0) ||
|
||||
_scrollDelta != til::point{ 0, 0 } ||
|
||||
_cursorMoved ||
|
||||
_titleChanged;
|
||||
|
||||
@@ -52,7 +52,7 @@ using namespace Microsoft::Console::Types;
|
||||
|
||||
_invalidMap.reset_all();
|
||||
|
||||
_scrollDelta = { 0 };
|
||||
_scrollDelta = { 0, 0 };
|
||||
_clearedAllThisFrame = false;
|
||||
_cursorMoved = false;
|
||||
_firstPaint = false;
|
||||
|
||||
@@ -38,7 +38,7 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
||||
_invalidMap(initialViewport.Dimensions()),
|
||||
_lastRealCursor({ 0 }),
|
||||
_lastText({ 0 }),
|
||||
_scrollDelta({ 0 }),
|
||||
_scrollDelta({ 0, 0 }),
|
||||
_quickReturn(false),
|
||||
_clearedAllThisFrame(false),
|
||||
_cursorMoved(false),
|
||||
|
||||
@@ -127,6 +127,23 @@ void RenderTracing::TraceTriggerCircling(const bool newFrame) const
|
||||
#endif UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceInvalidateScroll(const til::point scroll) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
|
||||
{
|
||||
const auto scrollDeltaStr = scroll.to_string();
|
||||
const auto scrollDelta = scrollDeltaStr.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceInvalidateScroll",
|
||||
TraceLoggingWideString(scrollDelta),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(scroll);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RenderTracing::TraceStartPaint(const bool quickReturn,
|
||||
const til::bitmap invalidMap,
|
||||
const til::rectangle lastViewport,
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void TracePaintCursor(const til::point coordCursor) const;
|
||||
void TraceInvalidateAll(const til::rectangle view) const;
|
||||
void TraceTriggerCircling(const bool newFrame) const;
|
||||
void TraceInvalidateScroll(const til::point scroll) const;
|
||||
void TraceStartPaint(const bool quickReturn,
|
||||
const til::bitmap invalidMap,
|
||||
const til::rectangle lastViewport,
|
||||
|
||||
@@ -14,3 +14,7 @@ TEST_CODE = 1
|
||||
# -------------------------------------
|
||||
|
||||
C_DEFINES = $(C_DEFINES) -DINLINE_TEST_METHOD_MARKUP -DUNIT_TESTING
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
$(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
COORD _lastRealCursor;
|
||||
COORD _lastText;
|
||||
COORD _scrollDelta;
|
||||
til::point _scrollDelta;
|
||||
|
||||
bool _quickReturn;
|
||||
bool _clearedAllThisFrame;
|
||||
|
||||
@@ -50,6 +50,7 @@ SOURCES = \
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
$(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \
|
||||
|
||||
TARGETLIBS = \
|
||||
$(TARGETLIBS) \
|
||||
|
||||
127
src/til/ut_til/MathTests.cpp
Normal file
127
src/til/ut_til/MathTests.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "til/math.h"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
class MathTests
|
||||
{
|
||||
TEST_CLASS(MathTests);
|
||||
|
||||
template<typename TG, typename TX = ptrdiff_t>
|
||||
struct TestCase
|
||||
{
|
||||
TG given;
|
||||
TX expected;
|
||||
};
|
||||
|
||||
template<class TilMath, typename TG, typename TX, int N>
|
||||
static void _RunCases(TilMath, const std::array<TestCase<TG, TX>, N>& cases)
|
||||
{
|
||||
for (const auto& tc : cases)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(tc.expected, TilMath::template cast<decltype(tc.expected)>(tc.given));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(Truncating)
|
||||
{
|
||||
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
|
||||
TestCase<long double, ptrdiff_t>{ 1., 1 },
|
||||
{ 1.9, 1 },
|
||||
{ -7.1, -7 },
|
||||
{ -8.5, -8 },
|
||||
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
|
||||
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
|
||||
{ INFINITY, PTRDIFF_MAX },
|
||||
{ -INFINITY, PTRDIFF_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::truncating, cases);
|
||||
|
||||
const auto fn = []() {
|
||||
const auto v = til::math::details::truncating_t::cast<ptrdiff_t>(NAN);
|
||||
};
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
TEST_METHOD(Ceiling)
|
||||
{
|
||||
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
|
||||
TestCase<long double, ptrdiff_t>{ 1., 1 },
|
||||
{ 1.9, 2 },
|
||||
{ -7.1, -7 },
|
||||
{ -8.5, -8 },
|
||||
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
|
||||
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
|
||||
{ INFINITY, PTRDIFF_MAX },
|
||||
{ -INFINITY, PTRDIFF_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::ceiling, cases);
|
||||
|
||||
const auto fn = []() {
|
||||
const auto v = til::math::details::ceiling_t::cast<ptrdiff_t>(NAN);
|
||||
};
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
TEST_METHOD(Flooring)
|
||||
{
|
||||
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
|
||||
TestCase<long double, ptrdiff_t>{ 1., 1 },
|
||||
{ 1.9, 1 },
|
||||
{ -7.1, -8 },
|
||||
{ -8.5, -9 },
|
||||
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
|
||||
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
|
||||
{ INFINITY, PTRDIFF_MAX },
|
||||
{ -INFINITY, PTRDIFF_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::flooring, cases);
|
||||
|
||||
const auto fn = []() {
|
||||
const auto v = til::math::details::flooring_t::cast<ptrdiff_t>(NAN);
|
||||
};
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
TEST_METHOD(Rounding)
|
||||
{
|
||||
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
|
||||
TestCase<long double, ptrdiff_t>{ 1., 1 },
|
||||
{ 1.9, 2 },
|
||||
{ -7.1, -7 },
|
||||
{ -8.5, -9 },
|
||||
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
|
||||
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
|
||||
{ INFINITY, PTRDIFF_MAX },
|
||||
{ -INFINITY, PTRDIFF_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::rounding, cases);
|
||||
|
||||
const auto fn = []() {
|
||||
const auto v = til::math::details::rounding_t::cast<ptrdiff_t>(NAN);
|
||||
};
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
TEST_METHOD(NormalIntegers)
|
||||
{
|
||||
std::array<TestCase<ptrdiff_t, int>, 4> cases{
|
||||
TestCase<ptrdiff_t, int>{ 1, 1 },
|
||||
{ -1, -1 },
|
||||
{ PTRDIFF_MAX, INT_MAX },
|
||||
{ PTRDIFF_MIN, INT_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::rounding, cases);
|
||||
}
|
||||
};
|
||||
@@ -207,6 +207,50 @@ class PointTests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(AdditionInplace)
|
||||
{
|
||||
Log::Comment(L"0.) Addition of two things that should be in bounds.");
|
||||
{
|
||||
const til::point pt{ 5, 10 };
|
||||
const til::point pt2{ 23, 47 };
|
||||
|
||||
const til::point expected{ pt.x() + pt2.x(), pt.y() + pt2.y() };
|
||||
|
||||
auto actual = pt;
|
||||
actual += pt2;
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Addition results in value that is too large (x).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
|
||||
const til::point pt2{ 1, 1 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt;
|
||||
actual += pt2;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Addition results in value that is too large (y).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
|
||||
const til::point pt2{ 1, 1 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt;
|
||||
actual += pt2;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(Subtraction)
|
||||
{
|
||||
Log::Comment(L"0.) Subtraction of two things that should be in bounds.");
|
||||
@@ -246,6 +290,50 @@ class PointTests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(SubtractionInplace)
|
||||
{
|
||||
Log::Comment(L"0.) Subtraction of two things that should be in bounds.");
|
||||
{
|
||||
const til::point pt{ 5, 10 };
|
||||
const til::point pt2{ 23, 47 };
|
||||
|
||||
const til::point expected{ pt.x() - pt2.x(), pt.y() - pt2.y() };
|
||||
|
||||
auto actual = pt;
|
||||
actual -= pt2;
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Subtraction results in value that is too small (x).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
|
||||
const til::point pt2{ -2, -2 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt2;
|
||||
actual -= pt;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Subtraction results in value that is too small (y).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
|
||||
const til::point pt2{ -2, -2 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt2;
|
||||
actual -= pt;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(Multiplication)
|
||||
{
|
||||
Log::Comment(L"0.) Multiplication of two things that should be in bounds.");
|
||||
@@ -285,6 +373,50 @@ class PointTests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(MultiplicationInplace)
|
||||
{
|
||||
Log::Comment(L"0.) Multiplication of two things that should be in bounds.");
|
||||
{
|
||||
const til::point pt{ 5, 10 };
|
||||
const til::point pt2{ 23, 47 };
|
||||
|
||||
const til::point expected{ pt.x() * pt2.x(), pt.y() * pt2.y() };
|
||||
|
||||
auto actual = pt;
|
||||
actual *= pt2;
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Multiplication results in value that is too large (x).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
|
||||
const til::point pt2{ 10, 10 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt;
|
||||
actual *= pt2;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Multiplication results in value that is too large (y).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
|
||||
const til::point pt2{ 10, 10 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt;
|
||||
actual *= pt2;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(Division)
|
||||
{
|
||||
Log::Comment(L"0.) Division of two things that should be in bounds.");
|
||||
@@ -311,6 +443,35 @@ class PointTests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(DivisionInplace)
|
||||
{
|
||||
Log::Comment(L"0.) Division of two things that should be in bounds.");
|
||||
{
|
||||
const til::point pt{ 555, 510 };
|
||||
const til::point pt2{ 23, 47 };
|
||||
|
||||
const til::point expected{ pt.x() / pt2.x(), pt.y() / pt2.y() };
|
||||
auto actual = pt;
|
||||
actual /= pt2;
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Division by zero");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
|
||||
const til::point pt2{ 1, 1 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt2;
|
||||
actual /= pt;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(X)
|
||||
{
|
||||
const til::point pt{ 5, 10 };
|
||||
@@ -443,4 +604,101 @@ class PointTests
|
||||
|
||||
// All ptrdiff_ts fit into a float, so there's no exception tests.
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct PointTypeWith_xy
|
||||
{
|
||||
T x, y;
|
||||
};
|
||||
template<typename T>
|
||||
struct PointTypeWith_XY
|
||||
{
|
||||
T X, Y;
|
||||
};
|
||||
TEST_METHOD(CastFromFloatWithMathTypes)
|
||||
{
|
||||
PointTypeWith_xy<float> xyFloatIntegral{ 1.f, 2.f };
|
||||
PointTypeWith_xy<float> xyFloat{ 1.6f, 2.4f };
|
||||
PointTypeWith_XY<double> XYDoubleIntegral{ 3., 4. };
|
||||
PointTypeWith_XY<double> XYDouble{ 3.6, 4.4 };
|
||||
Log::Comment(L"0.) Ceiling");
|
||||
{
|
||||
{
|
||||
til::point converted{ til::math::ceiling, xyFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::ceiling, xyFloat };
|
||||
VERIFY_ARE_EQUAL((til::point{ 2, 3 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::ceiling, XYDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::ceiling, XYDouble };
|
||||
VERIFY_ARE_EQUAL((til::point{ 4, 5 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Flooring");
|
||||
{
|
||||
{
|
||||
til::point converted{ til::math::flooring, xyFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::flooring, xyFloat };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::flooring, XYDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::flooring, XYDouble };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Rounding");
|
||||
{
|
||||
{
|
||||
til::point converted{ til::math::rounding, xyFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::rounding, xyFloat };
|
||||
VERIFY_ARE_EQUAL((til::point{ 2, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::rounding, XYDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::rounding, XYDouble };
|
||||
VERIFY_ARE_EQUAL((til::point{ 4, 4 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"3.) Truncating");
|
||||
{
|
||||
{
|
||||
til::point converted{ til::math::truncating, xyFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::truncating, xyFloat };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::truncating, XYDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::truncating, XYDouble };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -515,4 +515,140 @@ class SizeTests
|
||||
|
||||
// All ptrdiff_ts fit into a float, so there's no exception tests.
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct SizeTypeWith_XY
|
||||
{
|
||||
T X, Y;
|
||||
};
|
||||
template<typename T>
|
||||
struct SizeTypeWith_cxcy
|
||||
{
|
||||
T cx, cy;
|
||||
};
|
||||
template<typename T>
|
||||
struct SizeTypeWith_WidthHeight
|
||||
{
|
||||
T Width, Height;
|
||||
};
|
||||
TEST_METHOD(CastFromFloatWithMathTypes)
|
||||
{
|
||||
SizeTypeWith_XY<float> XYFloatIntegral{ 1.f, 2.f };
|
||||
SizeTypeWith_XY<float> XYFloat{ 1.6f, 2.4f };
|
||||
SizeTypeWith_cxcy<double> cxcyDoubleIntegral{ 3., 4. };
|
||||
SizeTypeWith_cxcy<double> cxcyDouble{ 3.6, 4.4 };
|
||||
SizeTypeWith_WidthHeight<double> WHDoubleIntegral{ 5., 6. };
|
||||
SizeTypeWith_WidthHeight<double> WHDouble{ 5.6, 6.4 };
|
||||
Log::Comment(L"0.) Ceiling");
|
||||
{
|
||||
{
|
||||
til::size converted{ til::math::ceiling, XYFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, XYFloat };
|
||||
VERIFY_ARE_EQUAL((til::size{ 2, 3 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, cxcyDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, cxcyDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 4, 5 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, WHDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, WHDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 6, 7 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Flooring");
|
||||
{
|
||||
{
|
||||
til::size converted{ til::math::flooring, XYFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, XYFloat };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, cxcyDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, cxcyDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, WHDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, WHDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Rounding");
|
||||
{
|
||||
{
|
||||
til::size converted{ til::math::rounding, XYFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, XYFloat };
|
||||
VERIFY_ARE_EQUAL((til::size{ 2, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, cxcyDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, cxcyDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 4, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, WHDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, WHDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 6, 6 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"3.) Truncating");
|
||||
{
|
||||
{
|
||||
til::size converted{ til::math::truncating, XYFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, XYFloat };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, cxcyDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, cxcyDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, WHDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, WHDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ SOURCES = \
|
||||
ColorTests.cpp \
|
||||
OperatorTests.cpp \
|
||||
PointTests.cpp \
|
||||
MathTests.cpp \
|
||||
RectangleTests.cpp \
|
||||
SizeTests.cpp \
|
||||
SomeTests.cpp \
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<ClCompile Include="BitmapTests.cpp" />
|
||||
<ClCompile Include="OperatorTests.cpp" />
|
||||
<ClCompile Include="PointTests.cpp" />
|
||||
<ClCompile Include="MathTests.cpp" />
|
||||
<ClCompile Include="RectangleTests.cpp" />
|
||||
<ClCompile Include="SizeTests.cpp" />
|
||||
<ClCompile Include="ColorTests.cpp" />
|
||||
|
||||
@@ -23,6 +23,7 @@ bool gVtInput = false;
|
||||
bool gVtOutput = true;
|
||||
bool gWindowInput = false;
|
||||
bool gUseAltBuffer = false;
|
||||
bool gUseAscii = false;
|
||||
|
||||
bool gExitRequested = false;
|
||||
|
||||
@@ -52,7 +53,7 @@ void useMainBuffer()
|
||||
csi("?1049l");
|
||||
}
|
||||
|
||||
void toPrintableBuffer(char c, char* printBuffer, int* printCch)
|
||||
void toPrintableBufferA(char c, char* printBuffer, int* printCch)
|
||||
{
|
||||
if (c == '\x1b')
|
||||
{
|
||||
@@ -111,13 +112,72 @@ void toPrintableBuffer(char c, char* printBuffer, int* printCch)
|
||||
*printCch = 2;
|
||||
}
|
||||
}
|
||||
void toPrintableBufferW(wchar_t c, wchar_t* printBuffer, int* printCch)
|
||||
{
|
||||
if (c == L'\x1b')
|
||||
{
|
||||
printBuffer[0] = L'^';
|
||||
printBuffer[1] = L'[';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\x03')
|
||||
{
|
||||
printBuffer[0] = L'^';
|
||||
printBuffer[1] = L'C';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\x0')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L'0';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\r')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L'r';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L'n';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\t')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L't';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\b')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L'b';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
printBuffer[0] = (wchar_t)c;
|
||||
printBuffer[1] = L' ';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
}
|
||||
|
||||
void handleKeyEvent(KEY_EVENT_RECORD keyEvent)
|
||||
void handleKeyEventA(KEY_EVENT_RECORD keyEvent)
|
||||
{
|
||||
char printBuffer[3];
|
||||
int printCch = 0;
|
||||
const char c = keyEvent.uChar.AsciiChar;
|
||||
toPrintableBuffer(c, printBuffer, &printCch);
|
||||
toPrintableBufferA(c, printBuffer, &printCch);
|
||||
|
||||
if (!keyEvent.bKeyDown)
|
||||
{
|
||||
@@ -137,13 +197,45 @@ void handleKeyEvent(KEY_EVENT_RECORD keyEvent)
|
||||
// restore colors
|
||||
csi("0m");
|
||||
|
||||
// Die on Ctrl+C
|
||||
// Die on Ctrl+D
|
||||
if (keyEvent.uChar.AsciiChar == CTRL_D)
|
||||
{
|
||||
gExitRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
void handleKeyEventW(KEY_EVENT_RECORD keyEvent)
|
||||
{
|
||||
wchar_t printBuffer[3];
|
||||
int printCch = 0;
|
||||
const wchar_t c = keyEvent.uChar.UnicodeChar;
|
||||
toPrintableBufferW(c, printBuffer, &printCch);
|
||||
|
||||
if (!keyEvent.bKeyDown)
|
||||
{
|
||||
// Print in grey
|
||||
csi("38;5;242m");
|
||||
}
|
||||
|
||||
wprintf(L"Down: %d Repeat: %d KeyCode: 0x%x ScanCode: 0x%x Char: %s (0x%x) KeyState: 0x%x\r\n",
|
||||
keyEvent.bKeyDown,
|
||||
keyEvent.wRepeatCount,
|
||||
keyEvent.wVirtualKeyCode,
|
||||
keyEvent.wVirtualScanCode,
|
||||
printBuffer,
|
||||
keyEvent.uChar.UnicodeChar,
|
||||
keyEvent.dwControlKeyState);
|
||||
|
||||
// restore colors
|
||||
csi("0m");
|
||||
|
||||
// Die on Ctrl+D
|
||||
if (c == CTRL_D)
|
||||
{
|
||||
gExitRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
void handleWindowEvent(WINDOW_BUFFER_SIZE_RECORD windowEvent)
|
||||
{
|
||||
SHORT bufferWidth = windowEvent.dwSize.X;
|
||||
@@ -190,6 +282,7 @@ void usage()
|
||||
wprintf(L"\t-i: enable reading VT input mode.\n");
|
||||
wprintf(L"\t-o: disable VT output.\n");
|
||||
wprintf(L"\t-w: enable reading window events.\n");
|
||||
wprintf(L"\t-a: Use ReadConsoleInputA instead.\n");
|
||||
wprintf(L"\t--alt: run in the alt buffer. Cannot be combined with `-o`\n");
|
||||
wprintf(L"\t-?: print this help message\n");
|
||||
}
|
||||
@@ -201,6 +294,7 @@ int __cdecl wmain(int argc, wchar_t* argv[])
|
||||
gWindowInput = false;
|
||||
gUseAltBuffer = false;
|
||||
gExitRequested = false;
|
||||
gUseAscii = false;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
@@ -226,6 +320,11 @@ int __cdecl wmain(int argc, wchar_t* argv[])
|
||||
gVtOutput = false;
|
||||
wprintf(L"Disabling VT Output\n");
|
||||
}
|
||||
else if (arg.compare(L"-a") == 0)
|
||||
{
|
||||
gUseAscii = true;
|
||||
wprintf(L"Using ReadConsoleInputA\n");
|
||||
}
|
||||
else if (arg.compare(L"-?") == 0)
|
||||
{
|
||||
usage();
|
||||
@@ -288,13 +387,28 @@ int __cdecl wmain(int argc, wchar_t* argv[])
|
||||
{
|
||||
INPUT_RECORD rc;
|
||||
DWORD dwRead = 0;
|
||||
ReadConsoleInputA(g_hIn, &rc, 1, &dwRead);
|
||||
|
||||
if (gUseAscii)
|
||||
{
|
||||
ReadConsoleInputA(g_hIn, &rc, 1, &dwRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadConsoleInputW(g_hIn, &rc, 1, &dwRead);
|
||||
}
|
||||
|
||||
switch (rc.EventType)
|
||||
{
|
||||
case KEY_EVENT:
|
||||
{
|
||||
handleKeyEvent(rc.Event.KeyEvent);
|
||||
if (gUseAscii)
|
||||
{
|
||||
handleKeyEventA(rc.Event.KeyEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleKeyEventW(rc.Event.KeyEvent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
|
||||
@@ -13,14 +13,14 @@ namespace VTApp
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static string CSI = ((char)0x1b)+"[";
|
||||
static string CSI = ((char)0x1b) + "[";
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WindowHeight = 25;
|
||||
Console.BufferHeight = 9000;
|
||||
Console.WindowWidth = 80;
|
||||
Console.BufferWidth = 80;
|
||||
|
||||
|
||||
Console.WriteLine("VT Tester");
|
||||
|
||||
while (true)
|
||||
@@ -271,11 +271,11 @@ namespace VTApp
|
||||
Console.Write("49m");
|
||||
break;
|
||||
case '<':
|
||||
Console.Write('\xD'); // carriage return \r
|
||||
break;
|
||||
Console.Write('\xD'); // carriage return \r
|
||||
break;
|
||||
case '>':
|
||||
Console.Write('\xA'); // line feed/new line \n
|
||||
break;
|
||||
Console.Write('\xA'); // line feed/new line \n
|
||||
break;
|
||||
case '`':
|
||||
Console.Write("z");
|
||||
break;
|
||||
@@ -385,7 +385,7 @@ namespace VTApp
|
||||
for (int j = 0; j < 80; j++)
|
||||
{
|
||||
if (j == 0)
|
||||
Console.Write(i%10);
|
||||
Console.Write(i % 10);
|
||||
else
|
||||
Console.Write("Z");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user