mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-06 14:19:45 +00:00
Compare commits
67 Commits
dev/migrie
...
dev/duhowe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37138fcaaf | ||
|
|
5bdb5e6caa | ||
|
|
d2f0f50651 | ||
|
|
271132edb4 | ||
|
|
7c5b39a041 | ||
|
|
34035509a9 | ||
|
|
a13f6237ff | ||
|
|
97702a1d9d | ||
|
|
d4cdbc9b91 | ||
|
|
596a8155ca | ||
|
|
58ad0a34fc | ||
|
|
8f4c4f4916 | ||
|
|
8133f2856d | ||
|
|
f64660ac2f | ||
|
|
b1a981daa9 | ||
|
|
7d8df11ede | ||
|
|
89fde46a94 | ||
|
|
00af538278 | ||
|
|
227ce8ff20 | ||
|
|
99d9bac51d | ||
|
|
97a2dc6878 | ||
|
|
abf66b2ff8 | ||
|
|
3f5f37d910 | ||
|
|
37e0614554 | ||
|
|
d43a14c63f | ||
|
|
862217b04b | ||
|
|
3a71ead757 | ||
|
|
20e88d3e3e | ||
|
|
3ffaa1714a | ||
|
|
4c16cb278e | ||
|
|
335f69e099 | ||
|
|
cf97a9f772 | ||
|
|
41ade2c57e | ||
|
|
d1f152adcf | ||
|
|
8779249b12 | ||
|
|
10b12ac90c | ||
|
|
6ce2543a94 | ||
|
|
5a5902d580 | ||
|
|
0fefdac414 | ||
|
|
79115e2058 | ||
|
|
9c1331ab2e | ||
|
|
5f2ac4e3e7 | ||
|
|
6e70c4ae07 | ||
|
|
b05a557f48 | ||
|
|
2e246123cf | ||
|
|
fb69aecb19 | ||
|
|
730d6960ab | ||
|
|
7f3bc3cb04 | ||
|
|
8947909121 | ||
|
|
293c36d42f | ||
|
|
cf8f411b8f | ||
|
|
ee6ca81d70 | ||
|
|
b609266d29 | ||
|
|
59e2835736 | ||
|
|
3bf5436122 | ||
|
|
5ea778b2b8 | ||
|
|
cabb83db61 | ||
|
|
769e910e35 | ||
|
|
1d02f82ab7 | ||
|
|
84e30bcd3a | ||
|
|
f68324cd09 | ||
|
|
fca87b2bb5 | ||
|
|
c12835783d | ||
|
|
56bbe86f96 | ||
|
|
e2005ca5d7 | ||
|
|
448684a5f4 | ||
|
|
0d3f85de85 |
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.
|
||||
38
.github/actions/spelling/advice.md
vendored
38
.github/actions/spelling/advice.md
vendored
@@ -1,4 +1,4 @@
|
||||
<!-- markdownlint-disable MD033 MD041 -->
|
||||
<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
|
||||
<details>
|
||||
<summary>
|
||||
:pencil2: Contributor please read this
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
|
||||
|
||||
:warning: The command is written for posix shells. You can copy the contents of each `perl` command excluding the outer `'` marks and dropping any `'"`/`"'` quotation mark pairs into a file and then run `perl file.pl` from the root of the repository to run the code. Alternatively, you can manually insert the items...
|
||||
: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:
|
||||
|
||||
@@ -20,31 +20,29 @@ 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>:clamp: If you see a bunch of garbage</summary>
|
||||
|
||||
If it relates to a ...
|
||||
<details><summary>well-formed pattern</summary>
|
||||
<details><summary>If the flagged items are :exploding_head: false positives</summary>
|
||||
|
||||
See if there's a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it.
|
||||
If items relate to a ...
|
||||
* binary file (or some other file you wouldn't want to check at all).
|
||||
|
||||
If not, try writing one and adding it to a `patterns/{file}.txt`.
|
||||
Please add a file path to the `excludes.txt` file matching the containing 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><summary>binary-ish string</summary>
|
||||
|
||||
Please add a file path to the `excludes.txt` file instead of just accepting the garbage.
|
||||
|
||||
File paths are Perl 5 Regular Expressions - you can [test](
|
||||
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](
|
||||
`^` 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).
|
||||
</details>
|
||||
|
||||
|
||||
* 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>
|
||||
|
||||
49
.github/actions/spelling/allow/allow.txt
vendored
49
.github/actions/spelling/allow/allow.txt
vendored
@@ -1,7 +1,18 @@
|
||||
admins
|
||||
allcolors
|
||||
Apc
|
||||
apc
|
||||
breadcrumb
|
||||
breadcrumbs
|
||||
bsd
|
||||
calt
|
||||
ccmp
|
||||
changelog
|
||||
clickable
|
||||
clig
|
||||
CMMI
|
||||
copyable
|
||||
cybersecurity
|
||||
dalet
|
||||
Dcs
|
||||
dcs
|
||||
@@ -11,49 +22,87 @@ 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
|
||||
|
||||
72
.github/actions/spelling/allow/apis.txt
vendored
72
.github/actions/spelling/allow/apis.txt
vendored
@@ -1,28 +1,44 @@
|
||||
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
|
||||
@@ -35,12 +51,16 @@ fullkbd
|
||||
futex
|
||||
GETDESKWALLPAPER
|
||||
GETHIGHCONTRAST
|
||||
GETMOUSEHOVERTIME
|
||||
Hashtable
|
||||
HIGHCONTRASTON
|
||||
HIGHCONTRASTW
|
||||
hotkeys
|
||||
href
|
||||
hrgn
|
||||
HTCLOSE
|
||||
hwinsta
|
||||
HWINSTA
|
||||
IActivation
|
||||
IApp
|
||||
IAppearance
|
||||
@@ -57,17 +77,22 @@ IDirect
|
||||
IExplorer
|
||||
IFACEMETHOD
|
||||
IFile
|
||||
IGraphics
|
||||
IInheritable
|
||||
IMap
|
||||
IMonarch
|
||||
IObject
|
||||
iosfwd
|
||||
IPackage
|
||||
IPeasant
|
||||
ISetup
|
||||
isspace
|
||||
IStorage
|
||||
istream
|
||||
IStringable
|
||||
ITab
|
||||
ITaskbar
|
||||
itow
|
||||
IUri
|
||||
IVirtual
|
||||
KEYSELECT
|
||||
@@ -76,12 +101,27 @@ 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
|
||||
@@ -91,6 +131,7 @@ NOCHANGEDIR
|
||||
NOPROGRESS
|
||||
NOREDIRECTIONBITMAP
|
||||
NOREPEAT
|
||||
NOTIFYBYPOS
|
||||
NOTIFYICON
|
||||
NOTIFYICONDATA
|
||||
ntprivapi
|
||||
@@ -98,26 +139,36 @@ 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
|
||||
@@ -131,25 +182,44 @@ 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
|
||||
@@ -166,6 +236,7 @@ xlocmes
|
||||
xlocmon
|
||||
xlocnum
|
||||
xloctime
|
||||
XMax
|
||||
xmemory
|
||||
XParse
|
||||
xpath
|
||||
@@ -174,3 +245,4 @@ xstring
|
||||
xtree
|
||||
xutility
|
||||
YIcon
|
||||
YMax
|
||||
|
||||
8
.github/actions/spelling/allow/math.txt
vendored
8
.github/actions/spelling/allow/math.txt
vendored
@@ -1,3 +1,11 @@
|
||||
atan
|
||||
CPrime
|
||||
HBar
|
||||
HPrime
|
||||
isnan
|
||||
LPrime
|
||||
LStep
|
||||
powf
|
||||
RSub
|
||||
sqrtf
|
||||
ULP
|
||||
|
||||
13
.github/actions/spelling/allow/microsoft.txt
vendored
13
.github/actions/spelling/allow/microsoft.txt
vendored
@@ -1,5 +1,6 @@
|
||||
ACLs
|
||||
ADMINS
|
||||
advapi
|
||||
altform
|
||||
altforms
|
||||
appendwttlogging
|
||||
@@ -15,8 +16,10 @@ CPLs
|
||||
cpptools
|
||||
cppvsdbg
|
||||
CPRs
|
||||
cryptbase
|
||||
DACL
|
||||
DACLs
|
||||
defaultlib
|
||||
diffs
|
||||
disposables
|
||||
dotnetfeed
|
||||
@@ -24,15 +27,22 @@ DTDs
|
||||
DWINRT
|
||||
enablewttlogging
|
||||
Intelli
|
||||
IVisual
|
||||
libucrt
|
||||
libucrtd
|
||||
LKG
|
||||
LOCKFILE
|
||||
Lxss
|
||||
mfcribbon
|
||||
microsoft
|
||||
microsoftonline
|
||||
MSAA
|
||||
msixbundle
|
||||
MSVC
|
||||
MSVCP
|
||||
muxc
|
||||
netcore
|
||||
Onefuzz
|
||||
osgvsowi
|
||||
PFILETIME
|
||||
pgc
|
||||
@@ -43,6 +53,7 @@ powershell
|
||||
propkey
|
||||
pscustomobject
|
||||
QWORD
|
||||
regedit
|
||||
robocopy
|
||||
SACLs
|
||||
sdkddkver
|
||||
@@ -56,6 +67,8 @@ systemroot
|
||||
taskkill
|
||||
tasklist
|
||||
tdbuildteamid
|
||||
ucrt
|
||||
ucrtd
|
||||
unvirtualized
|
||||
VCRT
|
||||
vcruntime
|
||||
|
||||
14
.github/actions/spelling/allow/names.txt
vendored
14
.github/actions/spelling/allow/names.txt
vendored
@@ -1,14 +1,18 @@
|
||||
Anup
|
||||
austdi
|
||||
arkthur
|
||||
Ballmer
|
||||
bhoj
|
||||
Bhojwani
|
||||
Bluloco
|
||||
carlos
|
||||
dhowett
|
||||
Diviness
|
||||
dsafa
|
||||
duhowett
|
||||
DXP
|
||||
ekg
|
||||
eryksun
|
||||
ethanschoonover
|
||||
Firefox
|
||||
Gatta
|
||||
@@ -20,6 +24,7 @@ Hernan
|
||||
Howett
|
||||
Illhardt
|
||||
iquilezles
|
||||
italo
|
||||
jantari
|
||||
jerrysh
|
||||
Kaiyu
|
||||
@@ -31,8 +36,11 @@ Kourosh
|
||||
kowalczyk
|
||||
leonmsft
|
||||
Lepilleur
|
||||
lhecker
|
||||
lukesampson
|
||||
Macbook
|
||||
Manandhar
|
||||
masserano
|
||||
mbadolato
|
||||
Mehrain
|
||||
menger
|
||||
@@ -52,6 +60,7 @@ oldnewthing
|
||||
opengl
|
||||
osgwiki
|
||||
pabhojwa
|
||||
panos
|
||||
paulcam
|
||||
pauldotknopf
|
||||
PGP
|
||||
@@ -60,12 +69,17 @@ Rincewind
|
||||
rprichard
|
||||
Schoonover
|
||||
shadertoy
|
||||
Shomnipotence
|
||||
simioni
|
||||
Somuah
|
||||
sonph
|
||||
sonpham
|
||||
stakx
|
||||
talo
|
||||
thereses
|
||||
Walisch
|
||||
WDX
|
||||
Wellons
|
||||
Wirt
|
||||
Wojciech
|
||||
zadjii
|
||||
|
||||
523
.github/actions/spelling/candidate.patterns
vendored
Normal file
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-)/
|
||||
48
.github/actions/spelling/excludes.txt
vendored
48
.github/actions/spelling/excludes.txt
vendored
@@ -1,28 +1,39 @@
|
||||
# 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$
|
||||
(?:^|/)package(?:-lock|)\.json$
|
||||
(?:^|/)sources(?:|\.dep)$
|
||||
SUMS$
|
||||
(?:^|/)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$
|
||||
@@ -30,28 +41,53 @@ SUMS$
|
||||
\.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$
|
||||
@@ -61,12 +97,14 @@ SUMS$
|
||||
^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$
|
||||
@@ -74,6 +112,6 @@ SUMS$
|
||||
^src/tools/texttests/fira\.txt$
|
||||
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
|
||||
^src/types/ut_types/UtilsTests.cpp$
|
||||
^\.github/actions/spelling/
|
||||
^\.gitignore$
|
||||
^\XamlStyler.json$
|
||||
^tools/ReleaseEngineering/ServicingPipeline.ps1$
|
||||
ignore$
|
||||
SUMS$
|
||||
|
||||
8
.github/actions/spelling/expect/alphabet.txt
vendored
8
.github/actions/spelling/expect/alphabet.txt
vendored
@@ -5,26 +5,19 @@ AAAAAABBBBBBCCC
|
||||
AAAAABBBBBBCCC
|
||||
abcd
|
||||
abcd
|
||||
abcde
|
||||
abcdef
|
||||
ABCDEFG
|
||||
ABCDEFGH
|
||||
ABCDEFGHIJ
|
||||
abcdefghijk
|
||||
ABCDEFGHIJKLMNO
|
||||
abcdefghijklmnop
|
||||
ABCDEFGHIJKLMNOPQRST
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
ABCG
|
||||
ABE
|
||||
abf
|
||||
BBBBB
|
||||
BBBBBBBB
|
||||
BBBBBBBBBBBBBBDDDD
|
||||
BBBBBCCC
|
||||
BBBBCCCCC
|
||||
BBGGRR
|
||||
CCE
|
||||
EFG
|
||||
EFGh
|
||||
QQQQQQQQQQABCDEFGHIJ
|
||||
@@ -33,7 +26,6 @@ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
|
||||
qrstuvwxyz
|
||||
qwerty
|
||||
QWERTYUIOP
|
||||
qwertyuiopasdfg
|
||||
YYYYYYYDDDDDDDDDDD
|
||||
ZAAZZ
|
||||
|
||||
936
.github/actions/spelling/expect/expect.txt
vendored
936
.github/actions/spelling/expect/expect.txt
vendored
File diff suppressed because it is too large
Load Diff
12
.github/actions/spelling/expect/web.txt
vendored
12
.github/actions/spelling/expect/web.txt
vendored
@@ -1,16 +1,6 @@
|
||||
http
|
||||
www
|
||||
ecma
|
||||
rapidtables
|
||||
WCAG
|
||||
freedesktop
|
||||
ycombinator
|
||||
robertelder
|
||||
kovidgoyal
|
||||
leonerd
|
||||
fixterms
|
||||
winui
|
||||
appshellintegration
|
||||
cppreference
|
||||
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
|
||||
86
.github/actions/spelling/patterns/patterns.txt
vendored
86
.github/actions/spelling/patterns/patterns.txt
vendored
@@ -1,11 +1,6 @@
|
||||
https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_#\/.]*
|
||||
https://aka\.ms/[-a-zA-Z0-9?&=\/_]*
|
||||
https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf
|
||||
https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]*
|
||||
https://www.w3.org/[-a-zA-Z0-9?&=\/_#]*
|
||||
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
|
||||
https://(?:[a-z-]+\.|)github(?:usercontent|)\.com/[-a-zA-Z0-9?%&=_\/.]*
|
||||
https://www.xfree86.org/[-a-zA-Z0-9?&=\/_#]*
|
||||
# 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
|
||||
@@ -24,3 +19,78 @@ VERIFY_ARE_EQUAL\(L"[^"]+"
|
||||
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
|
||||
|
||||
28
.github/actions/spelling/reject.txt
vendored
28
.github/actions/spelling/reject.txt
vendored
@@ -1,22 +1,12 @@
|
||||
^attache$
|
||||
^attacher$
|
||||
^attachers$
|
||||
^spae$
|
||||
^spaebook$
|
||||
^spaecraft$
|
||||
^spaed$
|
||||
^spaedom$
|
||||
^spaeing$
|
||||
^spaeings$
|
||||
^spae-man$
|
||||
^spaeman$
|
||||
^spaer$
|
||||
^Spaerobee$
|
||||
^spaes$
|
||||
^spaewife$
|
||||
^spaewoman$
|
||||
^spaework$
|
||||
^spaewright$
|
||||
^wether$
|
||||
^wethers$
|
||||
^wetherteg$
|
||||
benefitting
|
||||
occurences?
|
||||
^dependan.*
|
||||
^oer$
|
||||
Sorce
|
||||
^[Ss]pae.*
|
||||
^untill$
|
||||
^untilling$
|
||||
^wether.*
|
||||
|
||||
132
.github/workflows/spelling2.yml
vendored
132
.github/workflows/spelling2.yml
vendored
@@ -1,20 +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:
|
||||
pull_request_target:
|
||||
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: checkout-merge
|
||||
if: "contains(github.event_name, 'pull_request')"
|
||||
uses: actions/checkout@v2
|
||||
- name: check-spelling
|
||||
id: spelling
|
||||
uses: check-spelling/check-spelling@v0.0.21
|
||||
with:
|
||||
ref: refs/pull/${{github.event.pull_request.number}}/merge
|
||||
- name: checkout
|
||||
if: "!contains(github.event_name, 'pull_request')"
|
||||
uses: actions/checkout@v2
|
||||
- uses: check-spelling/check-spelling@v0.0.19
|
||||
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 }}
|
||||
|
||||
@@ -73,6 +73,7 @@ EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.unittest", "src\host\ut_lib\host.unittest.vcxproj", "{06EC74CB-9A12-429C-B551-8562EC954747}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{18D09A24-8240-42D6-8CB6-236EEE820263} = {18D09A24-8240-42D6-8CB6-236EEE820263}
|
||||
{71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF} = {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Unit", "src\host\ut_host\Host.UnitTests.vcxproj", "{531C23E7-4B76-4C08-8AAD-04164CB628C9}"
|
||||
|
||||
@@ -1,48 +1,492 @@
|
||||
# This build should never run as CI or against a pull request.
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
pool:
|
||||
name: Package ES Standard Build
|
||||
|
||||
parameters:
|
||||
- name: branding
|
||||
displayName: "Branding (Build Type)"
|
||||
type: string
|
||||
default: Release
|
||||
values:
|
||||
- Release
|
||||
- Preview
|
||||
- name: buildTerminal
|
||||
displayName: "Build Windows Terminal MSIX"
|
||||
type: boolean
|
||||
default: true
|
||||
- name: buildTerminalVPack
|
||||
displayName: "Build Windows Terminal VPack"
|
||||
type: boolean
|
||||
default: false
|
||||
- name: buildWPF
|
||||
displayName: "Build Terminal WPF Control"
|
||||
type: boolean
|
||||
default: false
|
||||
- name: pgoBuildMode
|
||||
displayName: "PGO Build Mode"
|
||||
type: string
|
||||
default: Optimize
|
||||
values:
|
||||
- Optimize
|
||||
- Instrument
|
||||
- None
|
||||
|
||||
- name: buildConfigurations
|
||||
type: object
|
||||
default:
|
||||
- Release
|
||||
- name: buildPlatforms
|
||||
type: object
|
||||
default:
|
||||
- x64
|
||||
- x86
|
||||
- arm64
|
||||
|
||||
variables:
|
||||
baseYearForVersioning: 2019 # Used by build-console-int
|
||||
versionMajor: 0
|
||||
versionMinor: 1
|
||||
TerminalInternalPackageVersion: "0.0.7"
|
||||
|
||||
# When we move off PackageES for Versioning, we'll need to switch
|
||||
# name to this format. For now, though, we need to use DayOfYear.Rev
|
||||
# to unique our builds, as mandated by PackageES's Setup task.
|
||||
# name: '$(versionMajor).$(versionMinor).$(DayOfYear)$(Rev:r).0'
|
||||
#
|
||||
# Build name/version number above must end with .0 to make the
|
||||
# store publication machinery happy.
|
||||
name: 'Terminal_$(date:yyMM).$(date:dd)$(rev:rrr)'
|
||||
|
||||
# Build Arguments:
|
||||
# WindowsTerminalOfficialBuild=[true,false]
|
||||
# true - this is running on our build agent
|
||||
# false - running locally
|
||||
# WindowsTerminalBranding=[Dev,Preview,Release]
|
||||
# <none> - Development build resources (default)
|
||||
# Preview - Preview build resources
|
||||
# Release - regular build resources
|
||||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
resources:
|
||||
repositories:
|
||||
- repository: self
|
||||
type: git
|
||||
ref: main
|
||||
jobs:
|
||||
- template: ./templates/build-console-audit-job.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
- job: Build
|
||||
strategy:
|
||||
matrix:
|
||||
${{ each config in parameters.buildConfigurations }}:
|
||||
${{ each platform in parameters.buildPlatforms }}:
|
||||
${{ config }}_${{ platform }}:
|
||||
BuildConfiguration: ${{ config }}
|
||||
BuildPlatform: ${{ platform }}
|
||||
displayName: Build
|
||||
cancelTimeoutInMinutes: 1
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: True
|
||||
- task: PkgESSetupBuild@10
|
||||
displayName: Package ES - Setup Build
|
||||
inputs:
|
||||
useDfs: false
|
||||
productName: OpenConsole
|
||||
disableOutputRedirect: true
|
||||
- task: PowerShell@2
|
||||
displayName: Rationalize Build Platform
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: >-
|
||||
$Arch = "$(BuildPlatform)"
|
||||
|
||||
- template: ./templates/build-console-int.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
|
||||
If ($Arch -Eq "x86") { $Arch = "Win32" }
|
||||
|
||||
- template: ./templates/build-console-int.yml
|
||||
parameters:
|
||||
platform: x86
|
||||
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
|
||||
Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}"
|
||||
- task: NuGetToolInstaller@1
|
||||
displayName: Use NuGet 5.10
|
||||
inputs:
|
||||
versionSpec: 5.10
|
||||
- task: NuGetCommand@2
|
||||
displayName: NuGet custom
|
||||
inputs:
|
||||
command: custom
|
||||
selectOrConfig: config
|
||||
nugetConfigPath: NuGet.Config
|
||||
arguments: restore OpenConsole.sln -SolutionDirectory $(Build.SourcesDirectory)
|
||||
- task: UniversalPackages@0
|
||||
displayName: Download terminal-internal Universal Package
|
||||
inputs:
|
||||
feedListDownload: 2b3f8893-a6e8-411f-b197-a9e05576da48
|
||||
packageListDownload: e82d490c-af86-4733-9dc4-07b772033204
|
||||
versionListDownload: $(TerminalInternalPackageVersion)
|
||||
- task: TouchdownBuildTask@1
|
||||
displayName: Download Localization Files
|
||||
inputs:
|
||||
teamId: 7105
|
||||
authId: $(TouchdownAppId)
|
||||
authKey: $(TouchdownAppKey)
|
||||
resourceFilePath: >-
|
||||
src\cascadia\TerminalApp\Resources\en-US\Resources.resw
|
||||
|
||||
- template: ./templates/build-console-int.yml
|
||||
parameters:
|
||||
platform: arm64
|
||||
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
|
||||
src\cascadia\TerminalControl\Resources\en-US\Resources.resw
|
||||
|
||||
- template: ./templates/check-formatting.yml
|
||||
src\cascadia\TerminalConnection\Resources\en-US\Resources.resw
|
||||
|
||||
- template: ./templates/release-sign-and-bundle.yml
|
||||
src\cascadia\TerminalSettingsModel\Resources\en-US\Resources.resw
|
||||
|
||||
src\cascadia\TerminalSettingsEditor\Resources\en-US\Resources.resw
|
||||
|
||||
src\cascadia\WindowsTerminalUniversal\Resources\en-US\Resources.resw
|
||||
|
||||
src\cascadia\CascadiaPackage\Resources\en-US\Resources.resw
|
||||
appendRelativeDir: true
|
||||
localizationTarget: false
|
||||
pseudoSetting: Included
|
||||
- task: PowerShell@2
|
||||
displayName: Move Loc files one level up
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: >-
|
||||
$Files = Get-ChildItem . -R -Filter 'Resources.resw' | ? FullName -Like '*en-US\*\Resources.resw'
|
||||
|
||||
$Files | % { Move-Item -Verbose $_.Directory $_.Directory.Parent.Parent -EA:Ignore }
|
||||
pwsh: true
|
||||
- task: PowerShell@2
|
||||
displayName: Generate NOTICE.html from NOTICE.md
|
||||
inputs:
|
||||
filePath: .\build\scripts\Generate-ThirdPartyNotices.ps1
|
||||
arguments: -MarkdownNoticePath .\NOTICE.md -OutputPath .\src\cascadia\CascadiaPackage\NOTICE.html
|
||||
pwsh: true
|
||||
- ${{ if eq(parameters.pgoBuildMode, 'Optimize') }}:
|
||||
- task: PowerShell@2
|
||||
displayName: Restore PGO Database
|
||||
inputs:
|
||||
filePath: tools/PGODatabase/restore-pgodb.ps1
|
||||
workingDirectory: $(Build.SourcesDirectory)\tools\PGODatabase
|
||||
- ${{ if eq(parameters.buildTerminal, true) }}:
|
||||
- task: VSBuild@1
|
||||
displayName: Build solution **\OpenConsole.sln
|
||||
inputs:
|
||||
solution: '**\OpenConsole.sln'
|
||||
vsVersion: 16.0
|
||||
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }} /t:Terminal\CascadiaPackage;Terminal\WindowsTerminalUniversal /p:WindowsTerminalReleaseBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
|
||||
platform: $(BuildPlatform)
|
||||
configuration: $(BuildConfiguration)
|
||||
clean: true
|
||||
maximumCpuCount: true
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Artifact: binlog'
|
||||
condition: failed()
|
||||
continueOnError: True
|
||||
inputs:
|
||||
PathtoPublish: $(Build.SourcesDirectory)\msbuild.binlog
|
||||
ArtifactName: binlog-$(BuildPlatform)
|
||||
- ${{ if eq(parameters.pgoBuildMode, 'Optimize') }}:
|
||||
- task: PowerShell@2
|
||||
displayName: Validate binaries are optimized
|
||||
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: >-
|
||||
$Binaries = 'OpenConsole.exe', 'WindowsTerminal.exe', 'TerminalApp.dll', 'TerminalConnection.dll', 'Microsoft.Terminal.Control.dll', 'Microsoft.Terminal.Remoting.dll', 'Microsoft.Terminal.Settings.Editor.dll', 'Microsoft.Terminal.Settings.Model.dll'
|
||||
|
||||
foreach ($BinFile in $Binaries) {
|
||||
|
||||
& "$(Build.SourcesDirectory)\tools\PGODatabase\verify-pgo.ps1" "$(Build.SourcesDirectory)/src/cascadia/CascadiaPackage/bin/$(BuildPlatform)/$(BuildConfiguration)/$BinFile"
|
||||
|
||||
}
|
||||
- task: PowerShell@2
|
||||
displayName: Check MSIX for common regressions
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: >-
|
||||
$Package = Get-ChildItem -Recurse -Filter "CascadiaPackage_*.msix"
|
||||
|
||||
.\build\scripts\Test-WindowsTerminalPackage.ps1 -Verbose -Path $Package.FullName
|
||||
pwsh: true
|
||||
- ${{ if eq(parameters.buildWPF, true) }}:
|
||||
- task: VSBuild@1
|
||||
displayName: Build solution **\OpenConsole.sln for PublicTerminalCore
|
||||
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
|
||||
inputs:
|
||||
solution: '**\OpenConsole.sln'
|
||||
vsVersion: 16.0
|
||||
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }} /p:WindowsTerminalReleaseBuild=true /t:Terminal\wpf\PublicTerminalCore
|
||||
platform: $(BuildPlatform)
|
||||
configuration: $(BuildConfiguration)
|
||||
- task: PowerShell@2
|
||||
displayName: Source Index PDBs
|
||||
inputs:
|
||||
filePath: build\scripts\Index-Pdbs.ps1
|
||||
arguments: -SearchDir '$(Build.SourcesDirectory)' -SourceRoot '$(Build.SourcesDirectory)' -recursive -Verbose -CommitId $(Build.SourceVersion)
|
||||
errorActionPreference: silentlyContinue
|
||||
- task: ComponentGovernanceComponentDetection@0
|
||||
displayName: Component Detection
|
||||
- task: PowerShell@2
|
||||
displayName: Run Unit Tests
|
||||
condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
|
||||
enabled: False
|
||||
inputs:
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
|
||||
- task: PowerShell@2
|
||||
displayName: Run Feature Tests
|
||||
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
|
||||
enabled: False
|
||||
inputs:
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
|
||||
- ${{ if eq(parameters.buildTerminal, true) }}:
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy *.appx/*.msix to Artifacts
|
||||
inputs:
|
||||
Contents: >-
|
||||
**/*.appx
|
||||
|
||||
**/*.msix
|
||||
|
||||
**/*.appxsym
|
||||
|
||||
!**/Microsoft.VCLibs*.appx
|
||||
TargetFolder: $(Build.ArtifactStagingDirectory)/appx
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Publish Artifact (appx)
|
||||
inputs:
|
||||
PathtoPublish: $(Build.ArtifactStagingDirectory)/appx
|
||||
ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)
|
||||
- ${{ if eq(parameters.buildWPF, true) }}:
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy PublicTerminalCore.dll to Artifacts
|
||||
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
|
||||
inputs:
|
||||
Contents: >-
|
||||
**/PublicTerminalCore.dll
|
||||
|
||||
**/api-ms-win-core-synch-l1-2-0.dll
|
||||
TargetFolder: $(Build.ArtifactStagingDirectory)/wpf
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Publish Artifact (PublicTerminalCore)
|
||||
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
|
||||
inputs:
|
||||
PathtoPublish: $(Build.ArtifactStagingDirectory)/wpf
|
||||
ArtifactName: wpf-dll-$(BuildPlatform)-$(BuildConfiguration)
|
||||
- task: PublishSymbols@2
|
||||
displayName: Publish symbols path
|
||||
continueOnError: True
|
||||
inputs:
|
||||
SearchPattern: '**/*.pdb'
|
||||
IndexSources: false
|
||||
SymbolServerType: TeamServices
|
||||
|
||||
- ${{ if eq(parameters.buildTerminal, true) }}:
|
||||
- job: BundleAndSign
|
||||
displayName: Create and sign AppX/MSIX bundles
|
||||
dependsOn: Build
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: True
|
||||
- task: PkgESSetupBuild@10
|
||||
displayName: Package ES - Setup Build
|
||||
inputs:
|
||||
useDfs: false
|
||||
productName: OpenConsole
|
||||
disableOutputRedirect: true
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download Artifacts (*.appx, *.msix)
|
||||
inputs:
|
||||
downloadType: specific
|
||||
itemPattern: >-
|
||||
**/*.msix
|
||||
|
||||
**/*.appx
|
||||
extractTars: false
|
||||
- task: PowerShell@2
|
||||
displayName: Create WindowsTerminal*.msixbundle
|
||||
inputs:
|
||||
filePath: build\scripts\Create-AppxBundle.ps1
|
||||
arguments: -InputPath "$(System.ArtifactsDirectory)" -ProjectName CascadiaPackage -BundleVersion 0.0.0.0 -OutputPath "$(System.ArtifactsDirectory)\Microsoft.WindowsTerminal_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
|
||||
- task: PowerShell@2
|
||||
displayName: Create WindowsTerminalUniversal*.msixbundle
|
||||
inputs:
|
||||
filePath: build\scripts\Create-AppxBundle.ps1
|
||||
arguments: -InputPath "$(System.ArtifactsDirectory)" -ProjectName WindowsTerminalUniversal -BundleVersion $(XES_APPXMANIFESTVERSION) -OutputPath "$(System.ArtifactsDirectory)\Microsoft.WindowsTerminalUniversal_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
|
||||
- task: EsrpCodeSigning@1
|
||||
displayName: Submit *.msixbundle to ESRP for code signing
|
||||
inputs:
|
||||
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
|
||||
FolderPath: $(System.ArtifactsDirectory)
|
||||
Pattern: Microsoft.WindowsTerminal*.msixbundle
|
||||
UseMinimatch: true
|
||||
signConfigType: inlineSignParams
|
||||
inlineOperation: >-
|
||||
[
|
||||
{
|
||||
"KeyCode": "Dynamic",
|
||||
"CertTemplateName": "WINMSAPP1ST",
|
||||
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||
"OperationCode": "SigntoolSign",
|
||||
"Parameters": {
|
||||
"OpusName": "Microsoft",
|
||||
"OpusInfo": "http://www.microsoft.com",
|
||||
"FileDigest": "/fd \"SHA256\"",
|
||||
"TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
|
||||
},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "Dynamic",
|
||||
"CertTemplateName": "WINMSAPP1ST",
|
||||
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||
"OperationCode": "SigntoolVerify",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Artifact: appxbundle-signed'
|
||||
inputs:
|
||||
PathtoPublish: $(System.ArtifactsDirectory)
|
||||
ArtifactName: appxbundle-signed
|
||||
|
||||
- ${{ if eq(parameters.buildWPF, true) }}:
|
||||
- job: PackageAndSignWPF
|
||||
strategy:
|
||||
matrix:
|
||||
${{ each config in parameters.buildConfigurations }}:
|
||||
${{ config }}:
|
||||
BuildConfiguration: ${{ config }}
|
||||
displayName: Create NuGet Package (WPF Terminal Control)
|
||||
dependsOn: Build
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: True
|
||||
- task: PkgESSetupBuild@10
|
||||
displayName: Package ES - Setup Build
|
||||
inputs:
|
||||
useDfs: false
|
||||
productName: OpenConsole
|
||||
disableOutputRedirect: true
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download x86 PublicTerminalCore
|
||||
inputs:
|
||||
artifactName: wpf-dll-x86-$(BuildConfiguration)
|
||||
itemPattern: '**/*.dll'
|
||||
downloadPath: bin\Win32\$(BuildConfiguration)\
|
||||
extractTars: false
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download x64 PublicTerminalCore
|
||||
inputs:
|
||||
artifactName: wpf-dll-x64-$(BuildConfiguration)
|
||||
itemPattern: '**/*.dll'
|
||||
downloadPath: bin\x64\$(BuildConfiguration)\
|
||||
extractTars: false
|
||||
- task: PowerShell@2
|
||||
displayName: Move downloaded artifacts up a level
|
||||
inputs:
|
||||
targetType: inline
|
||||
# Find all artifact files and move them up a directory. Ugh.
|
||||
script: >-
|
||||
Get-ChildItem bin -Recurse -Directory -Filter wpf-dll-* | % {
|
||||
$_ | Get-ChildItem -Recurse -File | % {
|
||||
Move-Item -Verbose $_.FullName $_.Directory.Parent.FullName
|
||||
}
|
||||
}
|
||||
- task: NuGetToolInstaller@1
|
||||
displayName: Use NuGet 5.10.0
|
||||
inputs:
|
||||
versionSpec: 5.10.0
|
||||
- task: NuGetCommand@2
|
||||
displayName: NuGet restore copy
|
||||
inputs:
|
||||
selectOrConfig: config
|
||||
nugetConfigPath: NuGet.Config
|
||||
- task: VSBuild@1
|
||||
displayName: Build solution **\OpenConsole.sln for WPF Control
|
||||
inputs:
|
||||
solution: '**\OpenConsole.sln'
|
||||
vsVersion: 16.0
|
||||
msbuildArgs: /p:WindowsTerminalReleaseBuild=$(UseReleaseBranding);Version=$(XES_PACKAGEVERSIONNUMBER) /t:Pack
|
||||
platform: Any CPU
|
||||
configuration: $(BuildConfiguration)
|
||||
maximumCpuCount: true
|
||||
- task: PublishSymbols@2
|
||||
displayName: Publish symbols path
|
||||
continueOnError: True
|
||||
inputs:
|
||||
SearchPattern: '**/*.pdb'
|
||||
IndexSources: false
|
||||
SymbolServerType: TeamServices
|
||||
SymbolsArtifactName: Symbols_WPF_$(BuildConfiguration)
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy *.nupkg to Artifacts
|
||||
inputs:
|
||||
Contents: '**/*Wpf*.nupkg'
|
||||
TargetFolder: $(Build.ArtifactStagingDirectory)/nupkg
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
- task: EsrpCodeSigning@1
|
||||
displayName: Submit *.nupkg to ESRP for code signing
|
||||
inputs:
|
||||
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
|
||||
FolderPath: $(Build.ArtifactStagingDirectory)/nupkg
|
||||
Pattern: '*.nupkg'
|
||||
UseMinimatch: true
|
||||
signConfigType: inlineSignParams
|
||||
inlineOperation: >-
|
||||
[
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetSign",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetVerify",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Publish Artifact (nupkg)
|
||||
inputs:
|
||||
PathtoPublish: $(Build.ArtifactStagingDirectory)\nupkg
|
||||
ArtifactName: wpf-nupkg-$(BuildConfiguration)
|
||||
|
||||
- ${{ if eq(parameters.buildTerminalVPack, true) }}:
|
||||
- job: VPack
|
||||
displayName: Create Windows vPack
|
||||
dependsOn: BundleAndSign
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
- task: PkgESSetupBuild@12
|
||||
displayName: Package ES - Setup Build
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download Build Artifacts
|
||||
inputs:
|
||||
artifactName: appxbundle-signed
|
||||
extractTars: false
|
||||
- task: PowerShell@2
|
||||
displayName: Rename and stage packages for vpack
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: >-
|
||||
# Rename to known/fixed name for Windows build system
|
||||
|
||||
Get-ChildItem Microsoft.WindowsTerminal_*.msixbundle | Rename-Item -NewName { 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle' }
|
||||
|
||||
|
||||
# Create vpack directory and place item inside
|
||||
|
||||
mkdir WindowsTerminal.app
|
||||
|
||||
mv Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle .\WindowsTerminal.app\
|
||||
workingDirectory: $(System.ArtifactsDirectory)\appxbundle-signed
|
||||
- task: PkgESVPack@10
|
||||
displayName: 'Package ES - VPack'
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
sourceDirectory: $(System.ArtifactsDirectory)\appxbundle-signed\WindowsTerminal.app
|
||||
description: Windows Terminal pre-install application
|
||||
pushPkgName: WindowsTerminal.app
|
||||
owner: condev
|
||||
...
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
parameters:
|
||||
configuration: 'Release'
|
||||
platform: ''
|
||||
additionalBuildArguments: ''
|
||||
|
||||
jobs:
|
||||
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }}
|
||||
variables:
|
||||
BuildConfiguration: ${{ parameters.configuration }}
|
||||
BuildPlatform: ${{ parameters.platform }}
|
||||
PGOBuildMode: 'Optimize'
|
||||
|
||||
pool:
|
||||
name: Package ES Lab E
|
||||
demands:
|
||||
- msbuild
|
||||
- visualstudio
|
||||
- vstest
|
||||
|
||||
steps:
|
||||
- task: PkgESSetupBuild@10
|
||||
displayName: 'Package ES - Setup Build'
|
||||
inputs:
|
||||
useDfs: false
|
||||
productName: WindowsTerminal
|
||||
disableOutputRedirect: true
|
||||
|
||||
- template: build-console-steps.yml
|
||||
parameters:
|
||||
additionalBuildArguments: "/p:XesUseOneStoreVersioning=true;XesBaseYearForStoreVersion=$(baseYearForVersioning) ${{ parameters.additionalBuildArguments }}"
|
||||
@@ -1,74 +0,0 @@
|
||||
parameters:
|
||||
configuration: 'Release'
|
||||
|
||||
jobs:
|
||||
- job: SignDeploy${{ parameters.configuration }}
|
||||
displayName: Sign and Deploy for ${{ parameters.configuration }}
|
||||
|
||||
dependsOn:
|
||||
- Buildx64AuditMode
|
||||
- Buildx64Release
|
||||
- Buildx86Release
|
||||
- Buildarm64Release
|
||||
- CodeFormatCheck
|
||||
condition: |
|
||||
and
|
||||
(
|
||||
in(dependencies.Buildx64AuditMode.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.Buildx64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.Buildx86Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.Buildarm64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.CodeFormatCheck.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
|
||||
)
|
||||
|
||||
variables:
|
||||
BuildConfiguration: ${{ parameters.configuration }}
|
||||
AppxProjectName: CascadiaPackage
|
||||
AppxBundleName: Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle
|
||||
|
||||
pool:
|
||||
name: Package ES Lab E
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
|
||||
- task: PkgESSetupBuild@10
|
||||
displayName: 'Package ES - Setup Build'
|
||||
inputs:
|
||||
useDfs: false
|
||||
productName: WindowsTerminal
|
||||
disableOutputRedirect: true
|
||||
|
||||
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
|
||||
displayName: 'Component Detection'
|
||||
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download AppX artifacts
|
||||
inputs:
|
||||
artifactName: 'appx-$(BuildConfiguration)'
|
||||
itemPattern: |
|
||||
**/*.appx
|
||||
**/*.msix
|
||||
downloadPath: '$(Build.ArtifactStagingDirectory)\appx'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Create $(AppxBundleName)'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: '.\build\scripts\Create-AppxBundle.ps1'
|
||||
arguments: |
|
||||
-InputPath "$(Build.ArtifactStagingDirectory)\appx" -ProjectName $(AppxProjectName) -BundleVersion 0.0.0.0 -OutputPath "$(Build.ArtifactStagingDirectory)\$(AppxBundleName)"
|
||||
|
||||
- task: PkgESCodeSign@10
|
||||
displayName: 'Package ES - SignConfig.WindowsTerminal.xml'
|
||||
inputs:
|
||||
signConfigXml: 'build\config\SignConfig.WindowsTerminal.xml'
|
||||
inPathRoot: '$(Build.ArtifactStagingDirectory)'
|
||||
outPathRoot: '$(Build.ArtifactStagingDirectory)\signed'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Signed AppX'
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)\signed'
|
||||
ArtifactName: 'appxbundle-signed-$(BuildConfiguration)'
|
||||
@@ -5,7 +5,7 @@
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2021</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>10</VersionMinor>
|
||||
<VersionMinor>11</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"title": "Microsoft's Windows Terminal Settings Profile Schema",
|
||||
"definitions": {
|
||||
"KeyChordSegment": {
|
||||
"pattern": "^(?<modifier>(?<mod1>ctrl|alt|shift|win)(?:\\+(?<mod2>ctrl|alt|shift|win)(?<!\\k<mod1>))?(?:\\+(?<mod3>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>))?(?:\\+(?<mod4>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>|\\k<mod3>))?\\+)?(?<key>[^\\s+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
|
||||
"pattern": "^(?:(?:ctrl|alt|shift|win)\\+)*(?:app|backspace|browser_(?:back|forward|refresh|stop|search|favorites|home)|comma|delete|down|end|enter|esc|escape|home|insert|left|menu|minus|pagedown|pageup|period|pgdn|pgup|plus|right|space|tab|up|f(?:1\\d?|2[0-4]?|[3-9])|numpad\\d|numpad_(?:\\d|add|decimal|divide|minus|multiply|period|plus|subtract)|(?:vk|sc)\\((?:[1-9]|1?\\d{2}|2[0-4]\\d|25[0-5])\\)|[^\\s+])(?:\\+(?:ctrl|alt|shift|win))*$",
|
||||
"type": "string",
|
||||
"description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<keyName>\", where each modifier is optional, separated by + symbols, and keyName is either one of the names listed in the table below, or any single key character. The string should be written in full lowercase.\napp, menu\tMENU key\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)"
|
||||
"description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<KeyName>\", where each modifier is optional. KeyName is either any single key character, an explicit virtual key or scan code in the form vk(nnn) and sc(nnn) respectively, or one of the special names listed at https://docs.microsoft.com/en-us/windows/terminal/customize-settings/actions#accepted-modifiers-and-keys"
|
||||
},
|
||||
"Color": {
|
||||
"default": "#",
|
||||
"pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
|
||||
"pattern": "^#[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{3})?$",
|
||||
"type": "string",
|
||||
"format": "color"
|
||||
},
|
||||
@@ -238,6 +238,7 @@
|
||||
"identifyWindow",
|
||||
"identifyWindows",
|
||||
"moveFocus",
|
||||
"movePane",
|
||||
"moveTab",
|
||||
"newTab",
|
||||
"newWindow",
|
||||
@@ -514,6 +515,23 @@
|
||||
],
|
||||
"required": [ "direction" ]
|
||||
},
|
||||
"MovePaneAction": {
|
||||
"description": "Arguments corresponding to a Move Pane Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "movePane" },
|
||||
"direction": {
|
||||
"$ref": "#/definitions/FocusDirection",
|
||||
"default": "left",
|
||||
"description": "The direction to move the focus pane in, swapping panes. Direction can be 'previous' to swap with the most recently used pane."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [ "direction" ]
|
||||
},
|
||||
"ResizePaneAction": {
|
||||
"description": "Arguments corresponding to a Resize Pane Action",
|
||||
"allOf": [
|
||||
@@ -596,7 +614,7 @@
|
||||
"defaultsFile",
|
||||
"allFiles",
|
||||
"settingsUI"
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -952,6 +970,7 @@
|
||||
{ "$ref": "#/definitions/NewTabAction" },
|
||||
{ "$ref": "#/definitions/SwitchToTabAction" },
|
||||
{ "$ref": "#/definitions/MoveFocusAction" },
|
||||
{ "$ref": "#/definitions/MovePaneAction" },
|
||||
{ "$ref": "#/definitions/ResizePaneAction" },
|
||||
{ "$ref": "#/definitions/SendInputAction" },
|
||||
{ "$ref": "#/definitions/SplitPaneAction" },
|
||||
@@ -1116,6 +1135,10 @@
|
||||
"description": "When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental.input.forceVT": {
|
||||
"description": "Force the terminal to use the legacy input encoding. Certain keys in some applications may stop working when enabling this setting.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"initialCols": {
|
||||
"default": 120,
|
||||
"description": "The number of columns displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "MyPage.h"
|
||||
#include <LibraryResources.h>
|
||||
#include "MyPage.g.cpp"
|
||||
#include "..\..\..\src\cascadia\UnitTests_Control\MockControlSettings.h"
|
||||
#include "MySettings.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
@@ -26,17 +26,24 @@ namespace winrt::SampleApp::implementation
|
||||
|
||||
void MyPage::Create()
|
||||
{
|
||||
TerminalConnection::EchoConnection conn{};
|
||||
auto settings = winrt::make_self<ControlUnitTests::MockControlSettings>();
|
||||
auto settings = winrt::make_self<implementation::MySettings>();
|
||||
|
||||
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
|
||||
winrt::hstring{},
|
||||
L"",
|
||||
nullptr,
|
||||
32,
|
||||
80,
|
||||
winrt::guid()) };
|
||||
|
||||
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
|
||||
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
|
||||
TerminalConnection::ConnectionInformation connectInfo{ myClass, connectionSettings };
|
||||
|
||||
TerminalConnection::ITerminalConnection conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectInfo) };
|
||||
Control::TermControl control{ *settings, conn };
|
||||
|
||||
InProcContent().Children().Append(control);
|
||||
|
||||
// Once the control loads (and not before that), write some text for debugging:
|
||||
control.Initialized([conn](auto&&, auto&&) {
|
||||
conn.WriteInput(L"This TermControl is hosted in-proc...");
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -19,10 +19,15 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Button x:Name="TabRow"
|
||||
Grid.Row="0">
|
||||
"Tabs"
|
||||
</Button>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox x:Name="GuidInput"
|
||||
Width="400"
|
||||
PlaceholderText="{}{guid here}" />
|
||||
<Button Grid.Row="0">
|
||||
Create
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<Grid x:Name="TabContent"
|
||||
Grid.Row="1"
|
||||
|
||||
@@ -4,6 +4,8 @@ Licensed under the MIT license.
|
||||
--*/
|
||||
#pragma once
|
||||
#include "../../inc/cppwinrt_utils.h"
|
||||
#include "../types/inc/colorTable.hpp"
|
||||
|
||||
#include <DefaultSettings.h>
|
||||
#include <conattrs.hpp>
|
||||
#include "MySettings.g.h"
|
||||
@@ -12,9 +14,6 @@ namespace winrt::SampleApp::implementation
|
||||
{
|
||||
struct MySettings : MySettingsT<MySettings>
|
||||
{
|
||||
public:
|
||||
MySettings() = default;
|
||||
|
||||
// --------------------------- Core Settings ---------------------------
|
||||
// All of these settings are defined in ICoreSettings.
|
||||
|
||||
@@ -88,6 +87,14 @@ namespace winrt::SampleApp::implementation
|
||||
winrt::Microsoft::Terminal::Core::Color GetColorTableEntry(int32_t index) noexcept { return _ColorTable.at(index); }
|
||||
std::array<winrt::Microsoft::Terminal::Core::Color, 16> ColorTable() { return _ColorTable; }
|
||||
void ColorTable(std::array<winrt::Microsoft::Terminal::Core::Color, 16> /*colors*/) {}
|
||||
|
||||
MySettings()
|
||||
{
|
||||
const auto campbellSpan = ::Microsoft::Console::Utils::CampbellColorTable();
|
||||
std::transform(campbellSpan.begin(), campbellSpan.end(), _ColorTable.begin(), [](auto&& color) {
|
||||
return static_cast<winrt::Microsoft::Terminal::Core::Color>(til::color{ color });
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
<Private>true</Private>
|
||||
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
|
||||
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
using const_iterator = rle_vector::const_iterator;
|
||||
|
||||
ATTR_ROW(uint16_t width, TextAttribute attr);
|
||||
ATTR_ROW(rle_vector&& v) :
|
||||
_data(std::move(v)) {}
|
||||
|
||||
~ATTR_ROW() = default;
|
||||
|
||||
@@ -57,11 +59,11 @@ public:
|
||||
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
|
||||
friend class ROW;
|
||||
|
||||
rle_vector _data;
|
||||
|
||||
private:
|
||||
void Reset(const TextAttribute attr);
|
||||
|
||||
rle_vector _data;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class CommonState;
|
||||
#endif
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "unicode.hpp"
|
||||
#include "Row.hpp"
|
||||
|
||||
// Routine Description:
|
||||
// - constructor
|
||||
// Arguments:
|
||||
// - rowWidth - the size (in wchar_t) of the char and attribute rows
|
||||
// - pParent - the parent ROW
|
||||
// Return Value:
|
||||
// - instantiated object
|
||||
// Note: will through if unable to allocate char/attribute buffers
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26447) // small_vector's constructor says it can throw but it should not given how we use it. This suppresses this error for the AuditMode build.
|
||||
CharRow::CharRow(size_t rowWidth, ROW* const pParent) noexcept :
|
||||
_data(rowWidth, value_type()),
|
||||
_pParent{ FAIL_FAST_IF_NULL(pParent) }
|
||||
{
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
// Routine Description:
|
||||
// - gets the size of the row, in glyph cells
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the size of the row
|
||||
size_t CharRow::size() const noexcept
|
||||
{
|
||||
return _data.size();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets all properties of the CharRowBase to default values
|
||||
// Arguments:
|
||||
// - sRowWidth - The width of the row.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CharRow::Reset() noexcept
|
||||
{
|
||||
for (auto& cell : _data)
|
||||
{
|
||||
cell.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - resizes the width of the CharRowBase
|
||||
// Arguments:
|
||||
// - newSize - the new width of the character and attributes rows
|
||||
// Return Value:
|
||||
// - S_OK on success, otherwise relevant error code
|
||||
[[nodiscard]] HRESULT CharRow::Resize(const size_t newSize) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
const value_type insertVals;
|
||||
_data.resize(newSize, insertVals);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
typename CharRow::iterator CharRow::begin() noexcept
|
||||
{
|
||||
return _data.begin();
|
||||
}
|
||||
|
||||
typename CharRow::const_iterator CharRow::cbegin() const noexcept
|
||||
{
|
||||
return _data.cbegin();
|
||||
}
|
||||
|
||||
typename CharRow::iterator CharRow::end() noexcept
|
||||
{
|
||||
return _data.end();
|
||||
}
|
||||
|
||||
typename CharRow::const_iterator CharRow::cend() const noexcept
|
||||
{
|
||||
return _data.cend();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Inspects the current internal string to find the left edge of it
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The calculated left boundary of the internal string.
|
||||
size_t CharRow::MeasureLeft() const noexcept
|
||||
{
|
||||
const_iterator it = _data.cbegin();
|
||||
while (it != _data.cend() && it->IsSpace())
|
||||
{
|
||||
++it;
|
||||
}
|
||||
return it - _data.cbegin();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Inspects the current internal string to find the right edge of it
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The calculated right boundary of the internal string.
|
||||
size_t CharRow::MeasureRight() const
|
||||
{
|
||||
const_reverse_iterator it = _data.crbegin();
|
||||
while (it != _data.crend() && it->IsSpace())
|
||||
{
|
||||
++it;
|
||||
}
|
||||
return _data.crend() - it;
|
||||
}
|
||||
|
||||
void CharRow::ClearCell(const size_t column)
|
||||
{
|
||||
_data.at(column).Reset();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Tells you whether or not this row contains any valid text.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - True if there is valid text in this row. False otherwise.
|
||||
bool CharRow::ContainsText() const noexcept
|
||||
{
|
||||
for (const value_type& cell : _data)
|
||||
{
|
||||
if (!cell.IsSpace())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets the attribute at the specified column
|
||||
// Arguments:
|
||||
// - column - the column to get the attribute for
|
||||
// Return Value:
|
||||
// - the attribute
|
||||
// Note: will throw exception if column is out of bounds
|
||||
const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const
|
||||
{
|
||||
return _data.at(column).DbcsAttr();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets the attribute at the specified column
|
||||
// Arguments:
|
||||
// - column - the column to get the attribute for
|
||||
// Return Value:
|
||||
// - the attribute
|
||||
// Note: will throw exception if column is out of bounds
|
||||
DbcsAttribute& CharRow::DbcsAttrAt(const size_t column)
|
||||
{
|
||||
return _data.at(column).DbcsAttr();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - resets text data at column
|
||||
// Arguments:
|
||||
// - column - column index to clear text data from
|
||||
// Return Value:
|
||||
// - <none>
|
||||
// Note: will throw exception if column is out of bounds
|
||||
void CharRow::ClearGlyph(const size_t column)
|
||||
{
|
||||
_data.at(column).EraseChars();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - returns text data at column as a const reference.
|
||||
// Arguments:
|
||||
// - column - column to get text data for
|
||||
// Return Value:
|
||||
// - text data at column
|
||||
// - Note: will throw exception if column is out of bounds
|
||||
const CharRow::reference CharRow::GlyphAt(const size_t column) const
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
|
||||
return { const_cast<CharRow&>(*this), column };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - returns text data at column as a reference.
|
||||
// Arguments:
|
||||
// - column - column to get text data for
|
||||
// Return Value:
|
||||
// - text data at column
|
||||
// - Note: will throw exception if column is out of bounds
|
||||
CharRow::reference CharRow::GlyphAt(const size_t column)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
|
||||
return { *this, column };
|
||||
}
|
||||
|
||||
std::wstring CharRow::GetText() const
|
||||
{
|
||||
std::wstring wstr;
|
||||
wstr.reserve(_data.size());
|
||||
|
||||
for (size_t i = 0; i < _data.size(); ++i)
|
||||
{
|
||||
const auto glyph = GlyphAt(i);
|
||||
if (!DbcsAttrAt(i).IsTrailing())
|
||||
{
|
||||
for (const auto wch : glyph)
|
||||
{
|
||||
wstr.push_back(wch);
|
||||
}
|
||||
}
|
||||
}
|
||||
return wstr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get delimiter class for a position in the char row
|
||||
// - used for double click selection and uia word navigation
|
||||
// Arguments:
|
||||
// - column: column to get text data for
|
||||
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const DelimiterClass CharRow::DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
|
||||
|
||||
const auto glyph = *GlyphAt(column).begin();
|
||||
if (glyph <= UNICODE_SPACE)
|
||||
{
|
||||
return DelimiterClass::ControlChar;
|
||||
}
|
||||
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
|
||||
{
|
||||
return DelimiterClass::DelimiterChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DelimiterClass::RegularChar;
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeStorage& CharRow::GetUnicodeStorage() noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
}
|
||||
|
||||
const UnicodeStorage& CharRow::GetUnicodeStorage() const noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - calculates the key used by the given column of the char row to store glyph data in UnicodeStorage
|
||||
// Arguments:
|
||||
// - column - the column to generate the key for
|
||||
// Return Value:
|
||||
// - the COORD key for data access from UnicodeStorage for the column
|
||||
COORD CharRow::GetStorageKey(const size_t column) const noexcept
|
||||
{
|
||||
return { gsl::narrow<SHORT>(column), _pParent->GetId() };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Updates the pointer to the parent row (which might change if we shuffle the rows around)
|
||||
// Arguments:
|
||||
// - pParent - Pointer to the parent row
|
||||
void CharRow::UpdateParent(ROW* const pParent)
|
||||
{
|
||||
_pParent = FAIL_FAST_IF_NULL(pParent);
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CharRow.hpp
|
||||
|
||||
Abstract:
|
||||
- contains data structure for UCS2 encoded character data of a row
|
||||
|
||||
Author(s):
|
||||
- Michael Niksa (miniksa) 10-Apr-2014
|
||||
- Paul Campbell (paulcam) 10-Apr-2014
|
||||
|
||||
Revision History:
|
||||
- From components of output.h/.c
|
||||
by Therese Stowell (ThereseS) 1990-1991
|
||||
- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017)
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "CharRowCellReference.hpp"
|
||||
#include "CharRowCell.hpp"
|
||||
#include "UnicodeStorage.hpp"
|
||||
|
||||
class ROW;
|
||||
|
||||
enum class DelimiterClass
|
||||
{
|
||||
ControlChar,
|
||||
DelimiterChar,
|
||||
RegularChar
|
||||
};
|
||||
|
||||
// the characters of one row of screen buffer
|
||||
// we keep the following values so that we don't write
|
||||
// more pixels to the screen than we have to:
|
||||
// left is initialized to screenbuffer width. right is
|
||||
// initialized to zero.
|
||||
//
|
||||
// [ foo.bar 12-12-61 ]
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
// Chars Left Right end of Chars buffer
|
||||
class CharRow final
|
||||
{
|
||||
public:
|
||||
using glyph_type = typename wchar_t;
|
||||
using value_type = typename CharRowCell;
|
||||
using iterator = typename boost::container::small_vector_base<value_type>::iterator;
|
||||
using const_iterator = typename boost::container::small_vector_base<value_type>::const_iterator;
|
||||
using const_reverse_iterator = typename boost::container::small_vector_base<value_type>::const_reverse_iterator;
|
||||
using reference = typename CharRowCellReference;
|
||||
|
||||
CharRow(size_t rowWidth, ROW* const pParent) noexcept;
|
||||
|
||||
size_t size() const noexcept;
|
||||
[[nodiscard]] HRESULT Resize(const size_t newSize) noexcept;
|
||||
size_t MeasureLeft() const noexcept;
|
||||
size_t MeasureRight() const;
|
||||
bool ContainsText() const noexcept;
|
||||
const DbcsAttribute& DbcsAttrAt(const size_t column) const;
|
||||
DbcsAttribute& DbcsAttrAt(const size_t column);
|
||||
void ClearGlyph(const size_t column);
|
||||
|
||||
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
// working with glyphs
|
||||
const reference GlyphAt(const size_t column) const;
|
||||
reference GlyphAt(const size_t column);
|
||||
|
||||
// iterators
|
||||
iterator begin() noexcept;
|
||||
const_iterator cbegin() const noexcept;
|
||||
const_iterator begin() const noexcept { return cbegin(); }
|
||||
|
||||
iterator end() noexcept;
|
||||
const_iterator cend() const noexcept;
|
||||
const_iterator end() const noexcept { return cend(); }
|
||||
|
||||
UnicodeStorage& GetUnicodeStorage() noexcept;
|
||||
const UnicodeStorage& GetUnicodeStorage() const noexcept;
|
||||
COORD GetStorageKey(const size_t column) const noexcept;
|
||||
|
||||
void UpdateParent(ROW* const pParent);
|
||||
|
||||
friend CharRowCellReference;
|
||||
friend class ROW;
|
||||
|
||||
private:
|
||||
void Reset() noexcept;
|
||||
void ClearCell(const size_t column);
|
||||
std::wstring GetText() const;
|
||||
|
||||
protected:
|
||||
// storage for glyph data and dbcs attributes
|
||||
boost::container::small_vector<value_type, 120> _data;
|
||||
|
||||
// ROW that this CharRow belongs to
|
||||
ROW* _pParent;
|
||||
};
|
||||
|
||||
template<typename InputIt1, typename InputIt2>
|
||||
void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt)
|
||||
{
|
||||
std::transform(startChars,
|
||||
endChars,
|
||||
startAttrs,
|
||||
outIt,
|
||||
[](const wchar_t wch, const DbcsAttribute attr) {
|
||||
return CharRow::value_type{ wch, attr };
|
||||
});
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "precomp.h"
|
||||
|
||||
#include "CharRowCell.hpp"
|
||||
#include "unicode.hpp"
|
||||
|
||||
// default glyph value, used for resetting the character data portion of a cell
|
||||
static constexpr wchar_t DefaultValue = UNICODE_SPACE;
|
||||
|
||||
// Routine Description:
|
||||
// - "erases" the glyph. really sets it back to the default "empty" value
|
||||
void CharRowCell::EraseChars() noexcept
|
||||
{
|
||||
if (_attr.IsGlyphStored())
|
||||
{
|
||||
_attr.SetGlyphStored(false);
|
||||
}
|
||||
_wch = DefaultValue;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - resets this object back to the defaults it would have from the default constructor
|
||||
void CharRowCell::Reset() noexcept
|
||||
{
|
||||
_attr.Reset();
|
||||
_wch = DefaultValue;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - checks if cell contains a space glyph
|
||||
// Return Value:
|
||||
// - true if cell contains a space glyph, false otherwise
|
||||
bool CharRowCell::IsSpace() const noexcept
|
||||
{
|
||||
return !_attr.IsGlyphStored() && _wch == UNICODE_SPACE;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Access the DbcsAttribute for the cell
|
||||
// Return Value:
|
||||
// - ref to the cells' DbcsAttribute
|
||||
DbcsAttribute& CharRowCell::DbcsAttr() noexcept
|
||||
{
|
||||
return _attr;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Access the DbcsAttribute for the cell
|
||||
// Return Value:
|
||||
// - ref to the cells' DbcsAttribute
|
||||
const DbcsAttribute& CharRowCell::DbcsAttr() const noexcept
|
||||
{
|
||||
return _attr;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Access the cell's wchar field. this does not access any char data through UnicodeStorage.
|
||||
// Return Value:
|
||||
// - the cell's wchar field
|
||||
wchar_t& CharRowCell::Char() noexcept
|
||||
{
|
||||
return _wch;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Access the cell's wchar field. this does not access any char data through UnicodeStorage.
|
||||
// Return Value:
|
||||
// - the cell's wchar field
|
||||
const wchar_t& CharRowCell::Char() const noexcept
|
||||
{
|
||||
return _wch;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CharRowCell.hpp
|
||||
|
||||
Abstract:
|
||||
- data structure for one cell of a char row. contains the char data for one
|
||||
coordinate position in the output buffer (leading/trailing information and
|
||||
the char itself.
|
||||
|
||||
Author(s):
|
||||
- Austin Diviness (AustDi) 02-May-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "unicode.hpp"
|
||||
|
||||
#if (defined(_M_IX86) || defined(_M_AMD64))
|
||||
// currently CharRowCell's fields use 3 bytes of memory, leaving the 4th byte in unused. this leads
|
||||
// to a rather large amount of useless memory allocated. so instead, pack CharRowCell by bytes instead of words.
|
||||
#pragma pack(push, 1)
|
||||
#endif
|
||||
|
||||
class CharRowCell final
|
||||
{
|
||||
public:
|
||||
CharRowCell() noexcept = default;
|
||||
CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept
|
||||
:
|
||||
_wch(wch),
|
||||
_attr(attr)
|
||||
{
|
||||
}
|
||||
|
||||
void EraseChars() noexcept;
|
||||
void Reset() noexcept;
|
||||
|
||||
bool IsSpace() const noexcept;
|
||||
|
||||
DbcsAttribute& DbcsAttr() noexcept;
|
||||
const DbcsAttribute& DbcsAttr() const noexcept;
|
||||
|
||||
wchar_t& Char() noexcept;
|
||||
const wchar_t& Char() const noexcept;
|
||||
|
||||
friend constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept;
|
||||
|
||||
private:
|
||||
wchar_t _wch{ UNICODE_SPACE };
|
||||
DbcsAttribute _attr{};
|
||||
};
|
||||
|
||||
#if (defined(_M_IX86) || defined(_M_AMD64))
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept
|
||||
{
|
||||
return (a._wch == b._wch &&
|
||||
a._attr == b._attr);
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "UnicodeStorage.hpp"
|
||||
#include "CharRow.hpp"
|
||||
|
||||
// Routine Description:
|
||||
// - assignment operator. will store extended glyph data in a separate storage location
|
||||
// Arguments:
|
||||
// - chars - the glyph data to store
|
||||
void CharRowCellReference::operator=(const std::wstring_view chars)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, chars.empty());
|
||||
if (chars.size() == 1)
|
||||
{
|
||||
_cellData().Char() = chars.front();
|
||||
_cellData().DbcsAttr().SetGlyphStored(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& storage = _parent.GetUnicodeStorage();
|
||||
const auto key = _parent.GetStorageKey(_index);
|
||||
storage.StoreGlyph(key, { chars.cbegin(), chars.cend() });
|
||||
_cellData().DbcsAttr().SetGlyphStored(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - implicit conversion to vector<wchar_t> operator.
|
||||
// Return Value:
|
||||
// - std::vector<wchar_t> of the glyph data in the referenced cell
|
||||
CharRowCellReference::operator std::wstring_view() const
|
||||
{
|
||||
return _glyphData();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - The CharRowCell this object "references"
|
||||
// Return Value:
|
||||
// - ref to the CharRowCell
|
||||
CharRowCell& CharRowCellReference::_cellData()
|
||||
{
|
||||
return _parent._data.at(_index);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - The CharRowCell this object "references"
|
||||
// Return Value:
|
||||
// - ref to the CharRowCell
|
||||
const CharRowCell& CharRowCellReference::_cellData() const
|
||||
{
|
||||
return _parent._data.at(_index);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - the glyph data of the referenced cell
|
||||
// Return Value:
|
||||
// - the glyph data
|
||||
std::wstring_view CharRowCellReference::_glyphData() const
|
||||
{
|
||||
if (_cellData().DbcsAttr().IsGlyphStored())
|
||||
{
|
||||
const auto& text = _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index));
|
||||
|
||||
return { text.data(), text.size() };
|
||||
}
|
||||
else
|
||||
{
|
||||
return { &_cellData().Char(), 1 };
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets read-only iterator to the beginning of the glyph data
|
||||
// Return Value:
|
||||
// - iterator of the glyph data
|
||||
CharRowCellReference::const_iterator CharRowCellReference::begin() const
|
||||
{
|
||||
if (_cellData().DbcsAttr().IsGlyphStored())
|
||||
{
|
||||
return _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index)).data();
|
||||
}
|
||||
else
|
||||
{
|
||||
return &_cellData().Char();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - get read-only iterator to the end of the glyph data
|
||||
// Return Value:
|
||||
// - end iterator of the glyph data
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26481)
|
||||
// TODO GH 2672: eliminate using pointers raw as begin/end markers in this class
|
||||
CharRowCellReference::const_iterator CharRowCellReference::end() const
|
||||
{
|
||||
if (_cellData().DbcsAttr().IsGlyphStored())
|
||||
{
|
||||
const auto& chars = _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index));
|
||||
return chars.data() + chars.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
return &_cellData().Char() + 1;
|
||||
}
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph)
|
||||
{
|
||||
const DbcsAttribute& dbcsAttr = ref._cellData().DbcsAttr();
|
||||
if (glyph.size() == 1 && dbcsAttr.IsGlyphStored())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (glyph.size() > 1 && !dbcsAttr.IsGlyphStored())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (glyph.size() == 1 && !dbcsAttr.IsGlyphStored())
|
||||
{
|
||||
return ref._cellData().Char() == glyph.front();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& chars = ref._parent.GetUnicodeStorage().GetText(ref._parent.GetStorageKey(ref._index));
|
||||
return chars == glyph;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref)
|
||||
{
|
||||
return ref == glyph;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CharRowCellReference.hpp
|
||||
|
||||
Abstract:
|
||||
- reference class for the glyph data of a char row cell
|
||||
|
||||
Author(s):
|
||||
- Austin Diviness (AustDi) 02-May-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "CharRowCell.hpp"
|
||||
#include <utility>
|
||||
|
||||
class CharRow;
|
||||
|
||||
class CharRowCellReference final
|
||||
{
|
||||
public:
|
||||
using const_iterator = const wchar_t*;
|
||||
|
||||
CharRowCellReference(CharRow& parent, const size_t index) noexcept :
|
||||
_parent{ parent },
|
||||
_index{ index }
|
||||
{
|
||||
}
|
||||
|
||||
~CharRowCellReference() = default;
|
||||
CharRowCellReference(const CharRowCellReference&) noexcept = default;
|
||||
CharRowCellReference(CharRowCellReference&&) noexcept = default;
|
||||
|
||||
void operator=(const CharRowCellReference&) = delete;
|
||||
void operator=(CharRowCellReference&&) = delete;
|
||||
|
||||
void operator=(const std::wstring_view chars);
|
||||
operator std::wstring_view() const;
|
||||
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
friend bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph);
|
||||
friend bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref);
|
||||
|
||||
private:
|
||||
// what char row the object belongs to
|
||||
CharRow& _parent;
|
||||
// the index of the cell in the parent char row
|
||||
const size_t _index;
|
||||
|
||||
CharRowCell& _cellData();
|
||||
const CharRowCell& _cellData() const;
|
||||
|
||||
std::wstring_view _glyphData() const;
|
||||
};
|
||||
|
||||
bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph);
|
||||
bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref);
|
||||
@@ -27,14 +27,12 @@ public:
|
||||
};
|
||||
|
||||
DbcsAttribute() noexcept :
|
||||
_attribute{ Attribute::Single },
|
||||
_glyphStored{ false }
|
||||
_attribute{ Attribute::Single }
|
||||
{
|
||||
}
|
||||
|
||||
DbcsAttribute(const Attribute attribute) noexcept :
|
||||
_attribute{ attribute },
|
||||
_glyphStored{ false }
|
||||
_attribute{ attribute }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -58,16 +56,6 @@ public:
|
||||
return IsLeading() || IsTrailing();
|
||||
}
|
||||
|
||||
constexpr bool IsGlyphStored() const noexcept
|
||||
{
|
||||
return _glyphStored;
|
||||
}
|
||||
|
||||
void SetGlyphStored(const bool stored) noexcept
|
||||
{
|
||||
_glyphStored = stored;
|
||||
}
|
||||
|
||||
void SetSingle() noexcept
|
||||
{
|
||||
_attribute = Attribute::Single;
|
||||
@@ -86,7 +74,6 @@ public:
|
||||
void Reset() noexcept
|
||||
{
|
||||
SetSingle();
|
||||
SetGlyphStored(false);
|
||||
}
|
||||
|
||||
WORD GeneratePublicApiAttributeFormat() const noexcept
|
||||
@@ -127,7 +114,6 @@ public:
|
||||
|
||||
private:
|
||||
Attribute _attribute : 2;
|
||||
bool _glyphStored : 1;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TextBufferTests;
|
||||
|
||||
@@ -1,546 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "OutputCellIterator.hpp"
|
||||
|
||||
#include "../../types/inc/convert.hpp"
|
||||
#include "../../types/inc/Utf16Parser.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLOR };
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular wchar. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - wch - The character to use for filling
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(wch)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular color. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - attr - The color attribute to use for filling
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const TextAttribute& attr, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(attr)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular character and color. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - wch - The character to use for filling
|
||||
// - attr - The color attribute to use for filling
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(wch, attr)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular CHAR_INFO. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - charInfo - The legacy character and color data to use for filling (uses Unicode portion of text data)
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(charInfo)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over a range of text only. No color data will be modified as the text is inserted.
|
||||
// Arguments:
|
||||
// - utf16Text - UTF-16 text range
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) :
|
||||
_mode(Mode::LooseTextOnly),
|
||||
_currentView(s_GenerateView(utf16Text)),
|
||||
_run(utf16Text),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over a range text that will apply the same color to every position.
|
||||
// Arguments:
|
||||
// - utf16Text - UTF-16 text range
|
||||
// - attribute - Color to apply over the entire range
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute) :
|
||||
_mode(Mode::Loose),
|
||||
_currentView(s_GenerateView(utf16Text, attribute)),
|
||||
_run(utf16Text),
|
||||
_attr(attribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over legacy colors only. The text is not modified.
|
||||
// Arguments:
|
||||
// - legacyAttrs - One legacy color item per cell
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs) noexcept :
|
||||
_mode(Mode::LegacyAttr),
|
||||
_currentView(s_GenerateViewLegacyAttr(til::at(legacyAttrs, 0))),
|
||||
_run(legacyAttrs),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute.
|
||||
// Arguments:
|
||||
// - charInfos - Multiple cell with unicode text and legacy color data.
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept :
|
||||
_mode(Mode::CharInfo),
|
||||
_currentView(s_GenerateView(til::at(charInfos, 0))),
|
||||
_run(charInfos),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over existing OutputCells with full text and color data.
|
||||
// Arguments:
|
||||
// - cells - Multiple cells in a run
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const OutputCell> cells) :
|
||||
_mode(Mode::Cell),
|
||||
_currentView(s_GenerateView(til::at(cells, 0))),
|
||||
_run(cells),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Specifies whether this iterator is valid for dereferencing (still valid underlying data)
|
||||
// Return Value:
|
||||
// - True if the views on dereference are valid. False if it shouldn't be dereferenced.
|
||||
OutputCellIterator::operator bool() const noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode::Loose:
|
||||
case Mode::LooseTextOnly:
|
||||
{
|
||||
// In lieu of using start and end, this custom iterator type simply becomes bool false
|
||||
// when we run out of items to iterate over.
|
||||
return _pos < std::get<std::wstring_view>(_run).length();
|
||||
}
|
||||
case Mode::Fill:
|
||||
{
|
||||
if (_fillLimit > 0)
|
||||
{
|
||||
return _pos < _fillLimit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Mode::Cell:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const OutputCell>>(_run).size();
|
||||
}
|
||||
case Mode::CharInfo:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const CHAR_INFO>>(_run).size();
|
||||
}
|
||||
case Mode::LegacyAttr:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const WORD>>(_run).size();
|
||||
}
|
||||
default:
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
}
|
||||
}
|
||||
CATCH_FAIL_FAST();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Advances the iterator one position over the underlying data source.
|
||||
// Return Value:
|
||||
// - Reference to self after advancement.
|
||||
OutputCellIterator& OutputCellIterator::operator++()
|
||||
{
|
||||
// Keep track of total distance moved (cells filled)
|
||||
_distance++;
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode::Loose:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
{
|
||||
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
|
||||
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
|
||||
_pos += _currentView.Chars().size();
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos), _attr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::LooseTextOnly:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
{
|
||||
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
|
||||
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
|
||||
_pos += _currentView.Chars().size();
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::Fill:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
{
|
||||
if (_currentView.DbcsAttr().IsTrailing())
|
||||
{
|
||||
auto dbcsAttr = _currentView.DbcsAttr();
|
||||
dbcsAttr.SetLeading();
|
||||
|
||||
_currentView = OutputCellView(_currentView.Chars(),
|
||||
dbcsAttr,
|
||||
_currentView.TextAttr(),
|
||||
_currentView.TextAttrBehavior());
|
||||
}
|
||||
|
||||
if (_fillLimit > 0)
|
||||
{
|
||||
// We walk forward by one because we fill with the same cell over and over no matter what
|
||||
_pos++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::Cell:
|
||||
{
|
||||
// Walk forward by one because cells are assumed to be in the form they needed to be
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(til::at(std::get<gsl::span<const OutputCell>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::CharInfo:
|
||||
{
|
||||
// Walk forward by one because charinfos are just the legacy version of cells and prealigned to columns
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(til::at(std::get<gsl::span<const CHAR_INFO>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::LegacyAttr:
|
||||
{
|
||||
// Walk forward by one because color attributes apply cell by cell (no complex text information)
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<gsl::span<const WORD>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
}
|
||||
|
||||
return (*this);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Advances the iterator one position over the underlying data source.
|
||||
// Return Value:
|
||||
// - Reference to self after advancement.
|
||||
OutputCellIterator OutputCellIterator::operator++(int)
|
||||
{
|
||||
auto temp(*this);
|
||||
operator++();
|
||||
return temp;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Reference the view to fully-formed output cell data representing the underlying data source.
|
||||
// Return Value:
|
||||
// - Reference to the view
|
||||
const OutputCellView& OutputCellIterator::operator*() const noexcept
|
||||
{
|
||||
return _currentView;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Get pointer to the view to fully-formed output cell data representing the underlying data source.
|
||||
// Return Value:
|
||||
// - Pointer to the view
|
||||
const OutputCellView* OutputCellIterator::operator->() const noexcept
|
||||
{
|
||||
return &_currentView;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Checks the current view. If it is a leading half, it updates the current
|
||||
// view to the trailing half of the same glyph.
|
||||
// - This helps us to draw glyphs that are two columns wide by "doubling"
|
||||
// the view that is returned so it will consume two cells.
|
||||
// Return Value:
|
||||
// - True if we just turned a lead half into a trailing half (and caller doesn't
|
||||
// need to further update the view).
|
||||
// - False if this wasn't applicable and the caller should update the view.
|
||||
bool OutputCellIterator::_TryMoveTrailing() noexcept
|
||||
{
|
||||
if (_currentView.DbcsAttr().IsLeading())
|
||||
{
|
||||
auto dbcsAttr = _currentView.DbcsAttr();
|
||||
dbcsAttr.SetTrailing();
|
||||
|
||||
_currentView = OutputCellView(_currentView.Chars(),
|
||||
dbcsAttr,
|
||||
_currentView.TextAttr(),
|
||||
_currentView.TextAttrBehavior());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and specify that the attributes shouldn't be changed.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
|
||||
{
|
||||
return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// - attr - Color attributes to apply to the text
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
|
||||
const TextAttribute attr)
|
||||
{
|
||||
return s_GenerateView(view, attr, TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// - attr - Color attributes to apply to the text
|
||||
// - behavior - Behavior of the given text attribute (used when writing)
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
|
||||
const TextAttribute attr,
|
||||
const TextAttributeBehavior behavior)
|
||||
{
|
||||
const auto glyph = Utf16Parser::ParseNext(view);
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (IsGlyphFullWidth(glyph))
|
||||
{
|
||||
dbcsAttr.SetLeading();
|
||||
}
|
||||
|
||||
return OutputCellView(glyph, dbcsAttr, attr, behavior);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&wch, 1);
|
||||
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (IsGlyphFullWidth(wch))
|
||||
{
|
||||
dbcsAttr.SetLeading();
|
||||
}
|
||||
|
||||
return OutputCellView(glyph, dbcsAttr, InvalidTextAttribute, TextAttributeBehavior::Current);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - attr - View representing a single color
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) noexcept
|
||||
{
|
||||
return OutputCellView({}, {}, attr, TextAttributeBehavior::StoredOnly);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
|
||||
// - attr - View representing a single color
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&wch, 1);
|
||||
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (IsGlyphFullWidth(wch))
|
||||
{
|
||||
dbcsAttr.SetLeading();
|
||||
}
|
||||
|
||||
return OutputCellView(glyph, dbcsAttr, attr, TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - legacyAttr - View representing a single legacy color
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept
|
||||
{
|
||||
WORD cleanAttr = legacyAttr;
|
||||
WI_ClearAllFlags(cleanAttr, COMMON_LVB_SBCSDBCS); // don't use legacy lead/trailing byte flags for colors
|
||||
|
||||
const TextAttribute attr(cleanAttr);
|
||||
return s_GenerateView(attr);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - charInfo - character and attribute pair representing a single cell
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&charInfo.Char.UnicodeChar, 1);
|
||||
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_LEADING_BYTE))
|
||||
{
|
||||
dbcsAttr.SetLeading();
|
||||
}
|
||||
else if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_TRAILING_BYTE))
|
||||
{
|
||||
dbcsAttr.SetTrailing();
|
||||
}
|
||||
|
||||
const TextAttribute textAttr(charInfo.Attributes);
|
||||
|
||||
const auto behavior = TextAttributeBehavior::Stored;
|
||||
return OutputCellView(glyph, dbcsAttr, textAttr, behavior);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// Arguments:
|
||||
// - cell - A reference to the cell for which we will make the read-only view
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const OutputCell& cell)
|
||||
{
|
||||
return OutputCellView(cell.Chars(), cell.DbcsAttr(), cell.TextAttr(), cell.TextAttrBehavior());
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the distance between two iterators relative to the input data given in.
|
||||
// Return Value:
|
||||
// - The number of items of the input run consumed between these two iterators.
|
||||
ptrdiff_t OutputCellIterator::GetInputDistance(OutputCellIterator other) const noexcept
|
||||
{
|
||||
return _pos - other._pos;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the distance between two iterators relative to the number of cells inserted.
|
||||
// Return Value:
|
||||
// - The number of cells in the backing buffer filled between these two iterators.
|
||||
ptrdiff_t OutputCellIterator::GetCellDistance(OutputCellIterator other) const noexcept
|
||||
{
|
||||
return _distance - other._distance;
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- OutputCellIterator.hpp
|
||||
|
||||
Abstract:
|
||||
- Read-only view into an entire batch of data to be written into the output buffer.
|
||||
- This is done for performance reasons (avoid heap allocs and copies).
|
||||
|
||||
Author:
|
||||
- Michael Niksa (MiNiksa) 06-Oct-2018
|
||||
|
||||
Revision History:
|
||||
- Based on work from OutputCell.hpp/cpp by Austin Diviness (AustDi)
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TextAttribute.hpp"
|
||||
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellView.hpp"
|
||||
|
||||
class OutputCellIterator final
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = OutputCellView;
|
||||
using difference_type = ptrdiff_t;
|
||||
using pointer = OutputCellView*;
|
||||
using reference = OutputCellView&;
|
||||
|
||||
OutputCellIterator(const wchar_t& wch, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const std::wstring_view utf16Text);
|
||||
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute);
|
||||
OutputCellIterator(const gsl::span<const WORD> legacyAttributes) noexcept;
|
||||
OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept;
|
||||
OutputCellIterator(const gsl::span<const OutputCell> cells);
|
||||
~OutputCellIterator() = default;
|
||||
|
||||
OutputCellIterator& operator=(const OutputCellIterator& it) = default;
|
||||
|
||||
operator bool() const noexcept;
|
||||
|
||||
ptrdiff_t GetCellDistance(OutputCellIterator other) const noexcept;
|
||||
ptrdiff_t GetInputDistance(OutputCellIterator other) const noexcept;
|
||||
friend ptrdiff_t operator-(OutputCellIterator one, OutputCellIterator two) = delete;
|
||||
|
||||
OutputCellIterator& operator++();
|
||||
OutputCellIterator operator++(int);
|
||||
|
||||
const OutputCellView& operator*() const noexcept;
|
||||
const OutputCellView* operator->() const noexcept;
|
||||
|
||||
private:
|
||||
enum class Mode
|
||||
{
|
||||
// Loose mode is where we're given text and attributes in a raw sort of form
|
||||
// like while data is being inserted from an API call.
|
||||
Loose,
|
||||
|
||||
// Loose mode with only text is where we're given just text and we want
|
||||
// to use the attribute already in the buffer when writing
|
||||
LooseTextOnly,
|
||||
|
||||
// Fill mode is where we were given one thing and we just need to keep giving
|
||||
// that back over and over for eternity.
|
||||
Fill,
|
||||
|
||||
// Given a run of legacy attributes, convert each of them and insert only attribute data.
|
||||
LegacyAttr,
|
||||
|
||||
// CharInfo mode is where we've been given a pair of text and attribute for each
|
||||
// cell in the legacy format from an API call.
|
||||
CharInfo,
|
||||
|
||||
// Cell mode is where we have an already fully structured cell data usually
|
||||
// from accessing/copying data already put into the OutputBuffer.
|
||||
Cell,
|
||||
};
|
||||
Mode _mode;
|
||||
|
||||
gsl::span<const WORD> _legacyAttrs;
|
||||
|
||||
std::variant<
|
||||
std::wstring_view,
|
||||
gsl::span<const WORD>,
|
||||
gsl::span<const CHAR_INFO>,
|
||||
gsl::span<const OutputCell>,
|
||||
std::monostate>
|
||||
_run;
|
||||
|
||||
TextAttribute _attr;
|
||||
|
||||
bool _TryMoveTrailing() noexcept;
|
||||
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view);
|
||||
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view,
|
||||
const TextAttribute attr);
|
||||
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view,
|
||||
const TextAttribute attr,
|
||||
const TextAttributeBehavior behavior);
|
||||
|
||||
static OutputCellView s_GenerateView(const wchar_t& wch) noexcept;
|
||||
static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept;
|
||||
static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept;
|
||||
static OutputCellView s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept;
|
||||
static OutputCellView s_GenerateView(const CHAR_INFO& charInfo) noexcept;
|
||||
|
||||
static OutputCellView s_GenerateView(const OutputCell& cell);
|
||||
|
||||
OutputCellView _currentView;
|
||||
|
||||
size_t _pos;
|
||||
size_t _distance;
|
||||
size_t _fillLimit;
|
||||
};
|
||||
@@ -42,19 +42,6 @@ gsl::span<OutputCell> OutputCellRect::GetRow(const size_t row)
|
||||
return gsl::span<OutputCell>(_FindRowOffset(row), _cols);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets a read-only iterator view over a single row of the rectangle.
|
||||
// Arguments:
|
||||
// - row - The Y position or row index in the buffer.
|
||||
// Return Value:
|
||||
// - Read-only iterator of OutputCells
|
||||
OutputCellIterator OutputCellRect::GetRowIter(const size_t row) const
|
||||
{
|
||||
const gsl::span<const OutputCell> view(_FindRowOffset(row), _cols);
|
||||
|
||||
return OutputCellIterator(view);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Internal helper to find the pointer to the specific row offset in the giant
|
||||
// contiguous block of memory allocated for this rectangle.
|
||||
|
||||
@@ -21,10 +21,8 @@ Revision History:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "TextAttribute.hpp"
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellIterator.hpp"
|
||||
|
||||
class OutputCellRect final
|
||||
{
|
||||
@@ -33,7 +31,6 @@ public:
|
||||
OutputCellRect(const size_t rows, const size_t cols);
|
||||
|
||||
gsl::span<OutputCell> GetRow(const size_t row);
|
||||
OutputCellIterator GetRowIter(const size_t row) const;
|
||||
|
||||
size_t Height() const noexcept;
|
||||
size_t Width() const noexcept;
|
||||
|
||||
@@ -6,7 +6,7 @@ Module Name:
|
||||
- OutputCellView.hpp
|
||||
|
||||
Abstract:
|
||||
- Read-only view into a single cell of data that someone is attempting to write into the output buffer.
|
||||
- Read view into a single cell of data that someone is attempting to write into the output buffer.
|
||||
- This is done for performance reasons (avoid heap allocs and copies).
|
||||
|
||||
Author:
|
||||
@@ -36,6 +36,21 @@ public:
|
||||
TextAttribute TextAttr() const noexcept;
|
||||
TextAttributeBehavior TextAttrBehavior() const noexcept;
|
||||
|
||||
void UpdateText(const std::wstring_view& view) noexcept
|
||||
{
|
||||
_view = view;
|
||||
};
|
||||
|
||||
void UpdateDbcsAttribute(const DbcsAttribute& dbcsAttr) noexcept
|
||||
{
|
||||
_dbcsAttr = dbcsAttr;
|
||||
}
|
||||
|
||||
void UpdateTextAttribute(const TextAttribute& textAttr) noexcept
|
||||
{
|
||||
_textAttr = textAttr;
|
||||
}
|
||||
|
||||
bool operator==(const OutputCellView& view) const noexcept;
|
||||
bool operator!=(const OutputCellView& view) const noexcept;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "precomp.h"
|
||||
#include "Row.hpp"
|
||||
#include "CharRow.hpp"
|
||||
#include "textBuffer.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
|
||||
@@ -16,15 +15,16 @@
|
||||
// - pParent - the text buffer that this row belongs to
|
||||
// Return Value:
|
||||
// - constructed object
|
||||
ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) :
|
||||
_id{ rowId },
|
||||
_rowWidth{ rowWidth },
|
||||
_charRow{ rowWidth, this },
|
||||
_attrRow{ rowWidth, fillAttribute },
|
||||
ROW::ROW(const SHORT /*rowId*/, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const /*pParent*/) :
|
||||
_data{
|
||||
std::wstring(rowWidth, UNICODE_SPACE),
|
||||
{ { gsl::narrow_cast<uint8_t>(1), gsl::narrow_cast<uint16_t>(rowWidth) } },
|
||||
{ rowWidth, fillAttribute },
|
||||
rowWidth
|
||||
},
|
||||
_lineRendition{ LineRendition::SingleWidth },
|
||||
_wrapForced{ false },
|
||||
_doubleBytePadded{ false },
|
||||
_pParent{ pParent }
|
||||
_doubleBytePadded{ false }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -39,10 +39,11 @@ bool ROW::Reset(const TextAttribute Attr)
|
||||
_lineRendition = LineRendition::SingleWidth;
|
||||
_wrapForced = false;
|
||||
_doubleBytePadded = false;
|
||||
_charRow.Reset();
|
||||
_data._cwid.replace(0, _data._width, { 1, _data._width }); // replace entire RLE with one run
|
||||
_data._data.replace(0, _data._width, _data._width, UNICODE_SPACE);
|
||||
try
|
||||
{
|
||||
_attrRow.Reset(Attr);
|
||||
_data._attrRow.Reset(Attr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -60,14 +61,20 @@ bool ROW::Reset(const TextAttribute Attr)
|
||||
// - S_OK if successful, otherwise relevant error
|
||||
[[nodiscard]] HRESULT ROW::Resize(const unsigned short width)
|
||||
{
|
||||
RETURN_IF_FAILED(_charRow.Resize(width));
|
||||
_data._data.resize(width, L' ');
|
||||
auto oldEnd{ _data._cwid.size() };
|
||||
_data._cwid.resize_trailing_extent(width);
|
||||
if (width > oldEnd)
|
||||
{
|
||||
_data._cwid.replace(oldEnd, width, { 1, gsl::narrow_cast<uint16_t>(width - oldEnd) });
|
||||
}
|
||||
try
|
||||
{
|
||||
_attrRow.Resize(width);
|
||||
_data._attrRow.Resize(width);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
_rowWidth = width;
|
||||
_data._width = width;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -80,20 +87,12 @@ bool ROW::Reset(const TextAttribute Attr)
|
||||
// - <none>
|
||||
void ROW::ClearColumn(const size_t column)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _charRow.size());
|
||||
_charRow.ClearCell(column);
|
||||
}
|
||||
|
||||
UnicodeStorage& ROW::GetUnicodeStorage() noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
}
|
||||
|
||||
const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data._width);
|
||||
WriteGlyphAtMeasured(column, 1, L" ");
|
||||
}
|
||||
|
||||
#if 0
|
||||
TODO (DH)
|
||||
// Routine Description:
|
||||
// - writes cell data to the row
|
||||
// Arguments:
|
||||
@@ -105,11 +104,11 @@ const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
|
||||
// - iterator to first cell that was not written to this row.
|
||||
OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap, std::optional<size_t> limitRight)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, index >= _charRow.size());
|
||||
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _charRow.size());
|
||||
THROW_HR_IF(E_INVALIDARG, index >= _data._width);
|
||||
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _data._width);
|
||||
|
||||
// If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row.
|
||||
const auto finalColumnInRow = limitRight.value_or(_charRow.size() - 1);
|
||||
const auto finalColumnInRow = limitRight.value_or(_data._width - 1);
|
||||
|
||||
auto currentColor = it->TextAttr();
|
||||
uint16_t colorUses = 0;
|
||||
@@ -131,7 +130,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
||||
{
|
||||
// Otherwise, commit this color into the run and save off the new one.
|
||||
// Now commit the new color runs into the attr row.
|
||||
_attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
_data._attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
currentColor = it->TextAttr();
|
||||
colorUses = 1;
|
||||
colorStarts = currentIndex;
|
||||
@@ -151,21 +150,31 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
||||
// Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
|
||||
if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
|
||||
{
|
||||
_charRow.ClearCell(currentIndex);
|
||||
ClearColumn(currentIndex);
|
||||
it.AddCellDistanceFault(1); // we couldn't fit a cell here but we skipped a column :|
|
||||
}
|
||||
// If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
|
||||
// Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
|
||||
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
|
||||
{
|
||||
_charRow.ClearCell(currentIndex);
|
||||
ClearColumn(currentIndex);
|
||||
it.AddCellDistanceFault(1); // we couldn't fit a cell here but we skipped a column :|
|
||||
SetDoubleBytePadded(true);
|
||||
}
|
||||
// Otherwise, copy the data given and increment the iterator.
|
||||
else
|
||||
{
|
||||
_charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
|
||||
_charRow.GlyphAt(currentIndex) = it->Chars();
|
||||
++it;
|
||||
if (!it->DbcsAttr().IsTrailing())
|
||||
{
|
||||
uint16_t d = it->DbcsAttr().IsSingle() ? 1 : 2;
|
||||
WriteGlyphAtMeasured(currentIndex, d, it->Chars());
|
||||
currentIndex += d - 1;
|
||||
while (d > 0)
|
||||
{ // TODO(DH) FFS
|
||||
++it;
|
||||
--d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're asked to (un)set the wrap status and we just filled the last column with some text...
|
||||
@@ -191,8 +200,9 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
||||
// Now commit the final color into the attr row
|
||||
if (colorUses)
|
||||
{
|
||||
_attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
_data._attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -23,18 +23,157 @@ Revision History:
|
||||
#include "AttrRow.hpp"
|
||||
#include "LineRendition.hpp"
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellIterator.hpp"
|
||||
#include "CharRow.hpp"
|
||||
#include "UnicodeStorage.hpp"
|
||||
#include "unicode.hpp"
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4267)
|
||||
class TextBuffer;
|
||||
|
||||
using RowMeasurementBuffer = til::small_rle<uint8_t, uint16_t, 3>;
|
||||
|
||||
struct DamageRanges
|
||||
{
|
||||
size_t dataOffset;
|
||||
size_t dataLength;
|
||||
uint16_t firstColumn;
|
||||
uint16_t lastColumnExclusive;
|
||||
};
|
||||
|
||||
template<typename TRuns>
|
||||
static DamageRanges DamageRangesForColumnInMeasurementBuffer(const TRuns& cwid, size_t col)
|
||||
{
|
||||
size_t currentCol{ 0 };
|
||||
size_t currentWchar{ 0 };
|
||||
auto it{ cwid.runs().cbegin() };
|
||||
while (it != cwid.runs().cend())
|
||||
{
|
||||
// Each compressed pair tells us how many columns x N wchar_t
|
||||
const auto colsCoveredByRun{ it->value * it->length };
|
||||
if (currentCol + colsCoveredByRun > col)
|
||||
{
|
||||
// We want to break out of the loop to manually handle this run, because
|
||||
// we've determined that it is the run that covers the column of interest.
|
||||
break;
|
||||
}
|
||||
currentCol += colsCoveredByRun;
|
||||
currentWchar += it->length;
|
||||
it++;
|
||||
}
|
||||
|
||||
if (it == cwid.runs().cend())
|
||||
{
|
||||
// this is an interesting case- somebody requested a column we cannot answer for.
|
||||
// The string might actually have data, and the caller might be interested in where that data is.
|
||||
// Ideally, we would return the index of the first char out-of-bounds, and the length of the remaining data as a single unit.
|
||||
// We can't answer for how much space it takes up, though.
|
||||
__debugbreak();
|
||||
return { 0, 0, 0u, 0u };
|
||||
//return { currentWchar, _data.size() - currentWchar, 0u, 0u };
|
||||
}
|
||||
// currentWchar is how many wchar_t we are into the string before processing this run
|
||||
// currentCol is how many columns we've covered before processing this run
|
||||
|
||||
// We are *guaranteed* that the hit is in this run -- no need to check it->length
|
||||
// col-currentCol is how many columns are left unaccounted for (how far into this run we need to go)
|
||||
const auto colsLeftToCountInCurrentRun{ col - currentCol };
|
||||
currentWchar += colsLeftToCountInCurrentRun / it->value; // one wch per column unit -- rounds down (correct behavior)
|
||||
|
||||
size_t lenInWchars{ 1 }; // the first hit takes up one wchar
|
||||
|
||||
// We use this to determine if we have exhausted every column this run can cough up.
|
||||
// colsLeftToCountInCurrentRun is 0-indexed, but colsConsumedByRun is 1-indexed (index 0 consumes N columns, etc.)
|
||||
// therefore, we reindex colsLeftToCountInCurrentRun and compare it to colsConsumedByRun
|
||||
const auto colsConsumedFromRun{ colsLeftToCountInCurrentRun + it->value };
|
||||
const auto colsCoveredByRun{ it->value * it->length };
|
||||
// If we *have* consumed every column this run can provide, we must check the run after it:
|
||||
// if it contributes "0" columns, it is actually a set of trailing code units.
|
||||
if (colsConsumedFromRun >= colsCoveredByRun && it != cwid.runs().cend())
|
||||
{
|
||||
const auto nextRunIt{ it + 1 };
|
||||
if (nextRunIt != cwid.runs().cend() && nextRunIt->value == 0)
|
||||
{
|
||||
// we were at the boundary of a column run, so if the next one is 0 it tells us that each
|
||||
// wchar after it is a trailer
|
||||
lenInWchars += nextRunIt->length;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentWchar, // wchar start
|
||||
lenInWchars, // wchar size
|
||||
gsl::narrow_cast<uint16_t>(col - (colsLeftToCountInCurrentRun % it->value)), // Column damage to the left (where we overlapped the right of a wide glyph)
|
||||
gsl::narrow_cast<uint16_t>(col - (colsLeftToCountInCurrentRun % it->value) + it->value), // Column damage to the right (where we overlapped the left of a wide glyph)
|
||||
};
|
||||
}
|
||||
|
||||
class ROW;
|
||||
struct RowImage
|
||||
{
|
||||
std::wstring _data;
|
||||
RowMeasurementBuffer _cwid;
|
||||
ATTR_ROW _attrRow;
|
||||
uint16_t _width;
|
||||
friend class ROW;
|
||||
friend class TextBuffer;
|
||||
RowImage() :
|
||||
_data{}, _cwid{}, _attrRow{ {} }, _width{ 0 } {}
|
||||
RowImage(const std::wstring& data, const RowMeasurementBuffer& cwid, ATTR_ROW attrRow, uint16_t width):
|
||||
_data{data}, _cwid{cwid}, _attrRow{std::move(attrRow)}, _width{width} {}
|
||||
|
||||
// exclusive
|
||||
std::tuple<RowImage, RowImage> split(uint16_t col) const
|
||||
{
|
||||
if (col >= _width)
|
||||
{
|
||||
return { *this, RowImage{} };
|
||||
}
|
||||
else if (col == 0)
|
||||
{
|
||||
return { RowImage{}, *this };
|
||||
}
|
||||
auto yes_more_fucking_damage_ranges = DamageRangesForColumnInMeasurementBuffer(_cwid, col);
|
||||
// here's a dumb decision: when you split along a wide char, you get spaces over its damage on the left side.
|
||||
// X X XX X X X
|
||||
// ^ split
|
||||
// X X S <- left
|
||||
// XX X X X <- right
|
||||
auto chopped_off = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? 0 : /*didn't get whole thing*/ yes_more_fucking_damage_ranges.lastColumnExclusive - col;
|
||||
auto chopped_on = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? yes_more_fucking_damage_ranges.dataLength : 0;
|
||||
auto lwid = _cwid.slice(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on);
|
||||
for (auto z{ chopped_off }; z > 0; --z)
|
||||
{
|
||||
lwid.append(1);
|
||||
}
|
||||
RowImage left{
|
||||
_data.substr(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on) + std::wstring(chopped_off, UNICODE_SPACE),
|
||||
lwid,
|
||||
_attrRow._data.slice(0, yes_more_fucking_damage_ranges.lastColumnExclusive),
|
||||
col
|
||||
};
|
||||
RowImage right{
|
||||
_data.substr(yes_more_fucking_damage_ranges.dataOffset + chopped_on),
|
||||
_cwid.slice(yes_more_fucking_damage_ranges.dataOffset + chopped_on, _data.length()),
|
||||
// we use first col here to catch the overlap
|
||||
_attrRow._data.slice(yes_more_fucking_damage_ranges.firstColumn, _width),
|
||||
::base::ClampSub(_width, col),
|
||||
};
|
||||
return { left, right };
|
||||
}
|
||||
};
|
||||
|
||||
enum class DelimiterClass
|
||||
{
|
||||
ControlChar,
|
||||
DelimiterChar,
|
||||
RegularChar
|
||||
};
|
||||
|
||||
class ROW final
|
||||
{
|
||||
public:
|
||||
ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent);
|
||||
|
||||
size_t size() const noexcept { return _rowWidth; }
|
||||
size_t size() const noexcept { return _data._width; }
|
||||
|
||||
void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; }
|
||||
bool WasWrapForced() const noexcept { return _wrapForced; }
|
||||
@@ -42,52 +181,357 @@ public:
|
||||
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept { _doubleBytePadded = doubleBytePadded; }
|
||||
bool WasDoubleBytePadded() const noexcept { return _doubleBytePadded; }
|
||||
|
||||
const CharRow& GetCharRow() const noexcept { return _charRow; }
|
||||
CharRow& GetCharRow() noexcept { return _charRow; }
|
||||
|
||||
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
|
||||
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
|
||||
const ATTR_ROW& GetAttrRow() const noexcept { return _data._attrRow; }
|
||||
ATTR_ROW& GetAttrRow() noexcept { return _data._attrRow; }
|
||||
|
||||
LineRendition GetLineRendition() const noexcept { return _lineRendition; }
|
||||
void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; }
|
||||
|
||||
SHORT GetId() const noexcept { return _id; }
|
||||
void SetId(const SHORT id) noexcept { _id = id; }
|
||||
|
||||
bool Reset(const TextAttribute Attr);
|
||||
[[nodiscard]] HRESULT Resize(const unsigned short width);
|
||||
|
||||
void ClearColumn(const size_t column);
|
||||
std::wstring GetText() const { return _charRow.GetText(); }
|
||||
|
||||
UnicodeStorage& GetUnicodeStorage() noexcept;
|
||||
const UnicodeStorage& GetUnicodeStorage() const noexcept;
|
||||
|
||||
OutputCellIterator WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap = std::nullopt, std::optional<size_t> limitRight = std::nullopt);
|
||||
std::wstring GetText() const { return _data._data; }
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept;
|
||||
friend class RowTests;
|
||||
#endif
|
||||
|
||||
struct RowData
|
||||
{
|
||||
std::wstring _data;
|
||||
RowMeasurementBuffer _cwid;
|
||||
ATTR_ROW _attrRow;
|
||||
unsigned short _width;
|
||||
|
||||
DamageRanges _damageForColumn(size_t col) const
|
||||
{
|
||||
return DamageRangesForColumnInMeasurementBuffer(_cwid, col);
|
||||
}
|
||||
|
||||
DamageRanges _damageForColumns(size_t col, size_t ncols) const
|
||||
{
|
||||
// When we want to replace a column, or set of columns, with a glyph, we need to:
|
||||
// * Figure out the physical extent of the character in that cell (UTF-16 code units).
|
||||
// * Figure out the columnar extent of the character in that cell (how many columns it covers).
|
||||
// * In the simple case (1->1, 2->2), there will be no damage.
|
||||
// * In the complex case (2->1, 1->2, 2->2 with middle overlap), there *WILL* be damage.
|
||||
// * Replace the physical character data in that cell with the new character data.
|
||||
// * Insert padding characters to the left and right to account for damage.
|
||||
//
|
||||
// ## DAMAGE
|
||||
// Damage is measured in the number of columns to the left
|
||||
// and right of the new glyph that are now NO LONGER VALID because
|
||||
// they were double-width characters that are being cut in half,
|
||||
// or single-width characters that are collateral damage from stomping
|
||||
// them with a double-width character.
|
||||
auto damage{ _damageForColumn(col) };
|
||||
const auto lastDamage{ _damageForColumn(col + ncols - 1 /*inclusive*/) };
|
||||
|
||||
// *INVARIANT* the beginning of the next column range must have a different beginning byte
|
||||
// This column began at a different data index, so we have to delete its data too.
|
||||
// Since it's contiguous, just increment len.
|
||||
damage.dataLength = lastDamage.dataOffset + lastDamage.dataLength - damage.dataOffset;
|
||||
damage.lastColumnExclusive = lastDamage.lastColumnExclusive;
|
||||
|
||||
return damage;
|
||||
}
|
||||
|
||||
void _strikeDamageAndAdjust(size_t col, size_t ncols, size_t incomingCodeUnitCount, DamageRanges& range)
|
||||
{
|
||||
(void)incomingCodeUnitCount;
|
||||
const bool damaged{ range.firstColumn < col || col + ncols < range.lastColumnExclusive };
|
||||
if (damaged)
|
||||
{
|
||||
const auto damagedColumns{ range.lastColumnExclusive - range.firstColumn };
|
||||
_data.replace(range.dataOffset, range.dataLength, damagedColumns, UNICODE_SPACE);
|
||||
_cwid.replace(range.dataOffset, range.dataOffset + range.dataLength, { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(damagedColumns) });
|
||||
// We may have replaced surrogate pairs/etc with fewer/more code units.
|
||||
range.dataLength = damagedColumns;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
CharRow _charRow;
|
||||
ATTR_ROW _attrRow;
|
||||
RowData _data;
|
||||
LineRendition _lineRendition;
|
||||
SHORT _id;
|
||||
unsigned short _rowWidth;
|
||||
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
|
||||
bool _wrapForced;
|
||||
// Occurs when the user runs out of text to support a double byte character and we're forced to the next line
|
||||
bool _doubleBytePadded;
|
||||
TextBuffer* _pParent; // non ownership pointer
|
||||
|
||||
public:
|
||||
std::wstring_view GlyphAt(size_t col) const
|
||||
{
|
||||
const auto lookup{ _data._damageForColumn(col) };
|
||||
return { _data._data.data() + lookup.dataOffset, lookup.dataLength };
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> WriteGlyphAtMeasured(size_t col, size_t ncols, std::wstring_view glyph)
|
||||
{
|
||||
const auto [begin, len, minDamageColumn, maxDamageColumnExclusive]{ _data._damageForColumns(col, ncols) };
|
||||
|
||||
if (minDamageColumn == col && maxDamageColumnExclusive == col + ncols)
|
||||
{
|
||||
// We are only damaging as many columns as we are introducing -- no spillover (!)
|
||||
// We can replace the code units in the data directly, and we can replace the
|
||||
// column counts with [col, 0, 0...] (with as many zeroes as we need to account
|
||||
// for any code units past the first.)
|
||||
_data._data.replace(begin, len, glyph);
|
||||
typename decltype(_data._cwid)::rle_type newRuns[]{
|
||||
{ gsl::narrow_cast<uint8_t>(ncols), 1 },
|
||||
{ 0, gsl::narrow_cast<uint16_t>(glyph.size() - 1) },
|
||||
};
|
||||
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), gsl::make_span(&newRuns[0], glyph.size() == 1 ? 1 : 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are damaging multiple columns -- oops. We need to insert replacement characters
|
||||
// to get us from the leftmost side of the damaged glyph up to the leftmost side of
|
||||
// our newly-inserted region. We also need to insert replacement characters from the
|
||||
// rightmost side of our glyph to the rightmost side of the glyph that was once in
|
||||
// that column.
|
||||
// Left side count : col - minDamageColumn
|
||||
// Right side count: maxDamageColumn - (col + ncols)
|
||||
const auto replacementCodeUnits{ (col - minDamageColumn) + glyph.size() + (maxDamageColumnExclusive - (col + ncols)) };
|
||||
std::wstring replacement(replacementCodeUnits, UNICODE_SPACE);
|
||||
replacement.replace(col - minDamageColumn, glyph.size(), glyph);
|
||||
|
||||
// New advances:
|
||||
// Our glyph and all its trailers
|
||||
// v-----v
|
||||
// [1, ..., 1, X, 0, 0, 1, ..., 1]
|
||||
// ^-------^ ^-------^
|
||||
// Each replacement space char
|
||||
// is one column wide. We have
|
||||
// to insert [1]s for each
|
||||
// damaged column.
|
||||
boost::container::small_vector<typename decltype(_data._cwid)::rle_type, 4> newRuns;
|
||||
if (col - minDamageColumn)
|
||||
{
|
||||
newRuns.emplace_back((uint8_t)1, gsl::narrow_cast<uint16_t>(col - minDamageColumn));
|
||||
}
|
||||
newRuns.emplace_back(gsl::narrow_cast<uint8_t>(ncols), (uint16_t)1);
|
||||
if (glyph.size() > 1)
|
||||
{
|
||||
newRuns.emplace_back((uint8_t)0, gsl::narrow_cast<uint16_t>(glyph.size() - 1)); // trailers
|
||||
}
|
||||
if (maxDamageColumnExclusive - (col + ncols))
|
||||
{
|
||||
newRuns.emplace_back((uint8_t)1, gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - (col + ncols)));
|
||||
}
|
||||
_data._data.replace(begin, len, replacement);
|
||||
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), gsl::make_span(newRuns));
|
||||
}
|
||||
|
||||
// Distance from requested column to final
|
||||
_maxc = std::max(_maxc, maxDamageColumnExclusive);
|
||||
return { begin + glyph.size(), col + ncols };
|
||||
}
|
||||
|
||||
DbcsAttribute DbcsAttrAt(size_t col) const
|
||||
{
|
||||
const auto [begin, len, first, lastE] = _data._damageForColumn(col);
|
||||
if (lastE - first == 1)
|
||||
{
|
||||
// The glyph under this column is only onw column wide.
|
||||
return DbcsAttribute{ DbcsAttribute::Attribute::Single };
|
||||
}
|
||||
else if (first != col)
|
||||
{
|
||||
// The glyph under this column is >1 col wide, and we're bisecting it
|
||||
return DbcsAttribute{ DbcsAttribute::Attribute::Trailing };
|
||||
}
|
||||
else
|
||||
{
|
||||
// The glyph under this column is >1 col wide, and we're at the head
|
||||
return DbcsAttribute{ DbcsAttribute::Attribute::Leading };
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<size_t, uint16_t, uint16_t> WriteStringAtMeasured(uint16_t col, uint16_t colCount, const std::wstring_view& string, const RowMeasurementBuffer& measurements)
|
||||
{
|
||||
size_t incomingLastColumn{ std::min<size_t>(_data._width - col, colCount) };
|
||||
auto incomingLastColumnOffsets{ DamageRangesForColumnInMeasurementBuffer(measurements, incomingLastColumn - 1 /*inclusive*/) };
|
||||
|
||||
auto codeUnitsToConsume{ incomingLastColumnOffsets.dataOffset };
|
||||
auto columnsToConsume{ incomingLastColumn };
|
||||
|
||||
const auto [begin, len, minDamageColumn, maxDamageColumnExclusive]{ _data._damageForColumns(col, incomingLastColumn) };
|
||||
|
||||
// If these don't match, we are cutting a multi-cell glyph.
|
||||
if (incomingLastColumnOffsets.lastColumnExclusive == incomingLastColumn)
|
||||
{
|
||||
// Since they *do* match, we should consume this part of the string too.
|
||||
codeUnitsToConsume += incomingLastColumnOffsets.dataLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only consume up to the final cell (the one we cut in half)
|
||||
columnsToConsume = incomingLastColumnOffsets.firstColumn;
|
||||
// **INVARIANT** we only get here if we had to cut off the incoming text, and that only
|
||||
// happens because we had to clamp the read buffer against our width. This means that the
|
||||
// incoming text definitely had a wide glyph that would not fit against the end of our
|
||||
// buffer.
|
||||
// THEREFORE: col+incomingLastColumn was our final column (exclusive)
|
||||
// which means that maxDamageColumnExclusive can be upgraded to be our width.
|
||||
// OPTIMIZATION: If we mark our last column as damaged, it will automatically get stomped
|
||||
// with spaces.
|
||||
// NO NO NO NO NO NO NO NO NO NO TODO(DH)
|
||||
// We can't do this without changing the damaged buffer region to delete the character
|
||||
// from _data at the same time. Use the non-optimial path.
|
||||
ClearColumn(_data._width - 1);
|
||||
SetDoubleBytePadded(true);
|
||||
// NO - we already marked this column when we calculated damage above
|
||||
//++columnsWrittenAfterInsertionPoint;
|
||||
}
|
||||
|
||||
auto mss = measurements.slice(0, gsl::narrow_cast<uint16_t>(codeUnitsToConsume));
|
||||
if (minDamageColumn == col && maxDamageColumnExclusive == col + incomingLastColumn)
|
||||
{
|
||||
// We are only damaging as many columns as we are introducing -- no spillover (!)
|
||||
// We can replace the code units in the data directly, and we can replace the
|
||||
// column counts with [col, 0, 0...] (with as many zeroes as we need to account
|
||||
// for any code units past the first.)
|
||||
_data._data.replace(begin, len, &*string.cbegin(), codeUnitsToConsume);
|
||||
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), mss.runs());
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are damaging multiple columns -- oops. We need to insert replacement characters
|
||||
// to get us from the leftmost side of the damaged glyph up to the leftmost side of
|
||||
// our newly-inserted region. We also need to insert replacement characters from the
|
||||
// rightmost side of our glyph to the rightmost side of the glyph that was once in
|
||||
// that column.
|
||||
// Left side count : col - minDamageColumn
|
||||
// Right side count: maxDamageColumn - (col + ncols)
|
||||
const auto replacementCodeUnits{ (col - minDamageColumn) + codeUnitsToConsume + (maxDamageColumnExclusive - (col + incomingLastColumn)) };
|
||||
std::wstring replacement(replacementCodeUnits, UNICODE_SPACE);
|
||||
replacement.replace(col - minDamageColumn, codeUnitsToConsume, &*string.cbegin(), codeUnitsToConsume);
|
||||
|
||||
// New advances:
|
||||
// Our glyph and all its trailers
|
||||
// v-----v
|
||||
// [1, ..., 1, X, 0, 0, 1, ..., 1]
|
||||
// ^-------^ ^-------^
|
||||
// Each replacement space char
|
||||
// is one column wide. We have
|
||||
// to insert [1]s for each
|
||||
// damaged column.
|
||||
mss.replace(0, 0, { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(col - minDamageColumn) });
|
||||
mss.replace(mss.size(), mss.size(), { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - (col + incomingLastColumn)) });
|
||||
_data._data.replace(begin, len, replacement);
|
||||
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), mss.runs()); //gsl::make_span(newRuns));
|
||||
}
|
||||
|
||||
return {
|
||||
codeUnitsToConsume,
|
||||
gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - col),
|
||||
gsl::narrow_cast<uint16_t>(columnsToConsume)
|
||||
};
|
||||
}
|
||||
|
||||
size_t Fill(size_t col, size_t count, wchar_t ch, uint8_t w)
|
||||
{
|
||||
const auto charsFitOrRemain = std::min((_data._width - col) / w, count);
|
||||
|
||||
const auto columnsRequired{ charsFitOrRemain * w };
|
||||
auto damage = _data._damageForColumns(col, columnsRequired);
|
||||
// If we are filling over the left/right halves of a character
|
||||
// This is a bit wasteful since it can grow/shrink the buffers and we're about
|
||||
// to do it again, but I was trying to be expedient.
|
||||
_data._strikeDamageAndAdjust(col, columnsRequired, charsFitOrRemain, damage);
|
||||
|
||||
const auto [begin, len, min, max] = damage;
|
||||
_data._data.replace(begin, len, charsFitOrRemain, ch);
|
||||
_data._cwid.replace(begin, begin + len, { w, gsl::narrow_cast<uint16_t>(charsFitOrRemain) });
|
||||
const auto doubleBytePadded{
|
||||
w > 1 // We had a wide glyph...
|
||||
&& max != _data._width // ...and didn't reach the edge
|
||||
&& count > charsFitOrRemain // ...but we had spare characters, so we wanted to
|
||||
};
|
||||
if (doubleBytePadded)
|
||||
{
|
||||
const uint16_t remaining{ gsl::narrow_cast<uint16_t>(_data._width - max) };
|
||||
// overflow: add spaces
|
||||
_data._data.replace(begin + charsFitOrRemain, _data._data.size() - begin + charsFitOrRemain, remaining, UNICODE_SPACE);
|
||||
_data._cwid.replace(begin + charsFitOrRemain, _data._cwid.size(), { uint8_t{ 1u }, gsl::narrow_cast<uint16_t>(remaining) });
|
||||
}
|
||||
if (max == _data._width || doubleBytePadded)
|
||||
{
|
||||
// TODO(DH): Evaluate the above condition
|
||||
// We only want to do this if we touched or near-touched the lat col
|
||||
SetDoubleBytePadded(doubleBytePadded);
|
||||
SetWrapForced(false);
|
||||
}
|
||||
return charsFitOrRemain;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get delimiter class for a position in the char row
|
||||
// - used for double click selection and uia word navigation
|
||||
// Arguments:
|
||||
// - column: column to get text data for
|
||||
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data._width);
|
||||
|
||||
const auto glyph = *GlyphAt(column).begin();
|
||||
if (glyph <= UNICODE_SPACE)
|
||||
{
|
||||
return DelimiterClass::ControlChar;
|
||||
}
|
||||
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
|
||||
{
|
||||
return DelimiterClass::DelimiterChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DelimiterClass::RegularChar;
|
||||
}
|
||||
}
|
||||
|
||||
RowImage Dump(uint16_t left, uint16_t size)
|
||||
{
|
||||
auto [begin, len, min, max] = _data._damageForColumns(left, size);
|
||||
return RowImage{
|
||||
_data._data.substr(begin, len),
|
||||
_data._cwid.slice(begin, begin + len),
|
||||
ATTR_ROW{ _data._attrRow._data.slice(min, max) },
|
||||
::base::MakeClampedNum(max) - min
|
||||
};
|
||||
}
|
||||
|
||||
void Reinsert(uint16_t left, const RowImage& ri)
|
||||
{
|
||||
auto damage{ _data._damageForColumns(left, ri._width) };
|
||||
_data._strikeDamageAndAdjust(left, ri._width, ri._data.size(), damage);
|
||||
_data._data.replace(damage.dataOffset, damage.dataLength, ri._data);
|
||||
_data._cwid.replace(damage.dataOffset, damage.dataOffset + damage.dataLength, ri._cwid.runs());
|
||||
_data._attrRow._data.replace(damage.firstColumn, damage.lastColumnExclusive, ri._attrRow._data.runs());
|
||||
}
|
||||
|
||||
uint16_t _maxc{};
|
||||
size_t MeasureRight() const
|
||||
{
|
||||
return _maxc;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
constexpr bool operator==(const ROW& a, const ROW& b) noexcept
|
||||
{
|
||||
// comparison is only used in the tests; this should suffice.
|
||||
return (a._pParent == b._pParent &&
|
||||
a._id == b._id);
|
||||
return (a._data == b._data &&
|
||||
a._cwid == b._cwid &&
|
||||
a._attrRow == b._attrRow &&
|
||||
a._rowWidth == b._rowWidth &&
|
||||
a._wrapForced == b._wrapForced &&
|
||||
a._doubleBytePadded == b._doubleBytePadded);
|
||||
}
|
||||
#endif
|
||||
#pragma warning(pop)
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "UnicodeStorage.hpp"
|
||||
|
||||
UnicodeStorage::UnicodeStorage() noexcept :
|
||||
_map{}
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - fetches the text associated with key
|
||||
// Arguments:
|
||||
// - key - the key into the storage
|
||||
// Return Value:
|
||||
// - the glyph data associated with key
|
||||
// Note: will throw exception if key is not stored yet
|
||||
const UnicodeStorage::mapped_type& UnicodeStorage::GetText(const key_type key) const
|
||||
{
|
||||
return _map.at(key);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - stores glyph data associated with key.
|
||||
// Arguments:
|
||||
// - key - the key into the storage
|
||||
// - glyph - the glyph data to store
|
||||
void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
|
||||
{
|
||||
_map.insert_or_assign(key, glyph);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - erases key and its associated data from the storage
|
||||
// Arguments:
|
||||
// - key - the key to remove
|
||||
void UnicodeStorage::Erase(const key_type key) noexcept
|
||||
{
|
||||
_map.erase(key);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Remaps all of the stored items to new coordinate positions
|
||||
// based on a bulk rearrangement of row IDs and potential row width resize.
|
||||
// Arguments:
|
||||
// - rowMap - A map of the old row IDs to the new row IDs.
|
||||
// - width - The width of the new row. Remove any items that are beyond the row width.
|
||||
// - Use nullopt if we're not resizing the width of the row, just renumbering the rows.
|
||||
void UnicodeStorage::Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
|
||||
{
|
||||
// Make a temporary map to hold all the new row positioning
|
||||
std::unordered_map<key_type, mapped_type> newMap;
|
||||
|
||||
// Walk through every stored item.
|
||||
for (const auto& pair : _map)
|
||||
{
|
||||
// Extract the old coordinate position
|
||||
const auto oldCoord = pair.first;
|
||||
|
||||
// Only try to short-circuit based on width if we were told it changed
|
||||
// by being given a new width value.
|
||||
if (width.has_value())
|
||||
{
|
||||
// Get the column ID
|
||||
const auto oldColId = oldCoord.X;
|
||||
|
||||
// If the column index is at/beyond the row width, don't bother copying it to the new map.
|
||||
if (oldColId >= width.value())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the row ID from the position as that's what we need to remap
|
||||
const auto oldRowId = oldCoord.Y;
|
||||
|
||||
// Use the mapping given to convert the old row ID to the new row ID
|
||||
const auto mapIter = rowMap.find(oldRowId);
|
||||
|
||||
// If there's no mapping to a new row, don't bother copying it to the new map. The row is gone.
|
||||
if (mapIter == rowMap.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto newRowId = mapIter->second;
|
||||
|
||||
// Generate a new coordinate with the same X as the old one, but a new Y value.
|
||||
const auto newCoord = COORD{ oldCoord.X, newRowId };
|
||||
|
||||
// Put the adjusted coordinate into the map with the original value.
|
||||
newMap.emplace(newCoord, pair.second);
|
||||
}
|
||||
|
||||
// Swap into the stored map, free the temporary when we exit.
|
||||
_map.swap(newMap);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- UnicodeStorage.hpp
|
||||
|
||||
Abstract:
|
||||
- dynamic storage location for glyphs that can't normally fit in the output buffer
|
||||
|
||||
Author(s):
|
||||
- Austin Diviness (AustDi) 02-May-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <climits>
|
||||
|
||||
// std::unordered_map needs help to know how to hash a COORD
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct hash<COORD>
|
||||
{
|
||||
// Routine Description:
|
||||
// - hashes a coord. coord will be hashed by storing the x and y values consecutively in the lower
|
||||
// bits of a size_t.
|
||||
// Arguments:
|
||||
// - coord - the coord to hash
|
||||
// Return Value:
|
||||
// - the hashed coord
|
||||
constexpr size_t operator()(const COORD& coord) const noexcept
|
||||
{
|
||||
size_t retVal = coord.Y;
|
||||
const size_t xCoord = coord.X;
|
||||
retVal |= xCoord << (sizeof(coord.Y) * CHAR_BIT);
|
||||
return retVal;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class UnicodeStorage final
|
||||
{
|
||||
public:
|
||||
using key_type = typename COORD;
|
||||
using mapped_type = typename std::vector<wchar_t>;
|
||||
|
||||
UnicodeStorage() noexcept;
|
||||
|
||||
const mapped_type& GetText(const key_type key) const;
|
||||
|
||||
void StoreGlyph(const key_type key, const mapped_type& glyph);
|
||||
|
||||
void Erase(const key_type key) noexcept;
|
||||
|
||||
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
|
||||
|
||||
private:
|
||||
std::unordered_map<key_type, mapped_type> _map;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class UnicodeStorageTests;
|
||||
friend class TextBufferTests;
|
||||
#endif
|
||||
};
|
||||
@@ -315,6 +315,11 @@ void Cursor::StartDeferDrawing() noexcept
|
||||
_fDeferCursorRedraw = true;
|
||||
}
|
||||
|
||||
bool Cursor::IsDeferDrawing() noexcept
|
||||
{
|
||||
return _fDeferCursorRedraw;
|
||||
}
|
||||
|
||||
void Cursor::EndDeferDrawing() noexcept
|
||||
{
|
||||
if (_fHaveDeferredCursorRedraw)
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
const COLORREF GetColor() const noexcept;
|
||||
|
||||
void StartDeferDrawing() noexcept;
|
||||
bool IsDeferDrawing() noexcept;
|
||||
void EndDeferDrawing() noexcept;
|
||||
|
||||
void SetHasMoved(const bool fHasMoved) noexcept;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<ClCompile Include="..\AttrRow.cpp" />
|
||||
<ClCompile Include="..\cursor.cpp" />
|
||||
<ClCompile Include="..\OutputCell.cpp" />
|
||||
<ClCompile Include="..\OutputCellIterator.cpp" />
|
||||
<ClCompile Include="..\OutputCellRect.cpp" />
|
||||
<ClCompile Include="..\OutputCellView.cpp" />
|
||||
<ClCompile Include="..\Row.cpp" />
|
||||
@@ -23,22 +22,16 @@
|
||||
<ClCompile Include="..\textBuffer.cpp" />
|
||||
<ClCompile Include="..\textBufferCellIterator.cpp" />
|
||||
<ClCompile Include="..\textBufferTextIterator.cpp" />
|
||||
<ClCompile Include="..\CharRow.cpp" />
|
||||
<ClCompile Include="..\CharRowCell.cpp" />
|
||||
<ClCompile Include="..\CharRowCellReference.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\UnicodeStorage.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\AttrRow.hpp" />
|
||||
<ClInclude Include="..\cursor.h" />
|
||||
<ClInclude Include="..\DbcsAttribute.hpp" />
|
||||
<ClInclude Include="..\ICharRow.hpp" />
|
||||
<ClInclude Include="..\LineRendition.hpp" />
|
||||
<ClInclude Include="..\OutputCell.hpp" />
|
||||
<ClInclude Include="..\OutputCellIterator.hpp" />
|
||||
<ClInclude Include="..\OutputCellRect.hpp" />
|
||||
<ClInclude Include="..\OutputCellView.hpp" />
|
||||
<ClInclude Include="..\Row.hpp" />
|
||||
@@ -48,11 +41,7 @@
|
||||
<ClInclude Include="..\textBuffer.hpp" />
|
||||
<ClInclude Include="..\textBufferCellIterator.hpp" />
|
||||
<ClInclude Include="..\textBufferTextIterator.hpp" />
|
||||
<ClInclude Include="..\CharRow.hpp" />
|
||||
<ClInclude Include="..\CharRowCell.hpp" />
|
||||
<ClInclude Include="..\CharRowCellReference.hpp" />
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
<ClInclude Include="..\UnicodeStorage.hpp" />
|
||||
</ItemGroup>
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "search.h"
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "textBuffer.hpp"
|
||||
#include "../types/inc/Utf16Parser.hpp"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
|
||||
@@ -41,10 +41,6 @@ SOURCES= \
|
||||
..\textBuffer.cpp \
|
||||
..\textBufferCellIterator.cpp \
|
||||
..\textBufferTextIterator.cpp \
|
||||
..\CharRow.cpp \
|
||||
..\CharRowCell.cpp \
|
||||
..\CharRowCellReference.cpp \
|
||||
..\UnicodeStorage.cpp \
|
||||
..\search.cpp \
|
||||
|
||||
INCLUDES= \
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
#include "precomp.h"
|
||||
|
||||
#include "textBuffer.hpp"
|
||||
#include "CharRow.hpp"
|
||||
|
||||
#include "../types/inc/utils.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../types/inc/Utf16Parser.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -35,7 +35,6 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
|
||||
_currentAttributes{ defaultAttributes },
|
||||
_cursor{ cursorSize, *this },
|
||||
_storage{},
|
||||
_unicodeStorage{},
|
||||
_renderTarget{ renderTarget },
|
||||
_size{},
|
||||
_currentHyperlinkId{ 1 },
|
||||
@@ -184,278 +183,6 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport
|
||||
return TextBufferCellIterator(*this, at, limit);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Corrects and enforces consistent double byte character state (KAttrs line) within a row of the text buffer.
|
||||
// - This will take the given double byte information and check that it will be consistent when inserted into the buffer
|
||||
// at the current cursor position.
|
||||
// - It will correct the buffer (by erasing the character prior to the cursor) if necessary to make a consistent state.
|
||||
//Arguments:
|
||||
// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
|
||||
//Return Value:
|
||||
// - True if it is valid to insert a character with the given double byte attributes. False otherwise.
|
||||
bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute)
|
||||
{
|
||||
// To figure out if the sequence is valid, we have to look at the character that comes before the current one
|
||||
const COORD coordPrevPosition = _GetPreviousFromCursor();
|
||||
ROW& prevRow = GetRowByOffset(coordPrevPosition.Y);
|
||||
DbcsAttribute prevDbcsAttr;
|
||||
try
|
||||
{
|
||||
prevDbcsAttr = prevRow.GetCharRow().DbcsAttrAt(coordPrevPosition.X);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fValidSequence = true; // Valid until proven otherwise
|
||||
bool fCorrectableByErase = false; // Can't be corrected until proven otherwise
|
||||
|
||||
// Here's the matrix of valid items:
|
||||
// N = None (single byte)
|
||||
// L = Lead (leading byte of double byte sequence
|
||||
// T = Trail (trailing byte of double byte sequence
|
||||
// Prev Curr Result
|
||||
// N N OK.
|
||||
// N L OK.
|
||||
// N T Fail, uncorrectable. Trailing byte must have had leading before it.
|
||||
// L N Fail, OK with erase. Lead needs trailing pair. Can erase lead to correct.
|
||||
// L L Fail, OK with erase. Lead needs trailing pair. Can erase prev lead to correct.
|
||||
// L T OK.
|
||||
// T N OK.
|
||||
// T L OK.
|
||||
// T T Fail, uncorrectable. New trailing byte must have had leading before it.
|
||||
|
||||
// Check for only failing portions of the matrix:
|
||||
if (prevDbcsAttr.IsSingle() && dbcsAttribute.IsTrailing())
|
||||
{
|
||||
// N, T failing case (uncorrectable)
|
||||
fValidSequence = false;
|
||||
}
|
||||
else if (prevDbcsAttr.IsLeading())
|
||||
{
|
||||
if (dbcsAttribute.IsSingle() || dbcsAttribute.IsLeading())
|
||||
{
|
||||
// L, N and L, L failing cases (correctable)
|
||||
fValidSequence = false;
|
||||
fCorrectableByErase = true;
|
||||
}
|
||||
}
|
||||
else if (prevDbcsAttr.IsTrailing() && dbcsAttribute.IsTrailing())
|
||||
{
|
||||
// T, T failing case (uncorrectable)
|
||||
fValidSequence = false;
|
||||
}
|
||||
|
||||
// If it's correctable by erase, erase the previous character
|
||||
if (fCorrectableByErase)
|
||||
{
|
||||
// Erase previous character into an N type.
|
||||
try
|
||||
{
|
||||
prevRow.ClearColumn(coordPrevPosition.X);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sequence is now N N or N L, which are both okay. Set sequence back to valid.
|
||||
fValidSequence = true;
|
||||
}
|
||||
|
||||
return fValidSequence;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Call before inserting a character into the buffer.
|
||||
// - This will ensure a consistent double byte state (KAttrs line) within the text buffer
|
||||
// - It will attempt to correct the buffer if we're inserting an unexpected double byte character type
|
||||
// and it will pad out the buffer if we're going to split a double byte sequence across two rows.
|
||||
//Arguments:
|
||||
// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
|
||||
//Return Value:
|
||||
// - true if we successfully prepared the buffer and moved the cursor
|
||||
// - false otherwise (out of memory)
|
||||
bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute)
|
||||
{
|
||||
// This function corrects most errors. If this is false, we had an uncorrectable one which
|
||||
// older versions of conhost simply let pass by unflinching.
|
||||
LOG_HR_IF(E_NOT_VALID_STATE, !(_AssertValidDoubleByteSequence(dbcsAttribute))); // Shouldn't be uncorrectable sequences unless something is very wrong.
|
||||
|
||||
bool fSuccess = true;
|
||||
// Now compensate if we don't have enough space for the upcoming double byte sequence
|
||||
// We only need to compensate for leading bytes
|
||||
if (dbcsAttribute.IsLeading())
|
||||
{
|
||||
const auto cursorPosition = GetCursor().GetPosition();
|
||||
const auto lineWidth = GetLineWidth(cursorPosition.Y);
|
||||
|
||||
// If we're about to lead on the last column in the row, we need to add a padding space
|
||||
if (cursorPosition.X == lineWidth - 1)
|
||||
{
|
||||
// set that we're wrapping for double byte reasons
|
||||
auto& row = GetRowByOffset(cursorPosition.Y);
|
||||
row.SetDoubleBytePadded(true);
|
||||
|
||||
// then move the cursor forward and onto the next row
|
||||
fSuccess = IncrementCursor();
|
||||
}
|
||||
}
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer. Writes at the cursor.
|
||||
// Arguments:
|
||||
// - givenIt - Iterator representing output cell data to write
|
||||
// Return Value:
|
||||
// - The final position of the iterator
|
||||
OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt)
|
||||
{
|
||||
const auto& cursor = GetCursor();
|
||||
const auto target = cursor.GetPosition();
|
||||
|
||||
const auto finalIt = Write(givenIt, target);
|
||||
|
||||
return finalIt;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer.
|
||||
// Arguments:
|
||||
// - givenIt - Iterator representing output cell data to write
|
||||
// - target - the row/column to start writing the text to
|
||||
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data
|
||||
// Return Value:
|
||||
// - The final position of the iterator
|
||||
OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt,
|
||||
const COORD target,
|
||||
const std::optional<bool> wrap)
|
||||
{
|
||||
// Make mutable copy so we can walk.
|
||||
auto it = givenIt;
|
||||
|
||||
// Make mutable target so we can walk down lines.
|
||||
auto lineTarget = target;
|
||||
|
||||
// Get size of the text buffer so we can stay in bounds.
|
||||
const auto size = GetSize();
|
||||
|
||||
// While there's still data in the iterator and we're still targeting in bounds...
|
||||
while (it && size.IsInBounds(lineTarget))
|
||||
{
|
||||
// Attempt to write as much data as possible onto this line.
|
||||
// NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line
|
||||
it = WriteLine(it, lineTarget, wrap);
|
||||
|
||||
// Move to the next line down.
|
||||
lineTarget.X = 0;
|
||||
++lineTarget.Y;
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes one line of text to the output buffer.
|
||||
// Arguments:
|
||||
// - givenIt - The iterator that will dereference into cell data to insert
|
||||
// - target - Coordinate targeted within output buffer
|
||||
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data in the iterator.
|
||||
// - limitRight - Optionally restrict the right boundary for writing (e.g. stop writing earlier than the end of line)
|
||||
// Return Value:
|
||||
// - The iterator, but advanced to where we stopped writing. Use to find input consumed length or cells written length.
|
||||
OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
|
||||
const COORD target,
|
||||
const std::optional<bool> wrap,
|
||||
std::optional<size_t> limitRight)
|
||||
{
|
||||
// If we're not in bounds, exit early.
|
||||
if (!GetSize().IsInBounds(target))
|
||||
{
|
||||
return givenIt;
|
||||
}
|
||||
|
||||
// Get the row and write the cells
|
||||
ROW& row = GetRowByOffset(target.Y);
|
||||
const auto newIt = row.WriteCells(givenIt, target.X, wrap, limitRight);
|
||||
|
||||
// Take the cell distance written and notify that it needs to be repainted.
|
||||
const auto written = newIt.GetCellDistance(givenIt);
|
||||
const Viewport paint = Viewport::FromDimensions(target, { gsl::narrow<SHORT>(written), 1 });
|
||||
_NotifyPaint(paint);
|
||||
|
||||
return newIt;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Inserts one codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
|
||||
//Arguments:
|
||||
// - chars - The codepoint to insert
|
||||
// - dbcsAttribute - Double byte information associated with the codepoint
|
||||
// - bAttr - Color data associated with the character
|
||||
//Return Value:
|
||||
// - true if we successfully inserted the character
|
||||
// - false otherwise (out of memory)
|
||||
bool TextBuffer::InsertCharacter(const std::wstring_view chars,
|
||||
const DbcsAttribute dbcsAttribute,
|
||||
const TextAttribute attr)
|
||||
{
|
||||
// Ensure consistent buffer state for double byte characters based on the character type we're about to insert
|
||||
bool fSuccess = _PrepareForDoubleByteSequence(dbcsAttribute);
|
||||
|
||||
if (fSuccess)
|
||||
{
|
||||
// Get the current cursor position
|
||||
short const iRow = GetCursor().GetPosition().Y; // row stored as logical position, not array position
|
||||
short const iCol = GetCursor().GetPosition().X; // column logical and array positions are equal.
|
||||
|
||||
// Get the row associated with the given logical position
|
||||
ROW& Row = GetRowByOffset(iRow);
|
||||
|
||||
// Store character and double byte data
|
||||
CharRow& charRow = Row.GetCharRow();
|
||||
short const cBufferWidth = GetSize().Width();
|
||||
|
||||
try
|
||||
{
|
||||
charRow.GlyphAt(iCol) = chars;
|
||||
charRow.DbcsAttrAt(iCol) = dbcsAttribute;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store color data
|
||||
fSuccess = Row.GetAttrRow().SetAttrToEnd(iCol, attr);
|
||||
if (fSuccess)
|
||||
{
|
||||
// Advance the cursor
|
||||
fSuccess = IncrementCursor();
|
||||
}
|
||||
}
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Inserts one ucs2 codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
|
||||
//Arguments:
|
||||
// - wch - The codepoint to insert
|
||||
// - dbcsAttribute - Double byte information associated with the codepoint
|
||||
// - bAttr - Color data associated with the character
|
||||
//Return Value:
|
||||
// - true if we successfully inserted the character
|
||||
// - false otherwise (out of memory)
|
||||
bool TextBuffer::InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr)
|
||||
{
|
||||
return InsertCharacter({ &wch, 1 }, dbcsAttribute, attr);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Finds the current row in the buffer (as indicated by the cursor position)
|
||||
// and specifies that we have forced a line wrap on that row
|
||||
@@ -607,7 +334,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
|
||||
|
||||
const auto& currRow = GetRowByOffset(coordEndOfText.Y);
|
||||
// The X position of the end of the valid text is the Right draw boundary (which is one beyond the final valid character)
|
||||
coordEndOfText.X = gsl::narrow<short>(currRow.GetCharRow().MeasureRight()) - 1;
|
||||
coordEndOfText.X = gsl::narrow<short>(currRow.MeasureRight()) - 1;
|
||||
|
||||
// If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text.
|
||||
const auto viewportTop = viewport.Top();
|
||||
@@ -618,7 +345,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
|
||||
const auto& backupRow = GetRowByOffset(coordEndOfText.Y);
|
||||
// We need to back up to the previous row if this line is empty, AND there are more rows
|
||||
|
||||
coordEndOfText.X = gsl::narrow<short>(backupRow.GetCharRow().MeasureRight()) - 1;
|
||||
coordEndOfText.X = gsl::narrow<short>(backupRow.MeasureRight()) - 1;
|
||||
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
|
||||
}
|
||||
|
||||
@@ -778,7 +505,6 @@ void TextBuffer::ScrollRows(const SHORT firstRow, const SHORT size, const SHORT
|
||||
}
|
||||
|
||||
// Renumber the IDs now that we've rearranged where the rows sit within the buffer.
|
||||
// Refreshing should also delegate to the UnicodeStorage to re-key all the stored unicode sequences (where applicable).
|
||||
_RefreshRowIDs(std::nullopt);
|
||||
}
|
||||
|
||||
@@ -815,13 +541,11 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
|
||||
// And if it's no longer single width, the right half of the row should be erased.
|
||||
if (lineRendition != LineRendition::SingleWidth)
|
||||
{
|
||||
const auto fillChar = L' ';
|
||||
auto fillAttrs = GetCurrentAttributes();
|
||||
fillAttrs.SetStandardErase();
|
||||
const size_t fillOffset = GetLineWidth(rowIndex);
|
||||
const size_t fillLength = GetSize().Width() - fillOffset;
|
||||
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
|
||||
row.WriteCells(fillData, fillOffset, false);
|
||||
FillWithCharacterAndAttributeLinear(til::point{ fillOffset, ::base::saturated_cast<size_t>(cursorPosition.Y) }, fillLength, UNICODE_SPACE, fillAttrs);
|
||||
// We also need to make sure the cursor is clamped within the new width.
|
||||
GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
|
||||
}
|
||||
@@ -931,8 +655,7 @@ void TextBuffer::Reset()
|
||||
}
|
||||
|
||||
// Now that we've tampered with the row placement, refresh all the row IDs.
|
||||
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension
|
||||
// and cleanup the UnicodeStorage characters that might fall outside the resized buffer.
|
||||
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension.
|
||||
_RefreshRowIDs(newSize.X);
|
||||
|
||||
// Update the cached size value
|
||||
@@ -943,40 +666,16 @@ void TextBuffer::Reset()
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const UnicodeStorage& TextBuffer::GetUnicodeStorage() const noexcept
|
||||
{
|
||||
return _unicodeStorage;
|
||||
}
|
||||
|
||||
UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
|
||||
{
|
||||
return _unicodeStorage;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Method to help refresh all the Row IDs after manipulating the row
|
||||
// by shuffling pointers around.
|
||||
// - This will also update parent pointers that are stored in depth within the buffer
|
||||
// (e.g. it will update CharRow parents pointing at Rows that might have been moved around)
|
||||
// - Optionally takes a new row width if we're resizing to perform a resize operation and cleanup
|
||||
// any high unicode (UnicodeStorage) runs while we're already looping through the rows.
|
||||
// - Optionally takes a new row width if we're resizing to perform a resize operation
|
||||
// Arguments:
|
||||
// - newRowWidth - Optional new value for the row width.
|
||||
void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
|
||||
{
|
||||
std::unordered_map<SHORT, SHORT> rowMap;
|
||||
SHORT i = 0;
|
||||
for (auto& it : _storage)
|
||||
{
|
||||
// Build a map so we can update Unicode Storage
|
||||
rowMap.emplace(it.GetId(), i);
|
||||
|
||||
// Update the IDs
|
||||
it.SetId(i++);
|
||||
|
||||
// Also update the char row parent pointers as they can get shuffled up in the rotates.
|
||||
it.GetCharRow().UpdateParent(&it);
|
||||
|
||||
// Resize the rows in the X dimension if we have a new width
|
||||
if (newRowWidth.has_value())
|
||||
{
|
||||
@@ -984,9 +683,6 @@ void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
|
||||
THROW_IF_FAILED(it.Resize(newRowWidth.value()));
|
||||
}
|
||||
}
|
||||
|
||||
// Give the new mapping to Unicode Storage
|
||||
_unicodeStorage.Remap(rowMap, newRowWidth);
|
||||
}
|
||||
|
||||
void TextBuffer::_NotifyPaint(const Viewport& viewport) const
|
||||
@@ -1005,27 +701,6 @@ ROW& TextBuffer::_GetFirstRow()
|
||||
return GetRowByOffset(0);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the row that comes before the given row.
|
||||
// - Does not wrap around the screen buffer.
|
||||
// Arguments:
|
||||
// - The current row.
|
||||
// Return Value:
|
||||
// - reference to the previous row
|
||||
// Note:
|
||||
// - will throw exception if called with the first row of the text buffer
|
||||
ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row)
|
||||
{
|
||||
int prevRowIndex = Row.GetId() - 1;
|
||||
if (prevRowIndex < 0)
|
||||
{
|
||||
prevRowIndex = TotalRowCount() - 1;
|
||||
}
|
||||
|
||||
THROW_HR_IF(E_FAIL, Row.GetId() == _firstRow);
|
||||
return _storage.at(prevRowIndex);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves this buffer's current render target.
|
||||
// Arguments:
|
||||
@@ -1047,7 +722,7 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep
|
||||
// - the delimiter class for the given char
|
||||
const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters);
|
||||
return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1650,13 +1325,14 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
|
||||
|
||||
if (!cell.DbcsAttr().IsTrailing())
|
||||
{
|
||||
selectionText.append(cell.Chars());
|
||||
const auto chars = cell.Chars();
|
||||
selectionText.append(chars);
|
||||
|
||||
if (copyTextColor)
|
||||
{
|
||||
const auto cellData = cell.TextAttr();
|
||||
const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData);
|
||||
for (const wchar_t wch : cell.Chars())
|
||||
for (size_t j = 0; j < chars.size(); ++j)
|
||||
{
|
||||
selectionFgAttr.push_back(CellFgAttr);
|
||||
selectionBkAttr.push_back(CellBkAttr);
|
||||
@@ -2138,8 +1814,13 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// Fetch the row and its "right" which is the last printable character.
|
||||
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
|
||||
const short cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
|
||||
const CharRow& charRow = row.GetCharRow();
|
||||
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
|
||||
short iRight = gsl::narrow_cast<short>(row.MeasureRight());
|
||||
if (iRight == 0 && !row.WasWrapForced())
|
||||
{
|
||||
// don't beef it if iRight=0 on iterator
|
||||
newBuffer.NewlineCursor();
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're starting a new row, try and preserve the line rendition
|
||||
// from the row in the original buffer.
|
||||
@@ -2174,33 +1855,26 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through every character in the current row (up to
|
||||
// the "right" boundary, which is one past the final valid
|
||||
// character)
|
||||
for (short iOldCol = 0; iOldCol < iRight; iOldCol++)
|
||||
#if 0
|
||||
OutputCellIterator it{ oldBuffer.GetCellDataAt({ 0, iOldRow }, Viewport::FromDimensions({ 0, iOldRow }, iRight, 1)) };
|
||||
auto preCurPos{ newBuffer.GetCursor().GetPosition() };
|
||||
const auto last = newBuffer.Write(it, preCurPos, true); // true - wrap if we don't fit!
|
||||
const auto distance = last.GetCellDistance(it);
|
||||
#endif
|
||||
auto preCurPos{ newBuffer.GetCursor().GetPosition() };
|
||||
const auto distance = 5;
|
||||
|
||||
if (iOldRow == cOldCursorPos.Y)
|
||||
{
|
||||
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
|
||||
{
|
||||
cNewCursorPos = newCursor.GetPosition();
|
||||
fFoundCursorPos = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
|
||||
const auto glyph = row.GetCharRow().GlyphAt(iOldCol);
|
||||
const auto dbcsAttr = row.GetCharRow().DbcsAttrAt(iOldCol);
|
||||
const auto textAttr = row.GetAttrRow().GetAttrByColumn(iOldCol);
|
||||
|
||||
if (!newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr))
|
||||
{
|
||||
hr = E_OUTOFMEMORY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
cNewCursorPos = preCurPos;
|
||||
// we started at (0, NewY) -- walk forward by the old position's X to figure out where we land in the new line
|
||||
newBuffer.GetSize().MoveInBounds(cOldCursorPos.X, cNewCursorPos);
|
||||
fFoundCursorPos = true;
|
||||
}
|
||||
|
||||
newBuffer.GetSize().MoveInBounds(distance, preCurPos);
|
||||
newBuffer.GetCursor().SetPosition(preCurPos);
|
||||
|
||||
// If we found the old row that the caller was interested in, set the
|
||||
// out value of that parameter to the cursor's current Y position (the
|
||||
// new location of the _end_ of that row in the buffer).
|
||||
@@ -2232,7 +1906,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// Only do so if we were not forced to wrap. If we did
|
||||
// force a word wrap, then the existing line break was
|
||||
// only because we ran out of space.
|
||||
if (iRight < cOldColsTotal && !row.WasWrapForced())
|
||||
if (distance < cOldColsTotal && !row.WasWrapForced())
|
||||
{
|
||||
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
|
||||
{
|
||||
@@ -2563,3 +2237,224 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c
|
||||
PointTree result(std::move(intervals));
|
||||
return result;
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute)
|
||||
{
|
||||
return _WriteRectangular(
|
||||
region,
|
||||
[&](WriteResult& result, auto&& at, auto&& size) {
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attribute);
|
||||
result.columnsWritten += size;
|
||||
});
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithAttributeLinear(til::point at, size_t count, const TextAttribute& attribute)
|
||||
{
|
||||
auto out = _WriteLinear(
|
||||
at,
|
||||
[&](WriteResult& result, auto&& at, auto&& size) -> bool {
|
||||
const auto w{ ::base::ClampMin(::base::saturated_cast<uint16_t>(size), count) };
|
||||
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attribute);
|
||||
|
||||
result.columnsWritten += w.RawValue();
|
||||
|
||||
const Viewport paint = Viewport::FromDimensions(at, { w.Cast<SHORT>(), 1 });
|
||||
_NotifyPaint(paint);
|
||||
|
||||
return (count -= w.RawValue()) > 0; // keep going if data remains
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO(DH): make this a member so that IsGlyphFullWidth can be a member ref
|
||||
static std::tuple<RowMeasurementBuffer, uint16_t> _MeasureStringAndCountColumns(std::wstring_view string) //const noexcept
|
||||
{
|
||||
RowMeasurementBuffer measurements;
|
||||
uint16_t colCount{};
|
||||
while (!string.empty())
|
||||
{
|
||||
auto codepoint = Utf16Parser::ParseNext(string);
|
||||
string = string.substr(codepoint.size());
|
||||
|
||||
uint8_t measurement{ IsGlyphFullWidth(codepoint) ? 2u : 1u };
|
||||
measurements.replace(measurements.size(), measurements.size(), { measurement, gsl::narrow_cast<uint16_t>(1) });
|
||||
if (codepoint.size() > 1)
|
||||
{
|
||||
measurements.replace(measurements.size(), measurements.size(), typename decltype(measurements)::rle_type{ uint8_t{ 0 }, gsl::narrow_cast<uint16_t>(codepoint.size() - 1) });
|
||||
}
|
||||
colCount += measurement;
|
||||
}
|
||||
return { std::move(measurements), colCount };
|
||||
}
|
||||
|
||||
struct no_attributes
|
||||
{
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
TextBuffer::WriteResult TextBuffer::_WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, [[maybe_unused]] const T& attributes)
|
||||
{
|
||||
uint16_t colCount = std::accumulate(
|
||||
measurements.runs().cbegin(),
|
||||
measurements.runs().cend(),
|
||||
uint16_t{},
|
||||
[](auto&& v, auto&& r) -> uint16_t { return v + (r.value * r.length); });
|
||||
const auto out = _WriteLinear(
|
||||
at,
|
||||
[&](WriteResult& result, auto&& at, auto&& /* size */) {
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
const auto [consumedWchar, consumedDestCols, consumedSourceCols] = row.WriteStringAtMeasured(at.x<uint16_t>(), colCount, string, measurements);
|
||||
|
||||
if constexpr (std::is_same<T, TextAttribute>::value)
|
||||
{
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), consumedDestCols), attributes);
|
||||
}
|
||||
#if 0
|
||||
else if constexpr (std::is_same<T, ATTR_ROW::rle_vector>::value)
|
||||
{
|
||||
// user has passed us a packed representation of attributes
|
||||
// It is a programming error to pass one whose total length does
|
||||
// not match the measurement buffer
|
||||
const auto attrRunSlice{ attributes.slice(0, consumedSourceCols) };
|
||||
attrRunSlice.resize_trailing_extent(consumedDestCols); // this may be very wrong. TODO(DH)
|
||||
// intent: I believe that this will take the attribute from the cell before the one we had to cut off
|
||||
// and then extend it to cover our empty column?
|
||||
row.GetAttrRow()._data.replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), consumedSourceCols), attrRunSlice.runs());
|
||||
}
|
||||
#endif
|
||||
|
||||
const Viewport paint = Viewport::FromDimensions(at, { ::base::saturated_cast<SHORT>(consumedDestCols), 1 });
|
||||
_NotifyPaint(paint);
|
||||
colCount -= consumedSourceCols;
|
||||
|
||||
result.charactersRead += consumedWchar;
|
||||
result.charactersWritten += consumedWchar;
|
||||
result.columnsWritten += consumedDestCols;
|
||||
result.columnsRead += consumedSourceCols;
|
||||
|
||||
// unchecked substr
|
||||
//string = std::wstring_view{ &*string.begin() + consumedWchar, string.size() - consumedWchar };
|
||||
//string = til::unchecked_substr(string, consumedWchar);
|
||||
string = string.substr(consumedWchar);
|
||||
measurements = measurements.slice(gsl::narrow_cast<uint16_t>(consumedWchar), measurements.size());
|
||||
|
||||
return !string.empty();
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements)
|
||||
{
|
||||
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), _currentAttributes);
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr)
|
||||
{
|
||||
auto [measurements, colCount] = _MeasureStringAndCountColumns(string);
|
||||
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), attr);
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string)
|
||||
{
|
||||
auto [measurements, colCount] = _MeasureStringAndCountColumns(string);
|
||||
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), no_attributes{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, [[maybe_unused]] const T& attr)
|
||||
{
|
||||
const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } };
|
||||
auto out = _WriteRectangular(
|
||||
region,
|
||||
[&](WriteResult& /* result */, auto&& at, auto&& size) {
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
row.Fill(region.left(), size / width, character, width);
|
||||
if constexpr (std::is_same<T, TextAttribute>::value)
|
||||
{
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attr);
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, [[maybe_unused]] const T& attr)
|
||||
{
|
||||
const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } };
|
||||
return _WriteLinear(
|
||||
at,
|
||||
[&](WriteResult& result, auto&& at, auto&& size) -> bool {
|
||||
// size is how many *columns* we have left to fill, k?
|
||||
const auto w{ ::base::ClampMin(::base::saturated_cast<uint16_t>(size), count * width) };
|
||||
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
const auto consumedWchars = row.Fill(at.x(), count, character, width);
|
||||
if constexpr (std::is_same<T, TextAttribute>::value)
|
||||
{
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), w), attr);
|
||||
}
|
||||
|
||||
result.charactersWritten += consumedWchars;
|
||||
result.columnsWritten += w.RawValue();
|
||||
|
||||
const Viewport paint = Viewport::FromDimensions(at, { w.Cast<SHORT>(), 1 });
|
||||
_NotifyPaint(paint);
|
||||
|
||||
return (count -= consumedWchars) > 0; // keep going if data remains
|
||||
});
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character)
|
||||
{
|
||||
return _FillWithCharacterAndAttributeRectangular(region, character, no_attributes{});
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr)
|
||||
{
|
||||
return _FillWithCharacterAndAttributeRectangular(region, character, attr);
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character)
|
||||
{
|
||||
return _FillWithCharacterAndAttributeLinear(at, count, character, no_attributes{});
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr)
|
||||
{
|
||||
return _FillWithCharacterAndAttributeLinear(at, count, character, attr);
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::WriteRowImage(const til::point at, const RowImage& ri)
|
||||
{
|
||||
auto& row{ GetRowByOffset(at.y()) };
|
||||
row.Reinsert(at.x<uint16_t>(), ri);
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename TLambda>
|
||||
TextBuffer::WriteResult TextBuffer::_WriteRectangular(const til::rectangle& rect, TLambda&& thing)
|
||||
{
|
||||
WriteResult out{};
|
||||
for (auto y{ rect.top() }; y < rect.bottom(); ++y)
|
||||
{
|
||||
thing(out, til::point{ rect.left(), y }, rect.width());
|
||||
}
|
||||
_NotifyPaint(Viewport::FromDimensions(rect.origin(), rect.size()));
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename TLambda>
|
||||
TextBuffer::WriteResult TextBuffer::_WriteLinear(const til::point& start, TLambda&& thing)
|
||||
{
|
||||
auto point{ start };
|
||||
WriteResult out{};
|
||||
while (_size.IsInBounds(point) && thing(out, point, _size.Width() - point.x()))
|
||||
{
|
||||
point = { 0, point.y() };
|
||||
};
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ filling in the last row, and updating the screen.
|
||||
#include "cursor.h"
|
||||
#include "Row.hpp"
|
||||
#include "TextAttribute.hpp"
|
||||
#include "UnicodeStorage.hpp"
|
||||
#include "../types/inc/Viewport.hpp"
|
||||
|
||||
#include "../buffer/out/textBufferCellIterator.hpp"
|
||||
@@ -86,19 +85,6 @@ public:
|
||||
TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const;
|
||||
|
||||
// Text insertion functions
|
||||
OutputCellIterator Write(const OutputCellIterator givenIt);
|
||||
|
||||
OutputCellIterator Write(const OutputCellIterator givenIt,
|
||||
const COORD target,
|
||||
const std::optional<bool> wrap = true);
|
||||
|
||||
OutputCellIterator WriteLine(const OutputCellIterator givenIt,
|
||||
const COORD target,
|
||||
const std::optional<bool> setWrap = std::nullopt,
|
||||
const std::optional<size_t> limitRight = std::nullopt);
|
||||
|
||||
bool InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
|
||||
bool InsertCharacter(const std::wstring_view chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
|
||||
bool IncrementCursor();
|
||||
bool NewlineCursor();
|
||||
|
||||
@@ -136,9 +122,6 @@ public:
|
||||
|
||||
[[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept;
|
||||
|
||||
const UnicodeStorage& GetUnicodeStorage() const noexcept;
|
||||
UnicodeStorage& GetUnicodeStorage() noexcept;
|
||||
|
||||
Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept;
|
||||
|
||||
const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const;
|
||||
@@ -200,6 +183,30 @@ public:
|
||||
void CopyPatterns(const TextBuffer& OtherBuffer);
|
||||
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const size_t firstRow, const size_t lastRow) const;
|
||||
|
||||
struct WriteResult
|
||||
{
|
||||
size_t charactersWritten;
|
||||
size_t charactersRead;
|
||||
size_t columnsWritten;
|
||||
size_t columnsRead;
|
||||
};
|
||||
|
||||
WriteResult WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements);
|
||||
|
||||
WriteResult WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr);
|
||||
WriteResult WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string);
|
||||
|
||||
WriteResult FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character);
|
||||
WriteResult FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character);
|
||||
|
||||
WriteResult FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr);
|
||||
WriteResult FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr);
|
||||
|
||||
WriteResult FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute);
|
||||
WriteResult FillWithAttributeLinear(const til::point at, size_t count, const TextAttribute& attribute);
|
||||
|
||||
WriteResult WriteRowImage(const til::point at, const RowImage& ri);
|
||||
|
||||
private:
|
||||
void _UpdateSize();
|
||||
Microsoft::Console::Types::Viewport _size;
|
||||
@@ -210,9 +217,6 @@ private:
|
||||
|
||||
TextAttribute _currentAttributes;
|
||||
|
||||
// storage location for glyphs that can't fit into the buffer normally
|
||||
UnicodeStorage _unicodeStorage;
|
||||
|
||||
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
|
||||
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
|
||||
uint16_t _currentHyperlinkId;
|
||||
@@ -230,12 +234,7 @@ private:
|
||||
|
||||
void _NotifyPaint(const Microsoft::Console::Types::Viewport& viewport) const;
|
||||
|
||||
// Assist with maintaining proper buffer state for Double Byte character sequences
|
||||
bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
|
||||
bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
|
||||
|
||||
ROW& _GetFirstRow();
|
||||
ROW& _GetPrevRowNoWrap(const ROW& row);
|
||||
|
||||
void _ExpandTextRow(SMALL_RECT& selectionRow) const;
|
||||
|
||||
@@ -247,6 +246,21 @@ private:
|
||||
|
||||
void _PruneHyperlinks();
|
||||
|
||||
template<typename TLambda>
|
||||
WriteResult _WriteRectangular(const til::rectangle& rect, TLambda&& thing);
|
||||
|
||||
template<typename TLambda>
|
||||
WriteResult _WriteLinear(const til::point& start, TLambda&& thing);
|
||||
|
||||
template<typename T>
|
||||
WriteResult _FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const T& attr);
|
||||
|
||||
template<typename T>
|
||||
WriteResult _FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const T& attr);
|
||||
|
||||
template<typename T>
|
||||
WriteResult _WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, const T& attributes);
|
||||
|
||||
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
|
||||
size_t _currentPatternId;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "textBufferCellIterator.hpp"
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "textBuffer.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/viewport.hpp"
|
||||
@@ -94,20 +93,92 @@ bool TextBufferCellIterator::operator!=(const TextBufferCellIterator& it) const
|
||||
// - Reference to self after movement.
|
||||
TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& movement)
|
||||
{
|
||||
// Note that this method is called intensively when the terminal is under heavy load.
|
||||
// We need to aggressively optimize it, comparing to the -= operator.
|
||||
ptrdiff_t move = movement;
|
||||
auto newPos = _pos;
|
||||
while (move > 0 && !_exceeded)
|
||||
if (move < 0)
|
||||
{
|
||||
_exceeded = !_bounds.IncrementInBounds(newPos);
|
||||
// Early branching to leave the rare case to -= operator.
|
||||
// This helps reducing the instruction count within this method, which is good for instruction cache.
|
||||
return *this -= -move;
|
||||
}
|
||||
|
||||
// The remaining code in this function is functionally equivalent to:
|
||||
// auto newPos = _pos;
|
||||
// while (move > 0 && !_exceeded)
|
||||
// {
|
||||
// _exceeded = !_bounds.IncrementInBounds(newPos);
|
||||
// move--;
|
||||
// }
|
||||
// _SetPos(newPos);
|
||||
//
|
||||
// _SetPos() necessitates calling _GenerateView() and thus the construction
|
||||
// of a new OutputCellView(). This has a high performance impact (ICache spill?).
|
||||
// The code below inlines _bounds.IncrementInBounds as well as SetPos.
|
||||
// In the hot path (_pos.Y doesn't change) we modify the _view directly.
|
||||
|
||||
// Hoist these integers which will be used frequently later.
|
||||
const auto boundsRightInclusive = _bounds.RightInclusive();
|
||||
const auto boundsLeft = _bounds.Left();
|
||||
const auto boundsBottomInclusive = _bounds.BottomInclusive();
|
||||
const auto boundsTop = _bounds.Top();
|
||||
const auto oldX = _pos.X;
|
||||
const auto oldY = _pos.Y;
|
||||
|
||||
// Under MSVC writing the individual members of a COORD generates worse assembly
|
||||
// compared to having them be local variables. This causes a performance impact.
|
||||
auto newX = oldX;
|
||||
auto newY = oldY;
|
||||
|
||||
while (move > 0)
|
||||
{
|
||||
if (newX == boundsRightInclusive)
|
||||
{
|
||||
newX = boundsLeft;
|
||||
newY++;
|
||||
if (newY > boundsBottomInclusive)
|
||||
{
|
||||
newY = boundsTop;
|
||||
_exceeded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newX++;
|
||||
_exceeded = false;
|
||||
}
|
||||
move--;
|
||||
}
|
||||
while (move < 0 && !_exceeded)
|
||||
|
||||
if (_exceeded)
|
||||
{
|
||||
_exceeded = !_bounds.DecrementInBounds(newPos);
|
||||
move++;
|
||||
// Early return because nothing needs to be done here.
|
||||
return *this;
|
||||
}
|
||||
_SetPos(newPos);
|
||||
return (*this);
|
||||
|
||||
if (newY == oldY)
|
||||
{
|
||||
// hot path
|
||||
const auto diff = gsl::narrow_cast<ptrdiff_t>(newX) - gsl::narrow_cast<ptrdiff_t>(oldX);
|
||||
_attrIter += diff;
|
||||
_view.UpdateTextAttribute(*_attrIter);
|
||||
|
||||
_view.UpdateText(_pRow->GlyphAt(newX));
|
||||
_view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX));
|
||||
_pos.X = newX;
|
||||
}
|
||||
else
|
||||
{
|
||||
// cold path (_GenerateView is slow)
|
||||
_pRow = s_GetRow(_buffer, { newX, newY });
|
||||
_attrIter = _pRow->GetAttrRow().cbegin() + newX;
|
||||
_pos.X = newX;
|
||||
_pos.Y = newY;
|
||||
_GenerateView();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -118,7 +189,22 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
|
||||
// - Reference to self after movement.
|
||||
TextBufferCellIterator& TextBufferCellIterator::operator-=(const ptrdiff_t& movement)
|
||||
{
|
||||
return this->operator+=(-movement);
|
||||
ptrdiff_t move = movement;
|
||||
if (move < 0)
|
||||
{
|
||||
return (*this) += (-move);
|
||||
}
|
||||
|
||||
auto newPos = _pos;
|
||||
while (move > 0 && !_exceeded)
|
||||
{
|
||||
_exceeded = !_bounds.DecrementInBounds(newPos);
|
||||
move--;
|
||||
}
|
||||
_SetPos(newPos);
|
||||
|
||||
_GenerateView();
|
||||
return (*this);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -238,8 +324,8 @@ const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const COOR
|
||||
// - Updates the internal view. Call after updating row, attribute, or positions.
|
||||
void TextBufferCellIterator::_GenerateView()
|
||||
{
|
||||
_view = OutputCellView(_pRow->GetCharRow().GlyphAt(_pos.X),
|
||||
_pRow->GetCharRow().DbcsAttrAt(_pos.X),
|
||||
_view = OutputCellView(_pRow->GlyphAt(_pos.X),
|
||||
_pRow->DbcsAttrAt(_pos.X),
|
||||
*_attrIter,
|
||||
TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ Author(s):
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "AttrRow.hpp"
|
||||
#include "OutputCellView.hpp"
|
||||
#include "../../types/inc/viewport.hpp"
|
||||
|
||||
class TextBuffer;
|
||||
class ROW;
|
||||
|
||||
class TextBufferCellIterator
|
||||
{
|
||||
@@ -58,8 +58,8 @@ protected:
|
||||
|
||||
const ROW* _pRow;
|
||||
ATTR_ROW::const_iterator _attrIter;
|
||||
const TextBuffer& _buffer;
|
||||
const Microsoft::Console::Types::Viewport _bounds;
|
||||
std::reference_wrapper<const TextBuffer> _buffer;
|
||||
Microsoft::Console::Types::Viewport _bounds;
|
||||
bool _exceeded;
|
||||
COORD _pos;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "textBufferTextIterator.hpp"
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "Row.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -1971,9 +1971,9 @@ namespace SettingsModelLocalTests
|
||||
auto settings = implementation::CascadiaSettings::FromJson(settingsObject);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings->_globals->_actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('a') }));
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('b') }));
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }));
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }));
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
|
||||
for (const auto& warning : settings->_globals->_keybindingsWarnings)
|
||||
{
|
||||
@@ -2124,7 +2124,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('A'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -2141,7 +2141,7 @@ namespace SettingsModelLocalTests
|
||||
Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set.");
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -2155,7 +2155,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -2169,7 +2169,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -2183,7 +2183,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -2841,7 +2841,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
|
||||
const KeyChord expectedKeyChord{ true, false, true, static_cast<int>('W') };
|
||||
const KeyChord expectedKeyChord{ true, false, true, false, static_cast<int>('W'), 0 };
|
||||
{
|
||||
// Verify NameMap returns correct value
|
||||
const auto& cmd{ nameMap.TryLookup(L"foo") };
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace SettingsModelLocalTests
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(KeyChords);
|
||||
TEST_METHOD(ManyKeysSameAction);
|
||||
TEST_METHOD(LayerKeybindings);
|
||||
TEST_METHOD(UnbindKeybindings);
|
||||
@@ -61,6 +62,59 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
};
|
||||
|
||||
void KeyBindingsTests::KeyChords()
|
||||
{
|
||||
struct testCase
|
||||
{
|
||||
VirtualKeyModifiers modifiers;
|
||||
int32_t vkey;
|
||||
int32_t scanCode;
|
||||
std::wstring_view expected;
|
||||
};
|
||||
|
||||
static constexpr std::array testCases{
|
||||
testCase{
|
||||
VirtualKeyModifiers::None,
|
||||
'A',
|
||||
0,
|
||||
L"a",
|
||||
},
|
||||
testCase{
|
||||
VirtualKeyModifiers::Control,
|
||||
'A',
|
||||
0,
|
||||
L"ctrl+a",
|
||||
},
|
||||
testCase{
|
||||
VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift,
|
||||
VK_OEM_PLUS,
|
||||
0,
|
||||
L"ctrl+shift+plus",
|
||||
},
|
||||
testCase{
|
||||
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
|
||||
255,
|
||||
0,
|
||||
L"ctrl+shift+alt+win+vk(255)",
|
||||
},
|
||||
testCase{
|
||||
VirtualKeyModifiers::Windows,
|
||||
0,
|
||||
123,
|
||||
L"ctrl+shift+alt+win+sc(123)",
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& tc : testCases)
|
||||
{
|
||||
KeyChord expectedKeyChord{ tc.modifiers, tc.vkey, tc.scanCode };
|
||||
const auto actualString = KeyChordSerialization::ToString(expectedKeyChord);
|
||||
VERIFY_ARE_EQUAL(tc.expected, actualString);
|
||||
const auto actualKeyChord = KeyChordSerialization::FromString(actualString);
|
||||
VERIFY_ARE_EQUAL(expectedKeyChord, actualKeyChord);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::ManyKeysSameAction()
|
||||
{
|
||||
const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
|
||||
@@ -75,7 +129,6 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
@@ -99,7 +152,6 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
@@ -129,7 +181,6 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings5Json = VerifyParseSucceeded(bindings5String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
@@ -142,7 +193,7 @@ namespace SettingsModelLocalTests
|
||||
L"Try unbinding a key using `\"unbound\"` to unbind the key"));
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key using `null` to unbind the key"));
|
||||
@@ -152,7 +203,7 @@ namespace SettingsModelLocalTests
|
||||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings3Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key using an unrecognized command to unbind the key"));
|
||||
@@ -162,7 +213,7 @@ namespace SettingsModelLocalTests
|
||||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings4Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key using a straight up invalid value to unbind the key"));
|
||||
@@ -172,13 +223,13 @@ namespace SettingsModelLocalTests
|
||||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings5Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key that wasn't bound at all"));
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestArbitraryArgs()
|
||||
@@ -203,7 +254,6 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size());
|
||||
@@ -211,10 +261,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` without args parses as Copy(SingleLine=false)"));
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
@@ -222,10 +271,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` with args parses them correctly"));
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('C') };
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('C'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
@@ -233,10 +281,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` with args parses them correctly"));
|
||||
KeyChord kc{ false, true, true, static_cast<int32_t>('C') };
|
||||
KeyChord kc{ false, true, true, false, static_cast<int32_t>('C'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_TRUE(realArgs.SingleLine());
|
||||
}
|
||||
@@ -244,11 +291,10 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `newTab` without args parses as NewTab(Index=null)"));
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('T') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('T'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_NULL(realArgs.TerminalArgs().ProfileIndex());
|
||||
@@ -256,11 +302,10 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `newTab` parses args correctly"));
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('T') };
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('T'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
|
||||
@@ -270,11 +315,10 @@ namespace SettingsModelLocalTests
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `newTab` with an index greater than the legacy "
|
||||
L"args afforded parses correctly"));
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('Y') };
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('Y'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
|
||||
@@ -284,11 +328,10 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` ignores args it doesn't understand"));
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
@@ -296,11 +339,10 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` null as it's `args` parses as the default option"));
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
@@ -308,11 +350,10 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `adjustFontSize` with a positive delta parses args correctly"));
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(1, realArgs.Delta());
|
||||
}
|
||||
@@ -320,11 +361,10 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `adjustFontSize` with a negative delta parses args correctly"));
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(-1, realArgs.Delta());
|
||||
}
|
||||
@@ -342,44 +382,39 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
@@ -396,37 +431,33 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TabColor());
|
||||
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
|
||||
VERIFY_ARE_EQUAL(til::color(0x563412), til::color(realArgs.TabColor().Value()));
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
@@ -441,16 +472,14 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
@@ -470,63 +499,56 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.RowsToScroll());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.RowsToScroll());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.RowsToScroll());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.RowsToScroll());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
|
||||
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_DOWN) };
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_DOWN), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
|
||||
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
|
||||
@@ -535,7 +557,6 @@ namespace SettingsModelLocalTests
|
||||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "scrollDown", "rowsToScroll": -1 } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
@@ -551,26 +572,23 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Forward);
|
||||
}
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Backward);
|
||||
}
|
||||
@@ -584,7 +602,6 @@ namespace SettingsModelLocalTests
|
||||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
@@ -601,35 +618,31 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::CommandLine);
|
||||
}
|
||||
@@ -637,7 +650,6 @@ namespace SettingsModelLocalTests
|
||||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "commandPalette", "launchMode": "bad" } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
@@ -669,7 +681,6 @@ namespace SettingsModelLocalTests
|
||||
};
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
@@ -677,7 +688,7 @@ namespace SettingsModelLocalTests
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) };
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A') }, kbd);
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with args");
|
||||
@@ -688,7 +699,7 @@ namespace SettingsModelLocalTests
|
||||
args->SingleLine(true);
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) };
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B') }, kbd);
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with new terminal args");
|
||||
@@ -700,7 +711,7 @@ namespace SettingsModelLocalTests
|
||||
auto args{ winrt::make_self<implementation::NewTabArgs>(*newTerminalArgs) };
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) };
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C') }, kbd);
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with hidden args");
|
||||
@@ -708,7 +719,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P') }, kbd);
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P'), 0 }, kbd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(12u, actionMapImpl->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('A'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -134,7 +134,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('B') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('B'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -156,7 +156,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -178,7 +178,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -200,7 +200,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -222,7 +222,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -245,7 +245,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
@@ -265,7 +265,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
@@ -287,7 +287,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('I'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
@@ -310,7 +310,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('J') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('J'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
@@ -332,7 +332,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('K') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('K'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
@@ -355,7 +355,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('L') };
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('L'), 0 };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(ParseComboCommandlineIntoArgs);
|
||||
TEST_METHOD(ParseFocusTabArgs);
|
||||
TEST_METHOD(ParseMoveFocusArgs);
|
||||
TEST_METHOD(ParseMovePaneArgs);
|
||||
TEST_METHOD(ParseArgumentsWithParsingTerminators);
|
||||
TEST_METHOD(ParseFocusPaneArgs);
|
||||
|
||||
@@ -1207,6 +1208,124 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::ParseMovePaneArgs()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `mp` instead of `move-pane`");
|
||||
const wchar_t* subcommand = useShortForm ? L"mp" : L"move-pane";
|
||||
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Just the subcommand, without a direction, should fail."));
|
||||
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"right" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"up" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Up, myArgs.Direction());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"down" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Down, myArgs.Direction());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"badDirection" };
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"move-pane with an invalid direction should fail."));
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left", L";", subcommand, L"right" };
|
||||
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
|
||||
|
||||
actionAndArgs = appArgs._startupActions.at(2);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::ParseFocusPaneArgs()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
|
||||
@@ -82,6 +82,8 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(MoveFocusFromZoomedPane);
|
||||
TEST_METHOD(CloseZoomedPane);
|
||||
|
||||
TEST_METHOD(MovePanes);
|
||||
|
||||
TEST_METHOD(NextMRUTab);
|
||||
TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder);
|
||||
|
||||
@@ -93,6 +95,8 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(TestPreviewDismissScheme);
|
||||
TEST_METHOD(TestPreviewSchemeWhilePreviewing);
|
||||
|
||||
TEST_METHOD(TestClampSwitchToTab);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
return true;
|
||||
@@ -817,6 +821,212 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::MovePanes()
|
||||
{
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Setup 4 panes.");
|
||||
// Create the following layout
|
||||
// -------------------
|
||||
// | 1 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 3 | 4 |
|
||||
// | | |
|
||||
// -------------------
|
||||
uint32_t firstId = 0, secondId = 0, thirdId = 0, fourthId = 0;
|
||||
TestOnUIThread([&]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
firstId = tab->_activePane->Id().value();
|
||||
// We start with 1 tab, split vertically to get
|
||||
// -------------------
|
||||
// | 1 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
page->_SplitPane(SplitState::Vertical, SplitType::Duplicate, 0.5f, nullptr);
|
||||
secondId = tab->_activePane->Id().value();
|
||||
});
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
// After this the `2` pane is focused, go back to `1` being focused
|
||||
page->_MoveFocus(FocusDirection::Left);
|
||||
});
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
// Split again to make the 3rd tab
|
||||
// -------------------
|
||||
// | 1 | |
|
||||
// | | |
|
||||
// ---------| 2 |
|
||||
// | 3 | |
|
||||
// | | |
|
||||
// -------------------
|
||||
page->_SplitPane(SplitState::Horizontal, SplitType::Duplicate, 0.5f, nullptr);
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
// Split again to make the 3rd tab
|
||||
thirdId = tab->_activePane->Id().value();
|
||||
});
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
// After this the `3` pane is focused, go back to `2` being focused
|
||||
page->_MoveFocus(FocusDirection::Right);
|
||||
});
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
// Split to create the final pane
|
||||
// -------------------
|
||||
// | 1 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 3 | 4 |
|
||||
// | | |
|
||||
// -------------------
|
||||
page->_SplitPane(SplitState::Horizontal, SplitType::Duplicate, 0.5f, nullptr);
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
fourthId = tab->_activePane->Id().value();
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// just to be complete, make sure we actually have 4 different ids
|
||||
VERIFY_ARE_NOT_EQUAL(firstId, fourthId);
|
||||
VERIFY_ARE_NOT_EQUAL(secondId, fourthId);
|
||||
VERIFY_ARE_NOT_EQUAL(thirdId, fourthId);
|
||||
VERIFY_ARE_NOT_EQUAL(firstId, thirdId);
|
||||
VERIFY_ARE_NOT_EQUAL(secondId, thirdId);
|
||||
VERIFY_ARE_NOT_EQUAL(firstId, secondId);
|
||||
});
|
||||
|
||||
// Gratuitous use of sleep to make sure that the UI has updated properly
|
||||
// after each operation.
|
||||
Sleep(250);
|
||||
// Now try to move the pane through the tree
|
||||
Log::Comment(L"Move pane to the left. This should swap panes 3 and 4");
|
||||
// -------------------
|
||||
// | 1 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 4 | 3 |
|
||||
// | | |
|
||||
// -------------------
|
||||
TestOnUIThread([&]() {
|
||||
// Set up action
|
||||
MovePaneArgs args{ FocusDirection::Left };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleMovePane(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// Our currently focused pane should be `4`
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
|
||||
|
||||
// Inspect the tree to make sure we swapped
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_secondChild->Id().value());
|
||||
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_secondChild->Id().value());
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
Log::Comment(L"Move pane to up. This should swap panes 1 and 4");
|
||||
// -------------------
|
||||
// | 4 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 1 | 3 |
|
||||
// | | |
|
||||
// -------------------
|
||||
TestOnUIThread([&]() {
|
||||
// Set up action
|
||||
MovePaneArgs args{ FocusDirection::Up };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleMovePane(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// Our currently focused pane should be `4`
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
|
||||
|
||||
// Inspect the tree to make sure we swapped
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_firstChild->Id().value());
|
||||
VERIFY_ARE_EQUAL(firstId, tab->_rootPane->_firstChild->_secondChild->Id().value());
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
Log::Comment(L"Move pane to the right. This should swap panes 2 and 4");
|
||||
// -------------------
|
||||
// | 2 | 4 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 1 | 3 |
|
||||
// | | |
|
||||
// -------------------
|
||||
TestOnUIThread([&]() {
|
||||
// Set up action
|
||||
MovePaneArgs args{ FocusDirection::Right };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleMovePane(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// Our currently focused pane should be `4`
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
|
||||
|
||||
// Inspect the tree to make sure we swapped
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_firstChild->Id().value());
|
||||
VERIFY_ARE_EQUAL(secondId, tab->_rootPane->_firstChild->_firstChild->Id().value());
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
Log::Comment(L"Move pane down. This should swap panes 3 and 4");
|
||||
// -------------------
|
||||
// | 2 | 3 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 1 | 4 |
|
||||
// | | |
|
||||
// -------------------
|
||||
TestOnUIThread([&]() {
|
||||
// Set up action
|
||||
MovePaneArgs args{ FocusDirection::Down };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleMovePane(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// Our currently focused pane should be `4`
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
|
||||
|
||||
// Inspect the tree to make sure we swapped
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_secondChild->Id().value());
|
||||
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_firstChild->Id().value());
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::NextMRUTab()
|
||||
{
|
||||
// This is a test for GH#8025 - we want to make sure that we can do both
|
||||
@@ -1342,4 +1552,55 @@ namespace TerminalAppLocalTests
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::TestClampSwitchToTab()
|
||||
{
|
||||
Log::Comment(L"Test that switching to a tab index higher than the number of tabs just clamps to the last tab.");
|
||||
|
||||
auto page = _commonSetup();
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
|
||||
Log::Comment(L"Create a second tab");
|
||||
TestOnUIThread([&page]() {
|
||||
NewTerminalArgs newTerminalArgs{ 1 };
|
||||
page->_OpenNewTab(newTerminalArgs);
|
||||
});
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
|
||||
Log::Comment(L"Create a third tab");
|
||||
TestOnUIThread([&page]() {
|
||||
NewTerminalArgs newTerminalArgs{ 2 };
|
||||
page->_OpenNewTab(newTerminalArgs);
|
||||
});
|
||||
VERIFY_ARE_EQUAL(3u, page->_tabs.Size());
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
|
||||
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
|
||||
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Switch to the first tab");
|
||||
page->_SelectTab(0);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
|
||||
|
||||
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
|
||||
VERIFY_ARE_EQUAL(0u, focusedTabIndexOpt.value());
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Switch to the tab 6, which is greater than number of tabs. This should switch to the third tab");
|
||||
page->_SelectTab(6);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
|
||||
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
|
||||
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -172,7 +172,6 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
|
||||
_desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8 },
|
||||
_actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
|
||||
_uiaProvider{ nullptr },
|
||||
_uiaProviderInitialized{ false },
|
||||
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
|
||||
_pfnWriteCallback{ nullptr },
|
||||
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
|
||||
@@ -324,26 +323,22 @@ void HwndTerminal::_UpdateFont(int newDpi)
|
||||
|
||||
IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
|
||||
{
|
||||
if (nullptr == _uiaProvider && !_uiaProviderInitialized)
|
||||
// If TermControlUiaProvider throws during construction,
|
||||
// we don't want to try constructing an instance again and again.
|
||||
// _uiaProviderInitialized helps us prevent this.
|
||||
if (!_uiaProviderInitialized)
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock;
|
||||
try
|
||||
{
|
||||
#pragma warning(suppress : 26441) // The lock is named, this appears to be a false positive
|
||||
lock = _terminal->LockForWriting();
|
||||
if (_uiaProviderInitialized)
|
||||
{
|
||||
return _uiaProvider.Get();
|
||||
}
|
||||
|
||||
auto lock = _terminal->LockForWriting();
|
||||
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, this->GetUiaData(), this));
|
||||
_uiaProviderInitialized = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
_uiaProvider = nullptr;
|
||||
}
|
||||
_uiaProviderInitialized = true;
|
||||
}
|
||||
|
||||
return _uiaProvider.Get();
|
||||
|
||||
@@ -73,7 +73,6 @@ private:
|
||||
FontInfoDesired _desiredFont;
|
||||
FontInfo _actualFont;
|
||||
int _currentDpi;
|
||||
bool _uiaProviderInitialized;
|
||||
std::function<void(wchar_t*)> _pfnWriteCallback;
|
||||
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
|
||||
|
||||
@@ -83,6 +82,7 @@ private:
|
||||
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
|
||||
|
||||
bool _focused{ false };
|
||||
bool _uiaProviderInitialized{ false };
|
||||
|
||||
std::chrono::milliseconds _multiClickTime;
|
||||
unsigned int _multiClickCounter{};
|
||||
|
||||
@@ -313,6 +313,24 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
|
||||
{
|
||||
if (realArgs.Direction() == FocusDirection::None)
|
||||
{
|
||||
// Do nothing
|
||||
args.Handled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_MovePane(realArgs.Direction());
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleCopyText(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
|
||||
@@ -192,6 +192,7 @@ void AppCommandlineArgs::_buildParser()
|
||||
_buildSplitPaneParser();
|
||||
_buildFocusTabParser();
|
||||
_buildMoveFocusParser();
|
||||
_buildMovePaneParser();
|
||||
_buildFocusPaneParser();
|
||||
}
|
||||
|
||||
@@ -398,6 +399,54 @@ void AppCommandlineArgs::_buildMoveFocusParser()
|
||||
setupSubcommand(_moveFocusShort);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Adds the `move-pane` subcommand and related options to the commandline parser.
|
||||
// - Additionally adds the `mp` subcommand, which is just a shortened version of `move-pane`
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppCommandlineArgs::_buildMovePaneParser()
|
||||
{
|
||||
_movePaneCommand = _app.add_subcommand("move-pane", RS_A(L"CmdMovePaneDesc"));
|
||||
_movePaneShort = _app.add_subcommand("mp", RS_A(L"CmdMPDesc"));
|
||||
|
||||
auto setupSubcommand = [this](auto* subcommand) {
|
||||
std::map<std::string, FocusDirection> map = {
|
||||
{ "left", FocusDirection::Left },
|
||||
{ "right", FocusDirection::Right },
|
||||
{ "up", FocusDirection::Up },
|
||||
{ "down", FocusDirection::Down }
|
||||
};
|
||||
|
||||
auto* directionOpt = subcommand->add_option("direction",
|
||||
_movePaneDirection,
|
||||
RS_A(L"CmdMovePaneDirectionArgDesc"));
|
||||
|
||||
directionOpt->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
|
||||
directionOpt->required();
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
subcommand->callback([&, this]() {
|
||||
if (_movePaneDirection != FocusDirection::None)
|
||||
{
|
||||
MovePaneArgs args{ _movePaneDirection };
|
||||
|
||||
ActionAndArgs actionAndArgs{};
|
||||
actionAndArgs.Action(ShortcutAction::MovePane);
|
||||
actionAndArgs.Args(args);
|
||||
|
||||
_startupActions.push_back(std::move(actionAndArgs));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setupSubcommand(_movePaneCommand);
|
||||
setupSubcommand(_movePaneShort);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Adds the `focus-pane` subcommand and related options to the commandline parser.
|
||||
// - Additionally adds the `fp` subcommand, which is just a shortened version of `focus-pane`
|
||||
@@ -574,6 +623,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
|
||||
*_focusTabShort ||
|
||||
*_moveFocusCommand ||
|
||||
*_moveFocusShort ||
|
||||
*_movePaneCommand ||
|
||||
*_movePaneShort ||
|
||||
*_focusPaneCommand ||
|
||||
*_focusPaneShort ||
|
||||
*_newPaneShort.subcommand ||
|
||||
@@ -607,6 +658,7 @@ void AppCommandlineArgs::_resetStateToDefault()
|
||||
_focusPrevTab = false;
|
||||
|
||||
_moveFocusDirection = FocusDirection::None;
|
||||
_movePaneDirection = FocusDirection::None;
|
||||
|
||||
_focusPaneTarget = -1;
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ private:
|
||||
CLI::App* _focusTabShort;
|
||||
CLI::App* _moveFocusCommand;
|
||||
CLI::App* _moveFocusShort;
|
||||
CLI::App* _movePaneCommand;
|
||||
CLI::App* _movePaneShort;
|
||||
CLI::App* _focusPaneCommand;
|
||||
CLI::App* _focusPaneShort;
|
||||
|
||||
@@ -95,6 +97,7 @@ private:
|
||||
bool _suppressApplicationTitle{ false };
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
|
||||
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _movePaneDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
|
||||
|
||||
// _commandline will contain the command line with which we'll be spawning a new terminal
|
||||
std::vector<std::string> _commandline;
|
||||
@@ -128,6 +131,7 @@ private:
|
||||
void _buildSplitPaneParser();
|
||||
void _buildFocusTabParser();
|
||||
void _buildMoveFocusParser();
|
||||
void _buildMovePaneParser();
|
||||
void _buildFocusPaneParser();
|
||||
bool _noCommandsProvided();
|
||||
void _resetStateToDefault();
|
||||
|
||||
@@ -1129,7 +1129,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Gets the taskbar state value from the last active control
|
||||
// Return Value:
|
||||
// - The taskbar state of the last active control
|
||||
size_t AppLogic::GetLastActiveControlTaskbarState()
|
||||
uint64_t AppLogic::GetLastActiveControlTaskbarState()
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
@@ -1142,7 +1142,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Gets the taskbar progress value from the last active control
|
||||
// Return Value:
|
||||
// - The taskbar progress of the last active control
|
||||
size_t AppLogic::GetLastActiveControlTaskbarProgress()
|
||||
uint64_t AppLogic::GetLastActiveControlTaskbarProgress()
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
|
||||
@@ -89,8 +89,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
size_t GetLastActiveControlTaskbarState();
|
||||
size_t GetLastActiveControlTaskbarProgress();
|
||||
uint64_t GetLastActiveControlTaskbarState();
|
||||
uint64_t GetLastActiveControlTaskbarProgress();
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
|
||||
|
||||
|
||||
@@ -63,23 +63,15 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void ColorPickupFlyout::ShowColorPickerButton_Click(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&)
|
||||
{
|
||||
auto targetType = this->FlyoutPresenterStyle().TargetType();
|
||||
auto s = Windows::UI::Xaml::Style{};
|
||||
s.TargetType(targetType);
|
||||
auto visibility = customColorPanel().Visibility();
|
||||
if (visibility == winrt::Windows::UI::Xaml::Visibility::Collapsed)
|
||||
{
|
||||
customColorPanel().Visibility(winrt::Windows::UI::Xaml::Visibility::Visible);
|
||||
auto setter = Windows::UI::Xaml::Setter(Windows::UI::Xaml::FrameworkElement::MinWidthProperty(), winrt::box_value(540));
|
||||
s.Setters().Append(setter);
|
||||
}
|
||||
else
|
||||
{
|
||||
customColorPanel().Visibility(winrt::Windows::UI::Xaml::Visibility::Collapsed);
|
||||
auto setter = Windows::UI::Xaml::Setter(Windows::UI::Xaml::FrameworkElement::MinWidthProperty(), winrt::box_value(0));
|
||||
s.Setters().Append(setter);
|
||||
}
|
||||
this->FlyoutPresenterStyle(s);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -250,10 +250,12 @@ namespace winrt::TerminalApp::implementation
|
||||
void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
const auto key = e.OriginalKey();
|
||||
const auto scanCode = e.KeyStatus().ScanCode;
|
||||
const auto coreWindow = CoreWindow::GetForCurrentThread();
|
||||
const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
// Some keypresses such as Tab, Return, Esc, and Arrow Keys are ignored by controls because
|
||||
// they're not considered input key presses. While they don't raise KeyDown events,
|
||||
@@ -264,7 +266,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// a really widely used keyboard navigation key.
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode && _actionMap)
|
||||
{
|
||||
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
|
||||
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, false, static_cast<int32_t>(key), static_cast<int32_t>(scanCode) };
|
||||
if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) })
|
||||
{
|
||||
if (cmd.ActionAndArgs().Action() == ShortcutAction::PrevTab)
|
||||
@@ -402,9 +404,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void CommandPalette::_anchorKeyUpHandler()
|
||||
{
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
const auto coreWindow = CoreWindow::GetForCurrentThread();
|
||||
const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
if (!ctrlDown && !altDown && !shiftDown)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
|
||||
_wrappedConnection{ std::move(wrappedConnection) }
|
||||
{
|
||||
}
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) {}
|
||||
~DebugInputTapConnection() = default;
|
||||
void Start()
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
|
||||
{
|
||||
public:
|
||||
explicit DebugTapConnection(Microsoft::Terminal::TerminalConnection::ITerminalConnection wrappedConnection);
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/){};
|
||||
~DebugTapConnection();
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
|
||||
@@ -213,41 +213,6 @@ bool Pane::ResizePane(const ResizeDirection& direction)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to handle moving focus to one of our children. If our split
|
||||
// direction isn't appropriate for the move direction, then we'll return
|
||||
// false, to try and let our parent handle the move. If our child we'd move
|
||||
// focus to is already focused, we'll also return false, to again let our
|
||||
// parent try and handle the focus movement.
|
||||
// Arguments:
|
||||
// - direction: The direction to move the focus in.
|
||||
// Return Value:
|
||||
// - true if we handled this focus move request.
|
||||
bool Pane::_NavigateFocus(const FocusDirection& direction)
|
||||
{
|
||||
if (!DirectionMatchesSplit(direction, _splitState))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool focusSecond = (direction == FocusDirection::Right) || (direction == FocusDirection::Down);
|
||||
|
||||
const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild;
|
||||
|
||||
// If the child we want to move focus to is _already_ focused, return false,
|
||||
// to try and let our parent figure it out.
|
||||
if (newlyFocusedChild->_HasFocusedChild())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Transfer focus to our child, and update the focus of our tree.
|
||||
newlyFocusedChild->_FocusFirstChild();
|
||||
UpdateVisuals();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to move focus to one of our children. If we have a focused child,
|
||||
// we'll try to move the focus in the direction requested.
|
||||
@@ -255,8 +220,8 @@ bool Pane::_NavigateFocus(const FocusDirection& direction)
|
||||
// direction, we'll return false. This will indicate to our parent that they
|
||||
// should try and move the focus themselves. In this way, the focus can move
|
||||
// up and down the tree to the correct pane.
|
||||
// - This method is _very_ similar to ResizePane. Both are trying to find the
|
||||
// right separator to move (focus) in a direction.
|
||||
// - This method is _very_ similar to MovePane. Both are trying to find the
|
||||
// right pane to move (focus) in a direction.
|
||||
// Arguments:
|
||||
// - direction: The direction to move the focus in.
|
||||
// Return Value:
|
||||
@@ -270,33 +235,400 @@ bool Pane::NavigateFocus(const FocusDirection& direction)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if either our first or second child is the currently focused leaf.
|
||||
// If it is, and the requested move direction matches our separator, then
|
||||
// we're the pane that needs to handle this focus move.
|
||||
const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastActive;
|
||||
const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastActive;
|
||||
if (firstIsFocused || secondIsFocused)
|
||||
// If the focus direction does not match the split direction, the focused pane
|
||||
// and its neighbor must necessarily be contained within the same child.
|
||||
if (!DirectionMatchesSplit(direction, _splitState))
|
||||
{
|
||||
return _NavigateFocus(direction);
|
||||
return _firstChild->NavigateFocus(direction) || _secondChild->NavigateFocus(direction);
|
||||
}
|
||||
|
||||
// If neither of our children were the focused leaf, then recurse into
|
||||
// our children and see if they can handle the focus move.
|
||||
// For each child, if it has a focused descendant, try having that child
|
||||
// handle the focus move.
|
||||
// If the child wasn't able to handle the focus move, it's possible that
|
||||
// there were no descendants with a separator the correct direction. If
|
||||
// our separator _is_ the correct direction, then we should be the pane
|
||||
// to move focus into our other child. Otherwise, just return false, as
|
||||
// we couldn't handle it either.
|
||||
if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild())
|
||||
// Since the direction is the same as our split, it is possible that we must
|
||||
// move focus from from one child to another child.
|
||||
// We now must keep track of state while we recurse.
|
||||
auto focusNeighborPair = _FindFocusAndNeighbor(direction, { 0, 0 });
|
||||
|
||||
// Once we have found the focused pane and its neighbor, wherever they may
|
||||
// be we can update the focus.
|
||||
if (focusNeighborPair.focus && focusNeighborPair.neighbor)
|
||||
{
|
||||
return _firstChild->NavigateFocus(direction) || _NavigateFocus(direction);
|
||||
focusNeighborPair.neighbor->_FocusFirstChild();
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to find the parent pane of the provided pane.
|
||||
// Arguments:
|
||||
// - pane: The pane to search for.
|
||||
// Return Value:
|
||||
// - the parent of `pane` if pane is in this tree.
|
||||
std::shared_ptr<Pane> Pane::_FindParentOfPane(const std::shared_ptr<Pane> pane)
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
return _secondChild->NavigateFocus(direction) || _NavigateFocus(direction);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (_firstChild == pane || _secondChild == pane)
|
||||
{
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
if (auto p = _firstChild->_FindParentOfPane(pane))
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
return _secondChild->_FindParentOfPane(pane);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to swap the location of the two given panes in the tree.
|
||||
// Searches the tree starting at this pane to find the parent pane for each of
|
||||
// the arguments, and if both parents are found, replaces the appropriate
|
||||
// child in each.
|
||||
// Arguments:
|
||||
// - first: A pointer to the first pane to switch.
|
||||
// - second: A pointer to the second pane to switch.
|
||||
// Return Value:
|
||||
// - true if a swap was performed.
|
||||
bool Pane::SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second)
|
||||
{
|
||||
// If there is nothing to swap, just return.
|
||||
if (first == second || _IsLeaf())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
|
||||
// Recurse through the tree to find the parent panes of each pane that is
|
||||
// being swapped.
|
||||
std::shared_ptr<Pane> firstParent = _FindParentOfPane(first);
|
||||
std::shared_ptr<Pane> secondParent = _FindParentOfPane(second);
|
||||
|
||||
// We should have found either no elements, or both elements.
|
||||
// If we only found one parent then the pane SwapPane was called on did not
|
||||
// contain both panes as leaves, as could happen if the tree was modified
|
||||
// after the pointers were found but before we reached this function.
|
||||
if (firstParent && secondParent)
|
||||
{
|
||||
// Swap size/display information of the two panes.
|
||||
std::swap(first->_borders, second->_borders);
|
||||
|
||||
// Replace the old child with new one, and revoke appropriate event
|
||||
// handlers.
|
||||
auto replaceChild = [](auto& parent, auto oldChild, auto newChild) {
|
||||
// Revoke the old handlers
|
||||
if (parent->_firstChild == oldChild)
|
||||
{
|
||||
parent->_firstChild->Closed(parent->_firstClosedToken);
|
||||
parent->_firstChild = newChild;
|
||||
}
|
||||
else if (parent->_secondChild == oldChild)
|
||||
{
|
||||
parent->_secondChild->Closed(parent->_secondClosedToken);
|
||||
parent->_secondChild = newChild;
|
||||
}
|
||||
// Clear now to ensure that we can add the child's grid to us later
|
||||
parent->_root.Children().Clear();
|
||||
};
|
||||
|
||||
// Make sure that the right event handlers are set, and the children
|
||||
// are placed in the appropriate locations in the grid.
|
||||
auto updateParent = [](auto& parent) {
|
||||
parent->_SetupChildCloseHandlers();
|
||||
parent->_root.Children().Clear();
|
||||
parent->_root.Children().Append(parent->_firstChild->GetRootElement());
|
||||
parent->_root.Children().Append(parent->_secondChild->GetRootElement());
|
||||
// Make sure they have the correct borders, and also that they are
|
||||
// placed in the right location in the grid.
|
||||
// This mildly reproduces ApplySplitDefinitions, but is different in
|
||||
// that it does not want to utilize the parent's border to set child
|
||||
// borders.
|
||||
if (parent->_splitState == SplitState::Vertical)
|
||||
{
|
||||
Controls::Grid::SetColumn(parent->_firstChild->GetRootElement(), 0);
|
||||
Controls::Grid::SetColumn(parent->_secondChild->GetRootElement(), 1);
|
||||
}
|
||||
else if (parent->_splitState == SplitState::Horizontal)
|
||||
{
|
||||
Controls::Grid::SetRow(parent->_firstChild->GetRootElement(), 0);
|
||||
Controls::Grid::SetRow(parent->_secondChild->GetRootElement(), 1);
|
||||
}
|
||||
parent->_firstChild->_UpdateBorders();
|
||||
parent->_secondChild->_UpdateBorders();
|
||||
};
|
||||
|
||||
// If the firstParent and secondParent are the same, then we are just
|
||||
// swapping the first child and second child of that parent.
|
||||
if (firstParent == secondParent)
|
||||
{
|
||||
firstParent->_firstChild->Closed(firstParent->_firstClosedToken);
|
||||
firstParent->_secondChild->Closed(firstParent->_secondClosedToken);
|
||||
std::swap(firstParent->_firstChild, firstParent->_secondChild);
|
||||
|
||||
updateParent(firstParent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Replace both children before updating display to ensure
|
||||
// that the grid elements are not attached to multiple panes
|
||||
replaceChild(firstParent, first, second);
|
||||
replaceChild(secondParent, second, first);
|
||||
updateParent(firstParent);
|
||||
updateParent(secondParent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Given two panes, test whether the `direction` side of first is adjacent to second.
|
||||
// Arguments:
|
||||
// - first: The reference pane.
|
||||
// - second: the pane to test adjacency with.
|
||||
// - direction: The direction to search in from the reference pane.
|
||||
// Return Value:
|
||||
// - true if the two panes are adjacent.
|
||||
bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
|
||||
const Pane::PanePoint firstOffset,
|
||||
const std::shared_ptr<Pane> second,
|
||||
const Pane::PanePoint secondOffset,
|
||||
const FocusDirection& direction) const
|
||||
{
|
||||
// Since float equality is tricky (arithmetic is non-associative, commutative),
|
||||
// test if the two numbers are within an epsilon distance of each other.
|
||||
auto floatEqual = [](float left, float right) {
|
||||
return abs(left - right) < 1e-4F;
|
||||
};
|
||||
|
||||
// When checking containment in a range, the range is half-closed, i.e. [x, x+w).
|
||||
// If the direction is left test that the left side of the first element is
|
||||
// next to the right side of the second element, and that the top left
|
||||
// corner of the first element is within the second element's height
|
||||
if (direction == FocusDirection::Left)
|
||||
{
|
||||
auto sharesBorders = floatEqual(firstOffset.x, secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
|
||||
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
|
||||
|
||||
return sharesBorders && withinHeight;
|
||||
}
|
||||
// If the direction is right test that the right side of the first element is
|
||||
// next to the left side of the second element, and that the top left
|
||||
// corner of the first element is within the second element's height
|
||||
else if (direction == FocusDirection::Right)
|
||||
{
|
||||
auto sharesBorders = floatEqual(firstOffset.x + gsl::narrow_cast<float>(first->GetRootElement().ActualWidth()), secondOffset.x);
|
||||
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
|
||||
|
||||
return sharesBorders && withinHeight;
|
||||
}
|
||||
// If the direction is up test that the top side of the first element is
|
||||
// next to the bottom side of the second element, and that the top left
|
||||
// corner of the first element is within the second element's width
|
||||
else if (direction == FocusDirection::Up)
|
||||
{
|
||||
auto sharesBorders = floatEqual(firstOffset.y, secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
|
||||
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
|
||||
|
||||
return sharesBorders && withinWidth;
|
||||
}
|
||||
// If the direction is down test that the bottom side of the first element is
|
||||
// next to the top side of the second element, and that the top left
|
||||
// corner of the first element is within the second element's width
|
||||
else if (direction == FocusDirection::Down)
|
||||
{
|
||||
auto sharesBorders = floatEqual(firstOffset.y + gsl::narrow_cast<float>(first->GetRootElement().ActualHeight()), secondOffset.y);
|
||||
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
|
||||
|
||||
return sharesBorders && withinWidth;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Given the focused pane, and its relative position in the tree, attempt to
|
||||
// find its visual neighbor within the current pane's tree.
|
||||
// The neighbor, if it exists, will be a leaf pane.
|
||||
// Arguments:
|
||||
// - direction: The direction to search in from the focused pane.
|
||||
// - focus: the focused pane
|
||||
// - focusIsSecondSide: If the focused pane is on the "second" side (down/right of split)
|
||||
// relative to the branch being searched
|
||||
// - offset: the offset of the current pane
|
||||
// Return Value:
|
||||
// - A tuple of Panes, the first being the focused pane if found, and the second
|
||||
// being the adjacent pane if it exists, and a bool that represents if the move
|
||||
// goes out of bounds.
|
||||
Pane::FocusNeighborSearch Pane::_FindNeighborForPane(const FocusDirection& direction,
|
||||
FocusNeighborSearch searchResult,
|
||||
const bool focusIsSecondSide,
|
||||
const Pane::PanePoint offset)
|
||||
{
|
||||
// Test if the move will go out of boundaries. E.g. if the focus is already
|
||||
// on the second child of some pane and it attempts to move right, there
|
||||
// can't possibly be a neighbor to be found in the first child.
|
||||
if ((focusIsSecondSide && (direction == FocusDirection::Right || direction == FocusDirection::Down)) ||
|
||||
(!focusIsSecondSide && (direction == FocusDirection::Left || direction == FocusDirection::Up)))
|
||||
{
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
// If we are a leaf node test if we adjacent to the focus node
|
||||
if (_IsLeaf())
|
||||
{
|
||||
if (_IsAdjacent(searchResult.focus, searchResult.focusOffset, shared_from_this(), offset, direction))
|
||||
{
|
||||
searchResult.neighbor = shared_from_this();
|
||||
}
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
auto firstOffset = offset;
|
||||
auto secondOffset = offset;
|
||||
// The second child has an offset depending on the split
|
||||
if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
secondOffset.y += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
secondOffset.x += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualWidth());
|
||||
}
|
||||
auto focusNeighborSearch = _firstChild->_FindNeighborForPane(direction, searchResult, focusIsSecondSide, firstOffset);
|
||||
if (focusNeighborSearch.neighbor)
|
||||
{
|
||||
return focusNeighborSearch;
|
||||
}
|
||||
|
||||
return _secondChild->_FindNeighborForPane(direction, searchResult, focusIsSecondSide, secondOffset);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Searches the tree to find the currently focused pane, and if it exists, the
|
||||
// visually adjacent pane by direction.
|
||||
// Arguments:
|
||||
// - direction: The direction to search in from the focused pane.
|
||||
// - offset: The offset, with the top-left corner being (0,0), that the current pane is relative to the root.
|
||||
// Return Value:
|
||||
// - The (partial) search result. If the search was successful, the focus and its neighbor will be returned.
|
||||
// Otherwise, the neighbor will be null and the focus will be null/non-null if it was found.
|
||||
Pane::FocusNeighborSearch Pane::_FindFocusAndNeighbor(const FocusDirection& direction, const Pane::PanePoint offset)
|
||||
{
|
||||
// If we are the currently focused pane, return ourselves
|
||||
if (_IsLeaf())
|
||||
{
|
||||
return { _lastActive ? shared_from_this() : nullptr, nullptr, offset };
|
||||
}
|
||||
|
||||
// Search the first child, which has no offset from the parent pane
|
||||
auto firstOffset = offset;
|
||||
auto secondOffset = offset;
|
||||
// The second child has an offset depending on the split
|
||||
if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
secondOffset.y += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
secondOffset.x += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualWidth());
|
||||
}
|
||||
|
||||
auto focusNeighborSearch = _firstChild->_FindFocusAndNeighbor(direction, firstOffset);
|
||||
// If we have both the focus element and its neighbor, we are done
|
||||
if (focusNeighborSearch.focus && focusNeighborSearch.neighbor)
|
||||
{
|
||||
return focusNeighborSearch;
|
||||
}
|
||||
// if we only found the focus, then we search the second branch for the
|
||||
// neighbor.
|
||||
if (focusNeighborSearch.focus)
|
||||
{
|
||||
// If we can possibly have both sides of a direction, check if the sibling has the neighbor
|
||||
if (DirectionMatchesSplit(direction, _splitState))
|
||||
{
|
||||
return _secondChild->_FindNeighborForPane(direction, focusNeighborSearch, false, secondOffset);
|
||||
}
|
||||
return focusNeighborSearch;
|
||||
}
|
||||
|
||||
// If we didn't find the focus at all, we need to search the second branch
|
||||
// for the focus (and possibly its neighbor).
|
||||
focusNeighborSearch = _secondChild->_FindFocusAndNeighbor(direction, secondOffset);
|
||||
// We found both so we are done.
|
||||
if (focusNeighborSearch.focus && focusNeighborSearch.neighbor)
|
||||
{
|
||||
return focusNeighborSearch;
|
||||
}
|
||||
// We only found the focus, which means that its neighbor might be in the
|
||||
// first branch.
|
||||
if (focusNeighborSearch.focus)
|
||||
{
|
||||
// If we can possibly have both sides of a direction, check if the sibling has the neighbor
|
||||
if (DirectionMatchesSplit(direction, _splitState))
|
||||
{
|
||||
return _firstChild->_FindNeighborForPane(direction, focusNeighborSearch, true, firstOffset);
|
||||
}
|
||||
return focusNeighborSearch;
|
||||
}
|
||||
|
||||
return { nullptr, nullptr, offset };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to swap places of the focused pane with one of our children. This
|
||||
// will swap with the visually adjacent leaf pane if one exists in the
|
||||
// direction requested, maintaining the existing tree structure.
|
||||
// This breaks down into a few possible cases
|
||||
// - If the move direction would encounter the edge of the pane, no move occurs
|
||||
// - If the focused pane has a single neighbor according to the direction,
|
||||
// then it will swap with it.
|
||||
// - If the focused pane has multiple neighbors, it will swap with the
|
||||
// first-most leaf of the neighboring panes.
|
||||
// Arguments:
|
||||
// - direction: The direction to move the focused pane in.
|
||||
// Return Value:
|
||||
// - true if we or a child handled this pane move request.
|
||||
bool Pane::MovePane(const FocusDirection& direction)
|
||||
{
|
||||
// If we're a leaf, do nothing. We can't possibly swap anything.
|
||||
if (_IsLeaf())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we get a request to move to the previous pane return false because
|
||||
// that needs to be handled at the tab level.
|
||||
if (direction == FocusDirection::Previous)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the move direction does not match the split direction, the focused pane
|
||||
// and its neighbor must necessarily be contained within the same child.
|
||||
if (!DirectionMatchesSplit(direction, _splitState))
|
||||
{
|
||||
return _firstChild->MovePane(direction) || _secondChild->MovePane(direction);
|
||||
}
|
||||
|
||||
// Since the direction is the same as our split, it is possible that we must
|
||||
// swap a pane from one child to the other child.
|
||||
// We now must keep track of state while we recurse.
|
||||
auto focusNeighborPair = _FindFocusAndNeighbor(direction, { 0, 0 });
|
||||
|
||||
// Once we have found the focused pane and its neighbor, wherever they may
|
||||
// be, we can swap them.
|
||||
if (focusNeighborPair.focus && focusNeighborPair.neighbor)
|
||||
{
|
||||
auto swapped = SwapPanes(focusNeighborPair.focus, focusNeighborPair.neighbor);
|
||||
focusNeighborPair.focus->_FocusFirstChild();
|
||||
return swapped;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -911,7 +1243,6 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
|
||||
auto removedChild = closeFirst ? _firstChild : _secondChild;
|
||||
auto remainingChild = closeFirst ? _secondChild : _firstChild;
|
||||
const bool splitWidth = _splitState == SplitState::Vertical;
|
||||
const auto totalSize = splitWidth ? _root.ActualWidth() : _root.ActualHeight();
|
||||
|
||||
Size removedOriginalSize{
|
||||
::base::saturated_cast<float>(removedChild->_root.ActualWidth()),
|
||||
@@ -1626,6 +1957,36 @@ bool Pane::FocusPane(const uint32_t id)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Recursive function that finds a pane with the given ID
|
||||
// Arguments:
|
||||
// - The ID of the pane we want to find
|
||||
// Return Value:
|
||||
// - A pointer to the pane with the given ID, if found.
|
||||
std::shared_ptr<Pane> Pane::FindPane(const uint32_t id)
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
if (id == _id)
|
||||
{
|
||||
return shared_from_this();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto pane = _firstChild->FindPane(id))
|
||||
{
|
||||
return pane;
|
||||
}
|
||||
if (auto pane = _secondChild->FindPane(id))
|
||||
{
|
||||
return pane;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gets the size in pixels of each of our children, given the full size they
|
||||
// should fill. Since these children own their own separators (borders), this
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
class TabTests;
|
||||
};
|
||||
|
||||
enum class Borders : int
|
||||
{
|
||||
None = 0x0,
|
||||
@@ -56,6 +62,8 @@ public:
|
||||
void Relayout();
|
||||
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
bool MovePane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
bool SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second);
|
||||
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
|
||||
const float splitSize,
|
||||
@@ -79,6 +87,7 @@ public:
|
||||
std::optional<uint32_t> Id() noexcept;
|
||||
void Id(uint32_t id) noexcept;
|
||||
bool FocusPane(const uint32_t id);
|
||||
std::shared_ptr<Pane> FindPane(const uint32_t id);
|
||||
|
||||
bool ContainsReadOnly() const;
|
||||
|
||||
@@ -88,6 +97,8 @@ public:
|
||||
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
|
||||
|
||||
private:
|
||||
struct PanePoint;
|
||||
struct FocusNeighborSearch;
|
||||
struct SnapSizeResult;
|
||||
struct SnapChildrenSizeResult;
|
||||
struct LayoutSizeNode;
|
||||
@@ -137,7 +148,15 @@ private:
|
||||
void _UpdateBorders();
|
||||
|
||||
bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
bool _NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
|
||||
std::shared_ptr<Pane> _FindParentOfPane(const std::shared_ptr<Pane> pane);
|
||||
bool _IsAdjacent(const std::shared_ptr<Pane> first, const PanePoint firstOffset, const std::shared_ptr<Pane> second, const PanePoint secondOffset, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction) const;
|
||||
FocusNeighborSearch _FindNeighborForPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
|
||||
FocusNeighborSearch searchResult,
|
||||
const bool focusIsSecondSide,
|
||||
const PanePoint offset);
|
||||
FocusNeighborSearch _FindFocusAndNeighbor(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
|
||||
const PanePoint offset);
|
||||
|
||||
void _CloseChild(const bool closeFirst);
|
||||
winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst);
|
||||
@@ -200,6 +219,19 @@ private:
|
||||
|
||||
static void _SetupResources();
|
||||
|
||||
struct PanePoint
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct FocusNeighborSearch
|
||||
{
|
||||
std::shared_ptr<Pane> focus;
|
||||
std::shared_ptr<Pane> neighbor;
|
||||
PanePoint focusOffset;
|
||||
};
|
||||
|
||||
struct SnapSizeResult
|
||||
{
|
||||
float lower;
|
||||
@@ -236,4 +268,6 @@ private:
|
||||
private:
|
||||
void _AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode);
|
||||
};
|
||||
|
||||
friend class ::TerminalAppLocalTests::TabTests;
|
||||
};
|
||||
|
||||
@@ -359,6 +359,16 @@
|
||||
<data name="CmdMoveFocusDirectionArgDesc" xml:space="preserve">
|
||||
<value>The direction to move focus in</value>
|
||||
</data>
|
||||
<data name="CmdMovePaneDesc" xml:space="preserve">
|
||||
<value>Swap the focused pane with the adjacent pane in the specified direction</value>
|
||||
</data>
|
||||
<data name="CmdMPDesc" xml:space="preserve">
|
||||
<value>An alias for the "move-pane" subcommand.</value>
|
||||
<comment>{Locked="\"move-pane\""}</comment>
|
||||
</data>
|
||||
<data name="CmdMovePaneDirectionArgDesc" xml:space="preserve">
|
||||
<value>The direction to move the focused pane in</value>
|
||||
</data>
|
||||
<data name="CmdFocusDesc" xml:space="preserve">
|
||||
<value>Launch the window in focus mode</value>
|
||||
</data>
|
||||
@@ -649,4 +659,7 @@
|
||||
<value>Open in Windows Terminal</value>
|
||||
<comment>{Locked="Windows"} This is a menu item that will be displayed in the Windows File Explorer that launches the non-preview version of Windows Terminal</comment>
|
||||
</data>
|
||||
<data name="DropPathTabRun.Text" xml:space="preserve">
|
||||
<value>Open a new tab in given starting directory</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -105,6 +105,14 @@ namespace winrt::TerminalApp::implementation
|
||||
// Create a connection based on the values in our settings object if we weren't given one.
|
||||
auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profileGuid, settings.DefaultSettings());
|
||||
|
||||
// If we had an `existingConnection`, then this is an inbound handoff from somewhere else.
|
||||
// We need to tell it about our size information so it can match the dimensions of what
|
||||
// we are about to present.
|
||||
if (existingConnection)
|
||||
{
|
||||
connection.Resize(settings.DefaultSettings().InitialRows(), settings.DefaultSettings().InitialCols());
|
||||
}
|
||||
|
||||
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
|
||||
if (_settings.GlobalSettings().DebugFeaturesEnabled())
|
||||
{
|
||||
@@ -497,24 +505,25 @@ namespace winrt::TerminalApp::implementation
|
||||
// TerminalPage::_OnTabSelectionChanged
|
||||
// Return Value:
|
||||
// true iff we were able to select that tab index, false otherwise
|
||||
bool TerminalPage::_SelectTab(const uint32_t tabIndex)
|
||||
bool TerminalPage::_SelectTab(uint32_t tabIndex)
|
||||
{
|
||||
if (tabIndex >= 0 && tabIndex < _tabs.Size())
|
||||
{
|
||||
auto tab{ _tabs.GetAt(tabIndex) };
|
||||
if (_startupState == StartupState::InStartup)
|
||||
{
|
||||
_tabView.SelectedItem(tab.TabViewItem());
|
||||
_UpdatedSelectedTab(tab);
|
||||
}
|
||||
else
|
||||
{
|
||||
_SetFocusedTab(tab);
|
||||
}
|
||||
// GH#9369 - if the argument is out of range, then clamp to the number
|
||||
// of available tabs. Previously, we'd just silently do nothing if the
|
||||
// value was greater than the number of tabs.
|
||||
tabIndex = std::clamp(tabIndex, 0u, _tabs.Size() - 1);
|
||||
|
||||
return true;
|
||||
auto tab{ _tabs.GetAt(tabIndex) };
|
||||
if (_startupState == StartupState::InStartup)
|
||||
{
|
||||
_tabView.SelectedItem(tab.TabViewItem());
|
||||
_UpdatedSelectedTab(tab);
|
||||
}
|
||||
return false;
|
||||
else
|
||||
{
|
||||
_SetFocusedTab(tab);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -5,9 +5,19 @@
|
||||
#include "TabRowControl.h"
|
||||
|
||||
#include "TabRowControl.g.cpp"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Microsoft::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Text;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
namespace MUX = Microsoft::UI::Xaml;
|
||||
namespace WUX = Windows::UI::Xaml;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
@@ -23,4 +33,42 @@ namespace winrt::TerminalApp::implementation
|
||||
void TabRowControl::OnNewTabButtonClick(IInspectable const&, Controls::SplitButtonClickEventArgs const&)
|
||||
{
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Bound in Drag&Drop of the Xaml editor to the [+] button.
|
||||
// Arguments:
|
||||
// <unused>
|
||||
void TabRowControl::OnNewTabButtonDrop(IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs const&)
|
||||
{
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Bound in Drag-over of the Xaml editor to the [+] button.
|
||||
// Allows drop of 'StorageItems' which will be used as StartingDirectory
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// - e: DragEventArgs which hold the items
|
||||
void TabRowControl::OnNewTabButtonDragOver(IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs const& e)
|
||||
{
|
||||
// We can only handle drag/dropping StorageItems (files).
|
||||
// If the format on the clipboard is anything else, returning
|
||||
// early here will prevent the drag/drop from doing anything.
|
||||
if (!e.DataView().Contains(StandardDataFormats::StorageItems()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to set the AcceptedOperation, so that we can later receive the path in the Drop event
|
||||
e.AcceptedOperation(DataPackageOperation::Copy);
|
||||
|
||||
// Sets custom UI text
|
||||
e.DragUIOverride().Caption(RS_(L"DropPathTabRun/Text"));
|
||||
|
||||
// Sets if the caption is visible
|
||||
e.DragUIOverride().IsCaptionVisible(true);
|
||||
// Sets if the dragged content is visible
|
||||
e.DragUIOverride().IsContentVisible(false);
|
||||
// Sets if the glyph is visible
|
||||
e.DragUIOverride().IsGlyphVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace winrt::TerminalApp::implementation
|
||||
TabRowControl();
|
||||
|
||||
void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::Controls::SplitButtonClickEventArgs const& args);
|
||||
void OnNewTabButtonDrop(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::DragEventArgs const& e);
|
||||
void OnNewTabButtonDragOver(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::DragEventArgs const& e);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,14 @@
|
||||
x:Uid="NewTabSplitButton"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
AllowDrop="True"
|
||||
AutomationProperties.AccessibilityView="Control"
|
||||
BorderThickness="0"
|
||||
Click="OnNewTabButtonClick"
|
||||
Content=""
|
||||
CornerRadius="{Binding Source={ThemeResource OverlayCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}"
|
||||
DragOver="OnNewTabButtonDragOver"
|
||||
Drop="OnNewTabButtonDrop"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="12"
|
||||
FontWeight="SemiLight"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Utils.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "TerminalPage.g.cpp"
|
||||
@@ -172,40 +173,13 @@ namespace winrt::TerminalApp::implementation
|
||||
_newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
// if alt is pressed, open a pane
|
||||
const CoreWindow window = CoreWindow::GetForCurrentThread();
|
||||
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
|
||||
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
|
||||
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
||||
|
||||
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
|
||||
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
|
||||
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
|
||||
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
|
||||
|
||||
// Check for DebugTap
|
||||
bool debugTap = page->_settings.GlobalSettings().DebugFeaturesEnabled() &&
|
||||
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
|
||||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
||||
|
||||
if (altPressed && !debugTap)
|
||||
{
|
||||
page->_SplitPane(SplitState::Automatic,
|
||||
SplitType::Manual,
|
||||
0.5f,
|
||||
nullptr);
|
||||
}
|
||||
else if (shiftPressed && !debugTap)
|
||||
{
|
||||
page->_OpenNewWindow(false, NewTerminalArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
page->_OpenNewTab(nullptr);
|
||||
}
|
||||
page->_OpenNewTerminal(NewTerminalArgs());
|
||||
}
|
||||
});
|
||||
_newTabButton.Drop([weakThis{ get_weak() }](Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs e) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->NewTerminalByDrop(e);
|
||||
}
|
||||
});
|
||||
_tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged });
|
||||
@@ -262,6 +236,44 @@ namespace winrt::TerminalApp::implementation
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e)
|
||||
{
|
||||
Windows::Foundation::Collections::IVectorView<Windows::Storage::IStorageItem> items;
|
||||
try
|
||||
{
|
||||
items = co_await e.DataView().GetStorageItemsAsync();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
if (items.Size() == 1)
|
||||
{
|
||||
std::filesystem::path path(items.GetAt(0).Path().c_str());
|
||||
if (!std::filesystem::is_directory(path))
|
||||
{
|
||||
path = path.parent_path();
|
||||
}
|
||||
|
||||
std::wstring pathText = path.wstring();
|
||||
|
||||
// Handle edge case of "C:\\", seems like the "StartingDirectory" doesn't like path which ends with '\'
|
||||
if (pathText.back() == L'\\')
|
||||
{
|
||||
pathText.erase(std::prev(pathText.end()));
|
||||
}
|
||||
|
||||
NewTerminalArgs args;
|
||||
args.StartingDirectory(winrt::hstring{ pathText });
|
||||
this->_OpenNewTerminal(args);
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"NewTabByDragDrop",
|
||||
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method is called once command palette action was chosen for dispatching
|
||||
// We'll use this event to dispatch this command.
|
||||
@@ -622,43 +634,7 @@ namespace winrt::TerminalApp::implementation
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
NewTerminalArgs newTerminalArgs{ profileIndex };
|
||||
|
||||
// if alt is pressed, open a pane
|
||||
const CoreWindow window = CoreWindow::GetForCurrentThread();
|
||||
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
|
||||
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
|
||||
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
||||
|
||||
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
|
||||
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
|
||||
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
|
||||
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
|
||||
|
||||
// Check for DebugTap
|
||||
bool debugTap = page->_settings.GlobalSettings().DebugFeaturesEnabled() &&
|
||||
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
|
||||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
||||
|
||||
if (altPressed && !debugTap)
|
||||
{
|
||||
page->_SplitPane(SplitState::Automatic,
|
||||
SplitType::Manual,
|
||||
0.5f,
|
||||
newTerminalArgs);
|
||||
}
|
||||
else if (shiftPressed && !debugTap)
|
||||
{
|
||||
// Manually fill in the evaluated profile.
|
||||
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(page->_settings.GetProfileForArgs(newTerminalArgs)));
|
||||
page->_OpenNewWindow(false, newTerminalArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
page->_OpenNewTab(newTerminalArgs);
|
||||
}
|
||||
page->_OpenNewTerminal(newTerminalArgs);
|
||||
}
|
||||
});
|
||||
newTabFlyout.Items().Append(profileMenuItem);
|
||||
@@ -752,6 +728,49 @@ namespace winrt::TerminalApp::implementation
|
||||
_newTabButton.Flyout().ShowAt(_newTabButton);
|
||||
}
|
||||
|
||||
void TerminalPage::_OpenNewTerminal(const NewTerminalArgs newTerminalArgs)
|
||||
{
|
||||
// if alt is pressed, open a pane
|
||||
const CoreWindow window = CoreWindow::GetForCurrentThread();
|
||||
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
|
||||
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
|
||||
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
||||
|
||||
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
|
||||
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
|
||||
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
|
||||
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
|
||||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
|
||||
|
||||
// Check for DebugTap
|
||||
bool debugTap = this->_settings.GlobalSettings().DebugFeaturesEnabled() &&
|
||||
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
|
||||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
||||
|
||||
if (altPressed && !debugTap)
|
||||
{
|
||||
this->_SplitPane(SplitState::Automatic,
|
||||
SplitType::Manual,
|
||||
0.5f,
|
||||
newTerminalArgs);
|
||||
}
|
||||
else if (shiftPressed && !debugTap)
|
||||
{
|
||||
// Manually fill in the evaluated profile.
|
||||
if (newTerminalArgs.ProfileIndex() != nullptr)
|
||||
{
|
||||
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(this->_settings.GetProfileForArgs(newTerminalArgs)));
|
||||
}
|
||||
this->_OpenNewWindow(false, newTerminalArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->_OpenNewTab(newTerminalArgs);
|
||||
}
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalPage::_RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page)
|
||||
{
|
||||
co_await winrt::resume_foreground(page->_tabView.Dispatcher());
|
||||
@@ -785,13 +804,14 @@ namespace winrt::TerminalApp::implementation
|
||||
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better
|
||||
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
|
||||
azBridgePath.replace_filename(L"TerminalAzBridge.exe");
|
||||
connection = TerminalConnection::ConptyConnection(azBridgePath.wstring(),
|
||||
L".",
|
||||
L"Azure",
|
||||
nullptr,
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid());
|
||||
connection = TerminalConnection::ConptyConnection();
|
||||
connection.Initialize(TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
|
||||
L".",
|
||||
L"Azure",
|
||||
nullptr,
|
||||
::base::saturated_cast<uint32_t>(settings.InitialRows()),
|
||||
::base::saturated_cast<uint32_t>(settings.InitialCols()),
|
||||
winrt::guid()));
|
||||
}
|
||||
|
||||
else
|
||||
@@ -821,14 +841,14 @@ namespace winrt::TerminalApp::implementation
|
||||
std::filesystem::path cwd{ cwdString };
|
||||
cwd /= settings.StartingDirectory().c_str();
|
||||
|
||||
auto conhostConn = TerminalConnection::ConptyConnection(
|
||||
settings.Commandline(),
|
||||
winrt::hstring{ cwd.c_str() },
|
||||
settings.StartingTitle(),
|
||||
envMap.GetView(),
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid());
|
||||
auto conhostConn = TerminalConnection::ConptyConnection();
|
||||
conhostConn.Initialize(TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
|
||||
winrt::hstring{ cwd.wstring() },
|
||||
settings.StartingTitle(),
|
||||
envMap.GetView(),
|
||||
::base::saturated_cast<uint32_t>(settings.InitialRows()),
|
||||
::base::saturated_cast<uint32_t>(settings.InitialCols()),
|
||||
winrt::guid()));
|
||||
|
||||
sessionGuid = conhostConn.Guid();
|
||||
connection = conhostConn;
|
||||
@@ -914,12 +934,14 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void TerminalPage::_KeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
const auto key = e.OriginalKey();
|
||||
const auto scanCode = e.KeyStatus().ScanCode;
|
||||
const auto coreWindow = CoreWindow::GetForCurrentThread();
|
||||
const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
|
||||
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, false, static_cast<int32_t>(key), static_cast<int32_t>(scanCode) };
|
||||
if (const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) })
|
||||
{
|
||||
if (CommandPalette().Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
|
||||
@@ -1111,6 +1133,22 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempt to swap the positions of the focused pane with another pane.
|
||||
// See Pane::MovePane for details.
|
||||
// Arguments:
|
||||
// - direction: The direction to move the focused pane in.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_MovePane(const FocusDirection& direction)
|
||||
{
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
_UnZoomIfNeeded();
|
||||
terminalTab->MovePane(direction);
|
||||
}
|
||||
}
|
||||
|
||||
TermControl TerminalPage::_GetActiveControl()
|
||||
{
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
@@ -2020,11 +2058,11 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Gets the taskbar state value from the last active control
|
||||
// Return Value:
|
||||
// - The taskbar state of the last active control
|
||||
size_t TerminalPage::GetLastActiveControlTaskbarState()
|
||||
uint64_t TerminalPage::GetLastActiveControlTaskbarState()
|
||||
{
|
||||
if (auto control{ _GetActiveControl() })
|
||||
{
|
||||
return gsl::narrow_cast<size_t>(control.TaskbarState());
|
||||
return control.TaskbarState();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -2033,11 +2071,11 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Gets the taskbar progress value from the last active control
|
||||
// Return Value:
|
||||
// - The taskbar progress of the last active control
|
||||
size_t TerminalPage::GetLastActiveControlTaskbarProgress()
|
||||
uint64_t TerminalPage::GetLastActiveControlTaskbarProgress()
|
||||
{
|
||||
if (auto control{ _GetActiveControl() })
|
||||
{
|
||||
return gsl::narrow_cast<size_t>(control.TaskbarProgress());
|
||||
return control.TaskbarProgress();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void Create();
|
||||
|
||||
winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e);
|
||||
|
||||
hstring Title();
|
||||
|
||||
void TitlebarClicked();
|
||||
@@ -83,8 +85,8 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::TerminalApp::IDialogPresenter DialogPresenter() const;
|
||||
void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter);
|
||||
|
||||
size_t GetLastActiveControlTaskbarState();
|
||||
size_t GetLastActiveControlTaskbarProgress();
|
||||
uint64_t GetLastActiveControlTaskbarState();
|
||||
uint64_t GetLastActiveControlTaskbarProgress();
|
||||
|
||||
void ShowKeyboardServiceWarning();
|
||||
winrt::hstring KeyboardServiceDisabledText();
|
||||
@@ -192,6 +194,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
|
||||
|
||||
void _OpenNewTerminal(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
|
||||
|
||||
bool _displayingCloseDialog{ false };
|
||||
void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
void _CommandPaletteButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
@@ -229,8 +233,9 @@ namespace winrt::TerminalApp::implementation
|
||||
void _ResizeTabContent(const winrt::Windows::Foundation::Size& newSize);
|
||||
|
||||
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
|
||||
bool _SelectTab(const uint32_t tabIndex);
|
||||
bool _SelectTab(uint32_t tabIndex);
|
||||
void _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
void _MovePane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
|
||||
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
|
||||
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
|
||||
|
||||
@@ -486,6 +486,10 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
if (direction == FocusDirection::Previous)
|
||||
{
|
||||
if (_mruPanes.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// To get to the previous pane, get the id of the previous pane and focus to that
|
||||
_rootPane->FocusPane(_mruPanes.at(1));
|
||||
}
|
||||
@@ -497,6 +501,35 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to swap the location of the focused pane with another pane
|
||||
// according to direction. When there are multiple adjacent panes it will
|
||||
// select the first one (top-left-most).
|
||||
// Arguments:
|
||||
// - direction: The direction to move the pane in.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalTab::MovePane(const FocusDirection& direction)
|
||||
{
|
||||
if (direction == FocusDirection::Previous)
|
||||
{
|
||||
if (_mruPanes.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (auto lastPane = _rootPane->FindPane(_mruPanes.at(1)))
|
||||
{
|
||||
_rootPane->SwapPanes(_activePane, lastPane);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: This _must_ be called on the root pane, so that it can propagate
|
||||
// throughout the entire tree.
|
||||
_rootPane->MovePane(direction);
|
||||
}
|
||||
}
|
||||
|
||||
bool TerminalTab::FocusPane(const uint32_t id)
|
||||
{
|
||||
return _rootPane->FocusPane(id);
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
|
||||
void ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
void NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
void MovePane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
bool FocusPane(const uint32_t id);
|
||||
|
||||
void UpdateSettings(const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const GUID& profile);
|
||||
|
||||
@@ -96,7 +96,11 @@ int wmain(int /*argc*/, wchar_t** /*argv*/)
|
||||
|
||||
const auto size = GetConsoleScreenSize(conOut);
|
||||
|
||||
AzureConnection azureConn{ gsl::narrow_cast<uint32_t>(size.Y), gsl::narrow_cast<uint32_t>(size.X) };
|
||||
AzureConnection azureConn{};
|
||||
winrt::Windows::Foundation::Collections::ValueSet vs{};
|
||||
vs.Insert(L"initialRows", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.Y)));
|
||||
vs.Insert(L"initialCols", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.X)));
|
||||
azureConn.Initialize(vs);
|
||||
|
||||
const auto state = RunConnectionToCompletion(azureConn, conOut, conIn);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ Abstract:
|
||||
#include <wil/cppwinrt.h>
|
||||
|
||||
#include <winrt/Windows.system.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
#include <wil/resource.h>
|
||||
|
||||
@@ -71,11 +71,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return (AzureClientID != L"0");
|
||||
}
|
||||
|
||||
AzureConnection::AzureConnection(const uint32_t initialRows, const uint32_t initialCols) :
|
||||
_initialRows{ initialRows },
|
||||
_initialCols{ initialCols },
|
||||
_expiry{}
|
||||
void AzureConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings)
|
||||
{
|
||||
if (settings)
|
||||
{
|
||||
_initialRows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows);
|
||||
_initialCols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols);
|
||||
}
|
||||
}
|
||||
|
||||
// Method description:
|
||||
|
||||
@@ -21,7 +21,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
static winrt::guid ConnectionType() noexcept;
|
||||
static bool IsAzureConnectionAvailable() noexcept;
|
||||
AzureConnection(const uint32_t rows, const uint32_t cols);
|
||||
|
||||
AzureConnection() = default;
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& settings);
|
||||
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Microsoft.Terminal.TerminalConnection
|
||||
static Guid ConnectionType { get; };
|
||||
static Boolean IsAzureConnectionAvailable();
|
||||
|
||||
AzureConnection(UInt32 rows, UInt32 columns);
|
||||
AzureConnection();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
66
src/cascadia/TerminalConnection/ConnectionInformation.cpp
Normal file
66
src/cascadia/TerminalConnection/ConnectionInformation.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "pch.h"
|
||||
#include "ConnectionInformation.h"
|
||||
#include "ConnectionInformation.g.cpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
ConnectionInformation::ConnectionInformation(hstring const& className,
|
||||
const Windows::Foundation::Collections::ValueSet& settings) :
|
||||
_ClassName{ className },
|
||||
_Settings{ settings }
|
||||
{
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Create an instance of the connection specified in the
|
||||
// ConnectionInformation, and Initialize it.
|
||||
// - This static method allows the content process to create a connection
|
||||
// from information that lives in the window process.
|
||||
// Arguments:
|
||||
// - info: A ConnectionInformation object that possibly lives out-of-proc,
|
||||
// containing the name of the WinRT class we should activate for this
|
||||
// connection, and a bag of setting to use to initialize that object.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
TerminalConnection::ITerminalConnection ConnectionInformation::CreateConnection(TerminalConnection::ConnectionInformation info)
|
||||
try
|
||||
{
|
||||
Windows::Foundation::IInspectable inspectable{};
|
||||
|
||||
const auto name = static_cast<HSTRING>(winrt::get_abi(info.ClassName()));
|
||||
const auto pointer = winrt::put_abi(inspectable);
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26490)
|
||||
// C++/WinRT just loves it's void**, nothing we can do here _except_ reinterpret_cast
|
||||
::IInspectable** raw = reinterpret_cast<::IInspectable**>(pointer);
|
||||
#pragma warning(pop)
|
||||
|
||||
// RoActivateInstance() will try to create an instance of the object,
|
||||
// who's fully qualified name is the string in Name().
|
||||
//
|
||||
// The class has to be activatable. For the Terminal, this is easy
|
||||
// enough - we're not hosting anything that's not already in our
|
||||
// manifest, or living as a .dll & .winmd SxS.
|
||||
//
|
||||
// When we get to extensions (GH#4000), we may want to revisit.
|
||||
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Now that thing we made, make sure it's actually a ITerminalConnection
|
||||
if (const auto connection{ inspectable.try_as<TerminalConnection::ITerminalConnection>() })
|
||||
{
|
||||
// Initialize it, and return it.
|
||||
connection.Initialize(info.Settings());
|
||||
return connection;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
43
src/cascadia/TerminalConnection/ConnectionInformation.h
Normal file
43
src/cascadia/TerminalConnection/ConnectionInformation.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- ConnectionInformation.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper object for storing both the name of a type of connection, and
|
||||
a bag of settings to use to initialize that connection.
|
||||
- This helper is used primarily in cross-proc scenarios, to allow the window
|
||||
process to tell the content process the name of the connection type it wants
|
||||
created, and how to set that connection up. This is done so the connection can
|
||||
live entirely in the content process, without having to go through the window
|
||||
process at all.
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
#include "../inc/cppwinrt_utils.h"
|
||||
#include "ConnectionInformation.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct ConnectionInformation : ConnectionInformationT<ConnectionInformation>
|
||||
{
|
||||
ConnectionInformation(hstring const& className,
|
||||
const Windows::Foundation::Collections::ValueSet& settings);
|
||||
|
||||
static TerminalConnection::ITerminalConnection CreateConnection(TerminalConnection::ConnectionInformation info);
|
||||
|
||||
winrt::hstring ClassName() const { return _ClassName; }
|
||||
void ClassName(const winrt::hstring& value) { _ClassName = value; }
|
||||
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::ValueSet, Settings);
|
||||
|
||||
private:
|
||||
winrt::hstring _ClassName{};
|
||||
};
|
||||
}
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ConnectionInformation);
|
||||
}
|
||||
17
src/cascadia/TerminalConnection/ConnectionInformation.idl
Normal file
17
src/cascadia/TerminalConnection/ConnectionInformation.idl
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ITerminalConnection.idl";
|
||||
|
||||
namespace Microsoft.Terminal.TerminalConnection
|
||||
{
|
||||
[default_interface] runtimeclass ConnectionInformation
|
||||
{
|
||||
ConnectionInformation(String className, Windows.Foundation.Collections.ValueSet settings);
|
||||
String ClassName { get; };
|
||||
Windows.Foundation.Collections.ValueSet Settings { get; };
|
||||
|
||||
static ITerminalConnection CreateConnection(ConnectionInformation info);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -116,17 +116,23 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// add additional WT env vars like WT_SETTINGS, WT_DEFAULTS and WT_PROFILE_ID
|
||||
for (auto item : _environment)
|
||||
{
|
||||
auto key = item.Key();
|
||||
auto value = item.Value();
|
||||
|
||||
// avoid clobbering WSLENV
|
||||
if (std::wstring_view{ key } == L"WSLENV")
|
||||
try
|
||||
{
|
||||
auto current = environment[L"WSLENV"];
|
||||
value = current + L":" + value;
|
||||
}
|
||||
auto key = item.Key();
|
||||
// This will throw if the value isn't a string. If that
|
||||
// happens, then just skip this entry.
|
||||
auto value = winrt::unbox_value<hstring>(item.Value());
|
||||
|
||||
environment.insert_or_assign(key.c_str(), value.c_str());
|
||||
// avoid clobbering WSLENV
|
||||
if (std::wstring_view{ key } == L"WSLENV")
|
||||
{
|
||||
auto current = environment[L"WSLENV"];
|
||||
value = current + L":" + value;
|
||||
}
|
||||
|
||||
environment.insert_or_assign(key.c_str(), value.c_str());
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,24 +225,54 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_piClient.hProcess = hClientProcess;
|
||||
}
|
||||
|
||||
ConptyConnection::ConptyConnection(const hstring& commandline,
|
||||
const hstring& startingDirectory,
|
||||
const hstring& startingTitle,
|
||||
const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
|
||||
const uint32_t initialRows,
|
||||
const uint32_t initialCols,
|
||||
const guid& initialGuid) :
|
||||
_initialRows{ initialRows },
|
||||
_initialCols{ initialCols },
|
||||
_commandline{ commandline },
|
||||
_startingDirectory{ startingDirectory },
|
||||
_startingTitle{ startingTitle },
|
||||
_environment{ environment },
|
||||
_guid{ initialGuid },
|
||||
_u8State{},
|
||||
_u16Str{},
|
||||
_buffer{}
|
||||
// Function Description:
|
||||
// - Helper function for constructing a ValueSet that we can use to get our settings from.
|
||||
Windows::Foundation::Collections::ValueSet ConptyConnection::CreateSettings(const winrt::hstring& cmdline,
|
||||
const winrt::hstring& startingDirectory,
|
||||
const winrt::hstring& startingTitle,
|
||||
Windows::Foundation::Collections::IMapView<hstring, hstring> const& environment,
|
||||
uint32_t rows,
|
||||
uint32_t columns,
|
||||
winrt::guid const& guid)
|
||||
{
|
||||
Windows::Foundation::Collections::ValueSet vs{};
|
||||
|
||||
vs.Insert(L"commandline", Windows::Foundation::PropertyValue::CreateString(cmdline));
|
||||
vs.Insert(L"startingDirectory", Windows::Foundation::PropertyValue::CreateString(startingDirectory));
|
||||
vs.Insert(L"startingTitle", Windows::Foundation::PropertyValue::CreateString(startingTitle));
|
||||
vs.Insert(L"initialRows", Windows::Foundation::PropertyValue::CreateUInt32(rows));
|
||||
vs.Insert(L"initialCols", Windows::Foundation::PropertyValue::CreateUInt32(columns));
|
||||
vs.Insert(L"guid", Windows::Foundation::PropertyValue::CreateGuid(guid));
|
||||
|
||||
if (environment)
|
||||
{
|
||||
Windows::Foundation::Collections::ValueSet env{};
|
||||
for (const auto& [k, v] : environment)
|
||||
{
|
||||
env.Insert(k, Windows::Foundation::PropertyValue::CreateString(v));
|
||||
}
|
||||
vs.Insert(L"environment", env);
|
||||
}
|
||||
return vs;
|
||||
}
|
||||
|
||||
void ConptyConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings)
|
||||
{
|
||||
if (settings)
|
||||
{
|
||||
// For the record, the following won't crash:
|
||||
// auto bad = unbox_value_or<hstring>(settings.TryLookup(L"foo").try_as<IPropertyValue>(), nullptr);
|
||||
// It'll just return null
|
||||
|
||||
_commandline = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"commandline").try_as<Windows::Foundation::IPropertyValue>(), _commandline);
|
||||
_startingDirectory = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingDirectory").try_as<Windows::Foundation::IPropertyValue>(), _startingDirectory);
|
||||
_startingTitle = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingTitle").try_as<Windows::Foundation::IPropertyValue>(), _startingTitle);
|
||||
_initialRows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows);
|
||||
_initialCols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols);
|
||||
_guid = winrt::unbox_value_or<winrt::guid>(settings.TryLookup(L"guid").try_as<Windows::Foundation::IPropertyValue>(), _guid);
|
||||
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
|
||||
}
|
||||
|
||||
if (_guid == guid{})
|
||||
{
|
||||
_guid = Utils::CreateGuid();
|
||||
@@ -253,12 +289,21 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
_transitionToState(ConnectionState::Connecting);
|
||||
|
||||
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
|
||||
|
||||
// If we do not have pipes already, then this is a fresh connection... not an inbound one that is a received
|
||||
// handoff from an already-started PTY process.
|
||||
if (!_inPipe)
|
||||
{
|
||||
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
|
||||
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE, &_inPipe, &_outPipe, &_hPC));
|
||||
THROW_IF_FAILED(_LaunchAttachedClient());
|
||||
}
|
||||
// But if it was an inbound handoff... attempt to synchronize the size of it with what our connection
|
||||
// window is expecting it to be on the first layout.
|
||||
else
|
||||
{
|
||||
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), dimensions));
|
||||
}
|
||||
|
||||
_startTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@@ -387,11 +432,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
void ConptyConnection::Resize(uint32_t rows, uint32_t columns)
|
||||
{
|
||||
if (!_hPC)
|
||||
// If we haven't started connecting at all, it's still fair to update
|
||||
// the initial rows and columns before we set things up.
|
||||
if (!_isStateAtOrBeyond(ConnectionState::Connecting))
|
||||
{
|
||||
_initialRows = rows;
|
||||
_initialCols = columns;
|
||||
}
|
||||
// Otherwise, we can really only dispatch a resize if we're already connected.
|
||||
else if (_isConnected())
|
||||
{
|
||||
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(columns, 1), Utils::ClampToShortMax(rows, 1) }));
|
||||
|
||||
@@ -26,14 +26,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
const HANDLE hServerProcess,
|
||||
const HANDLE hClientProcess);
|
||||
|
||||
ConptyConnection(
|
||||
const hstring& cmdline,
|
||||
const hstring& startingDirectory,
|
||||
const hstring& startingTitle,
|
||||
const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
|
||||
const uint32_t rows,
|
||||
const uint32_t cols,
|
||||
const guid& guid);
|
||||
ConptyConnection() noexcept = default;
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& settings);
|
||||
|
||||
static winrt::fire_and_forget final_release(std::unique_ptr<ConptyConnection> connection);
|
||||
|
||||
void Start();
|
||||
@@ -49,6 +44,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
static winrt::event_token NewConnection(NewConnectionHandler const& handler);
|
||||
static void NewConnection(winrt::event_token const& token);
|
||||
|
||||
static Windows::Foundation::Collections::ValueSet CreateSettings(const winrt::hstring& cmdline,
|
||||
const winrt::hstring& startingDirectory,
|
||||
const winrt::hstring& startingTitle,
|
||||
Windows::Foundation::Collections::IMapView<hstring, hstring> const& environment,
|
||||
uint32_t rows,
|
||||
uint32_t columns,
|
||||
winrt::guid const& guid);
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
|
||||
private:
|
||||
@@ -60,10 +63,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
uint32_t _initialRows{};
|
||||
uint32_t _initialCols{};
|
||||
hstring _commandline;
|
||||
hstring _startingDirectory;
|
||||
hstring _startingTitle;
|
||||
Windows::Foundation::Collections::IMapView<hstring, hstring> _environment;
|
||||
hstring _commandline{};
|
||||
hstring _startingDirectory{};
|
||||
hstring _startingTitle{};
|
||||
Windows::Foundation::Collections::ValueSet _environment{ nullptr };
|
||||
guid _guid{}; // A unique session identifier for connected client
|
||||
hstring _clientName{}; // The name of the process hosted by this ConPTY connection (as of launch).
|
||||
|
||||
@@ -77,9 +80,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
wil::unique_static_pseudoconsole_handle _hPC;
|
||||
wil::unique_threadpool_wait _clientExitWait;
|
||||
|
||||
til::u8state _u8State;
|
||||
std::wstring _u16Str;
|
||||
std::array<char, 4096> _buffer;
|
||||
til::u8state _u8State{};
|
||||
std::wstring _u16Str{};
|
||||
std::array<char, 4096> _buffer{};
|
||||
|
||||
DWORD _OutputThread();
|
||||
};
|
||||
|
||||
@@ -7,11 +7,19 @@ namespace Microsoft.Terminal.TerminalConnection
|
||||
{
|
||||
[default_interface] runtimeclass ConptyConnection : ITerminalConnection
|
||||
{
|
||||
ConptyConnection(String cmdline, String startingDirectory, String startingTitle, IMapView<String, String> environment, UInt32 rows, UInt32 columns, Guid guid);
|
||||
ConptyConnection();
|
||||
Guid Guid { get; };
|
||||
|
||||
static event NewConnectionHandler NewConnection;
|
||||
static void StartInboundListener();
|
||||
static void StopInboundListener();
|
||||
|
||||
static Windows.Foundation.Collections.ValueSet CreateSettings(String cmdline,
|
||||
String startingDirectory,
|
||||
String startingTitle,
|
||||
IMapView<String, String> environment,
|
||||
UInt32 rows,
|
||||
UInt32 columns,
|
||||
Guid guid);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
void Resize(uint32_t rows, uint32_t columns) noexcept;
|
||||
void Close() noexcept;
|
||||
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) const noexcept {};
|
||||
|
||||
ConnectionState State() const noexcept { return ConnectionState::Connected; }
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Microsoft.Terminal.TerminalConnection
|
||||
|
||||
interface ITerminalConnection
|
||||
{
|
||||
void Initialize(Windows.Foundation.Collections.ValueSet settings);
|
||||
|
||||
void Start();
|
||||
void WriteInput(String data);
|
||||
void Resize(UInt32 rows, UInt32 columns);
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AzureClientID.h" />
|
||||
<ClInclude Include="ConnectionInformation.h">
|
||||
<DependentUpon>ConnectionInformation.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AzureConnection.h">
|
||||
<DependentUpon>AzureConnection.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -28,6 +31,9 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CTerminalHandoff.cpp" />
|
||||
<ClCompile Include="init.cpp" />
|
||||
<ClCompile Include="ConnectionInformation.cpp">
|
||||
<DependentUpon>ConnectionInformation.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AzureConnection.cpp">
|
||||
<DependentUpon>AzureConnection.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -43,6 +49,7 @@
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="ConnectionInformation.idl" />
|
||||
<Midl Include="ITerminalConnection.idl" />
|
||||
<Midl Include="ConptyConnection.idl" />
|
||||
<Midl Include="EchoConnection.idl" />
|
||||
|
||||
@@ -155,6 +155,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Set up the DX Engine
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
_renderEngine = std::move(dxEngine);
|
||||
|
||||
// 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
|
||||
@@ -168,12 +169,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Then, using the font, get the number of characters that can fit.
|
||||
// Resize our terminal connection to match that size, and initialize the terminal with that size.
|
||||
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
|
||||
LOG_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
LOG_IF_FAILED(_renderEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
|
||||
// Update DxEngine's SelectionBackground
|
||||
dxEngine->SetSelectionBackground(til::color{ _settings.SelectionBackground() });
|
||||
_renderEngine->SetSelectionBackground(til::color{ _settings.SelectionBackground() });
|
||||
|
||||
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
|
||||
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
|
||||
const auto width = vp.Width();
|
||||
const auto height = vp.Height();
|
||||
_connection.Resize(height, width);
|
||||
@@ -188,27 +189,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// after Enable, then it'll be possible to paint the frame once
|
||||
// _before_ the warning handler is set up, and then warnings from
|
||||
// the first paint will be ignored!
|
||||
dxEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1));
|
||||
_renderEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1));
|
||||
|
||||
// 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)
|
||||
dxEngine->SetCallback(std::bind(&ControlCore::_renderEngineSwapChainChanged, this));
|
||||
_renderEngine->SetCallback(std::bind(&ControlCore::_renderEngineSwapChainChanged, this));
|
||||
|
||||
dxEngine->SetRetroTerminalEffect(_settings.RetroTerminalEffect());
|
||||
dxEngine->SetPixelShaderPath(_settings.PixelShaderPath());
|
||||
dxEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
|
||||
dxEngine->SetSoftwareRendering(_settings.SoftwareRendering());
|
||||
_renderEngine->SetRetroTerminalEffect(_settings.RetroTerminalEffect());
|
||||
_renderEngine->SetPixelShaderPath(_settings.PixelShaderPath());
|
||||
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
|
||||
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
|
||||
|
||||
_updateAntiAliasingMode(dxEngine.get());
|
||||
_updateAntiAliasingMode(_renderEngine.get());
|
||||
|
||||
// GH#5098: Inform the engine of the opacity of the default text background.
|
||||
if (_settings.UseAcrylic())
|
||||
{
|
||||
dxEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
|
||||
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(dxEngine->Enable());
|
||||
_renderEngine = std::move(dxEngine);
|
||||
THROW_IF_FAILED(_renderEngine->Enable());
|
||||
|
||||
_initializedTerminal = true;
|
||||
} // scope for TerminalLock
|
||||
@@ -426,7 +426,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// - Updates last hovered cell, renders / removes rendering of hyper-link if required
|
||||
// Arguments:
|
||||
// - terminalPosition: The terminal position of the pointer
|
||||
void ControlCore::UpdateHoveredCell(const std::optional<til::point>& terminalPosition)
|
||||
void ControlCore::SetHoveredCell(Core::Point pos)
|
||||
{
|
||||
_updateHoveredCell(std::optional<til::point>{ pos });
|
||||
}
|
||||
void ControlCore::ClearHoveredCell()
|
||||
{
|
||||
_updateHoveredCell(std::nullopt);
|
||||
}
|
||||
|
||||
void ControlCore::_updateHoveredCell(const std::optional<til::point> terminalPosition)
|
||||
{
|
||||
if (terminalPosition == _lastHoveredCell)
|
||||
{
|
||||
@@ -477,7 +486,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return winrt::hstring{ _terminal->GetHyperlinkAtPosition(pos) };
|
||||
}
|
||||
|
||||
winrt::hstring ControlCore::GetHoveredUriText() const
|
||||
winrt::hstring ControlCore::HoveredUriText() const
|
||||
{
|
||||
auto lock = _terminal->LockForReading(); // Lock for the duration of our reads.
|
||||
if (_lastHoveredCell.has_value())
|
||||
@@ -487,9 +496,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<til::point> ControlCore::GetHoveredCell() const
|
||||
Windows::Foundation::IReference<Core::Point> ControlCore::HoveredCell() const
|
||||
{
|
||||
return _lastHoveredCell;
|
||||
return _lastHoveredCell.has_value() ? Windows::Foundation::IReference<Core::Point>{ _lastHoveredCell.value() } : nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -594,9 +603,34 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
_terminal->SetFontInfo(_actualFont);
|
||||
|
||||
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
|
||||
// actually fail. We need a way to gracefully fallback.
|
||||
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);
|
||||
if (_renderEngine)
|
||||
{
|
||||
std::unordered_map<std::wstring_view, uint32_t> featureMap;
|
||||
if (const auto fontFeatures = _settings.FontFeatures())
|
||||
{
|
||||
featureMap.reserve(fontFeatures.Size());
|
||||
|
||||
for (const auto& [tag, param] : fontFeatures)
|
||||
{
|
||||
featureMap.emplace(tag, param);
|
||||
}
|
||||
}
|
||||
std::unordered_map<std::wstring_view, float> axesMap;
|
||||
if (const auto fontAxes = _settings.FontAxes())
|
||||
{
|
||||
axesMap.reserve(fontAxes.Size());
|
||||
|
||||
for (const auto& [axis, value] : fontAxes)
|
||||
{
|
||||
axesMap.emplace(axis, value);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
|
||||
// actually fail. We need a way to gracefully fallback.
|
||||
LOG_IF_FAILED(_renderEngine->UpdateDpi(newDpi));
|
||||
LOG_IF_FAILED(_renderEngine->UpdateFont(_desiredFont, _actualFont, featureMap, axesMap));
|
||||
}
|
||||
|
||||
// If the actual font isn't what was requested...
|
||||
if (_actualFont.GetFaceName() != _desiredFont.GetFaceName())
|
||||
@@ -895,6 +929,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return _actualFont;
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::Size ControlCore::FontSize() const noexcept
|
||||
{
|
||||
const auto fontSize = GetFont().GetSize();
|
||||
return {
|
||||
::base::saturated_cast<float>(fontSize.X),
|
||||
::base::saturated_cast<float>(fontSize.Y)
|
||||
};
|
||||
}
|
||||
winrt::hstring ControlCore::FontFaceName() const noexcept
|
||||
{
|
||||
return winrt::hstring{ GetFont().GetFaceName() };
|
||||
}
|
||||
|
||||
uint16_t ControlCore::FontWeight() const noexcept
|
||||
{
|
||||
return static_cast<uint16_t>(GetFont().GetWeight());
|
||||
}
|
||||
|
||||
til::size ControlCore::FontSizeInDips() const
|
||||
{
|
||||
const til::size fontSize{ GetFont().GetSize() };
|
||||
@@ -1077,10 +1129,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return _settings.CopyOnSelect();
|
||||
}
|
||||
|
||||
std::vector<std::wstring> ControlCore::SelectedText(bool trimTrailingWhitespace) const
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> ControlCore::SelectedText(bool trimTrailingWhitespace) const
|
||||
{
|
||||
// RetrieveSelectedTextFromBuffer will lock while it's reading
|
||||
return _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace).text;
|
||||
const auto internalResult{ _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace).text };
|
||||
|
||||
auto result = winrt::single_threaded_vector<winrt::hstring>();
|
||||
|
||||
for (const auto& row : internalResult)
|
||||
{
|
||||
result.Append(winrt::hstring{ row });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
::Microsoft::Console::Types::IUiaData* ControlCore::GetUiaData() const
|
||||
@@ -1124,7 +1184,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void ControlCore::SetBackgroundOpacity(const float opacity)
|
||||
void ControlCore::SetBackgroundOpacity(const double opacity)
|
||||
{
|
||||
if (_renderEngine)
|
||||
{
|
||||
@@ -1176,7 +1236,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE ControlCore::GetSwapChainHandle() const
|
||||
uint64_t ControlCore::SwapChainHandle() const
|
||||
{
|
||||
// This is called by:
|
||||
// * TermControl::RenderEngineSwapChainChanged, who is only registered
|
||||
@@ -1184,7 +1244,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// * TermControl::_InitializeTerminal, after the call to Initialize, for
|
||||
// _AttachDxgiSwapChainToXaml.
|
||||
// In both cases, we'll have a _renderEngine by then.
|
||||
return _renderEngine->GetSwapChainHandle();
|
||||
return reinterpret_cast<uint64_t>(_renderEngine->GetSwapChainHandle());
|
||||
}
|
||||
|
||||
void ControlCore::_rendererWarning(const HRESULT hr)
|
||||
|
||||
@@ -48,15 +48,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void UpdateAppearance(const IControlAppearance& newAppearance);
|
||||
void SizeChanged(const double width, const double height);
|
||||
void ScaleChanged(const double scale);
|
||||
HANDLE GetSwapChainHandle() const;
|
||||
uint64_t SwapChainHandle() const;
|
||||
|
||||
void AdjustFontSize(int fontSizeDelta);
|
||||
void ResetFontSize();
|
||||
FontInfo GetFont() const;
|
||||
til::size FontSizeInDips() const;
|
||||
|
||||
winrt::Windows::Foundation::Size FontSize() const noexcept;
|
||||
winrt::hstring FontFaceName() const noexcept;
|
||||
uint16_t FontWeight() const noexcept;
|
||||
|
||||
til::color BackgroundColor() const;
|
||||
void SetBackgroundOpacity(const float opacity);
|
||||
void SetBackgroundOpacity(const double opacity);
|
||||
|
||||
void SendInput(const winrt::hstring& wstr);
|
||||
void PasteText(const winrt::hstring& hstr);
|
||||
@@ -67,10 +71,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void ResumeRendering();
|
||||
|
||||
void UpdatePatternLocations();
|
||||
void UpdateHoveredCell(const std::optional<til::point>& terminalPosition);
|
||||
void SetHoveredCell(Core::Point terminalPosition);
|
||||
void ClearHoveredCell();
|
||||
winrt::hstring GetHyperlink(const til::point position) const;
|
||||
winrt::hstring GetHoveredUriText() const;
|
||||
std::optional<til::point> GetHoveredCell() const;
|
||||
winrt::hstring HoveredUriText() const;
|
||||
Windows::Foundation::IReference<Core::Point> HoveredCell() const;
|
||||
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
|
||||
|
||||
@@ -119,7 +124,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
bool HasSelection() const;
|
||||
bool CopyOnSelect() const;
|
||||
std::vector<std::wstring> SelectedText(bool trimTrailingWhitespace) const;
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> SelectedText(bool trimTrailingWhitespace) const;
|
||||
void SetSelectionAnchor(til::point const& position);
|
||||
void SetEndSelectionPoint(til::point const& position);
|
||||
|
||||
@@ -232,6 +237,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _raiseReadOnlyWarning();
|
||||
void _updateAntiAliasingMode(::Microsoft::Console::Render::DxEngine* const dxEngine);
|
||||
void _connectionOutputHandler(const hstring& hstr);
|
||||
void _updateHoveredCell(const std::optional<til::point> terminalPosition);
|
||||
|
||||
friend class ControlUnitTests::ControlCoreTests;
|
||||
friend class ControlUnitTests::ControlInteractivityTests;
|
||||
|
||||
@@ -8,9 +8,97 @@ import "EventArgs.idl";
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
|
||||
// This is a mirror of
|
||||
// ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState,
|
||||
// but projectable.
|
||||
// !! LOAD BEARING !! If you make this a struct with Booleans (like they
|
||||
// make the most sense as), then the app will crash trying to toss one of
|
||||
// these across the process boundary. I haven't the damndest idea why.
|
||||
[flags]
|
||||
enum MouseButtonState
|
||||
{
|
||||
IsLeftButtonDown = 0x1,
|
||||
IsMiddleButtonDown = 0x2,
|
||||
IsRightButtonDown = 0x4
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ControlCore : ICoreState
|
||||
{
|
||||
ControlCore(IControlSettings settings,
|
||||
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
|
||||
Boolean Initialize(Double actualWidth,
|
||||
Double actualHeight,
|
||||
Double compositionScale);
|
||||
|
||||
void UpdateSettings(IControlSettings settings);
|
||||
void UpdateAppearance(IControlAppearance appearance);
|
||||
|
||||
UInt64 SwapChainHandle { get; };
|
||||
|
||||
Windows.Foundation.Size FontSize { get; };
|
||||
String FontFaceName { get; };
|
||||
UInt16 FontWeight { get; };
|
||||
|
||||
Boolean TrySendKeyEvent(Int16 vkey,
|
||||
Int16 scanCode,
|
||||
Microsoft.Terminal.Core.ControlKeyStates modifiers,
|
||||
Boolean keyDown);
|
||||
Boolean SendCharEvent(Char ch,
|
||||
Int16 scanCode,
|
||||
Microsoft.Terminal.Core.ControlKeyStates modifiers);
|
||||
void SendInput(String text);
|
||||
void PasteText(String text);
|
||||
|
||||
void SetHoveredCell(Microsoft.Terminal.Core.Point terminalPosition);
|
||||
void ClearHoveredCell();
|
||||
|
||||
void ResetFontSize();
|
||||
void AdjustFontSize(Int32 fontSizeDelta);
|
||||
void SizeChanged(Double width, Double height);
|
||||
void ScaleChanged(Double scale);
|
||||
|
||||
void ToggleShaderEffects();
|
||||
void ToggleReadOnlyMode();
|
||||
|
||||
Microsoft.Terminal.Core.Point CursorPosition { get; };
|
||||
void ResumeRendering();
|
||||
void BlinkAttributeTick();
|
||||
void UpdatePatternLocations();
|
||||
void Search(String text, Boolean goForward, Boolean caseSensitive);
|
||||
void SetBackgroundOpacity(Double opacity);
|
||||
Microsoft.Terminal.Core.Color BackgroundColor { get; };
|
||||
|
||||
Boolean HasSelection { get; };
|
||||
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
|
||||
|
||||
String HoveredUriText { get; };
|
||||
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
|
||||
|
||||
void Close();
|
||||
void BlinkCursor();
|
||||
Boolean IsInReadOnlyMode { get; };
|
||||
Boolean CursorOn;
|
||||
void EnablePainting();
|
||||
|
||||
event FontSizeChangedEventArgs FontSizeChanged;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
|
||||
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> BackgroundColorChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> CursorPositionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ConnectionStateChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HoveredHyperlinkChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> SwapChainChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, RendererWarningArgs> RendererWarning;
|
||||
event Windows.Foundation.TypedEventHandler<Object, NoticeEventArgs> RaiseNotice;
|
||||
event Windows.Foundation.TypedEventHandler<Object, TransparencyChangedEventArgs> TransparencyChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ReceivedOutput;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,13 +8,15 @@
|
||||
#include <unicode.hpp>
|
||||
#include <Utf16Parser.hpp>
|
||||
#include <Utils.h>
|
||||
#include <WinUser.h>
|
||||
#include <LibraryResources.h>
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
#include "../../buffer/out/search.h"
|
||||
|
||||
#include "InteractivityAutomationPeer.h"
|
||||
|
||||
#include "ControlInteractivity.g.cpp"
|
||||
#include "TermControl.h"
|
||||
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
using namespace ::Microsoft::Console::VirtualTerminal;
|
||||
@@ -27,6 +29,15 @@ static constexpr unsigned int MAX_CLICK_COUNT = 3;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
static constexpr TerminalInput::MouseButtonState toInternalMouseState(const Control::MouseButtonState& state)
|
||||
{
|
||||
return TerminalInput::MouseButtonState{
|
||||
WI_IsFlagSet(state, MouseButtonState::IsLeftButtonDown),
|
||||
WI_IsFlagSet(state, MouseButtonState::IsMiddleButtonDown),
|
||||
WI_IsFlagSet(state, MouseButtonState::IsRightButtonDown)
|
||||
};
|
||||
}
|
||||
|
||||
ControlInteractivity::ControlInteractivity(IControlSettings settings,
|
||||
TerminalConnection::ITerminalConnection connection) :
|
||||
_touchAnchor{ std::nullopt },
|
||||
@@ -37,6 +48,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_core = winrt::make_self<ControlCore>(settings, connection);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Updates our internal settings. These settings should be
|
||||
// interactivity-specific. Right now, we primarily update _rowsToScroll
|
||||
// with the current value of SPI_GETWHEELSCROLLLINES.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void ControlInteractivity::UpdateSettings()
|
||||
{
|
||||
_updateSystemParameterSettings();
|
||||
@@ -50,9 +69,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_multiClickTimer = GetDoubleClickTime() * 1000;
|
||||
}
|
||||
|
||||
winrt::com_ptr<ControlCore> ControlInteractivity::GetCore()
|
||||
Control::ControlCore ControlInteractivity::Core()
|
||||
{
|
||||
return _core;
|
||||
return *_core;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -85,11 +104,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return _multiClickCounter;
|
||||
}
|
||||
|
||||
void ControlInteractivity::GainFocus()
|
||||
void ControlInteractivity::GotFocus()
|
||||
{
|
||||
if (_uiaEngine.get())
|
||||
{
|
||||
THROW_IF_FAILED(_uiaEngine->Enable());
|
||||
}
|
||||
|
||||
_updateSystemParameterSettings();
|
||||
}
|
||||
|
||||
void ControlInteractivity::LostFocus()
|
||||
{
|
||||
if (_uiaEngine.get())
|
||||
{
|
||||
THROW_IF_FAILED(_uiaEngine->Disable());
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description
|
||||
// - Updates internal params based on system parameters
|
||||
void ControlInteractivity::_updateSystemParameterSettings() noexcept
|
||||
@@ -156,7 +188,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_core->PasteText(winrt::hstring{ wstr });
|
||||
}
|
||||
|
||||
void ControlInteractivity::PointerPressed(TerminalInput::MouseButtonState buttonState,
|
||||
void ControlInteractivity::PointerPressed(Control::MouseButtonState buttonState,
|
||||
const unsigned int pointerUpdateKind,
|
||||
const uint64_t timestamp,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
@@ -170,7 +202,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
// GH#9396: we prioritize hyper-link over VT mouse events
|
||||
auto hyperlink = _core->GetHyperlink(terminalPosition);
|
||||
if (buttonState.isLeftButtonDown &&
|
||||
if (WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown) &&
|
||||
ctrlEnabled && !hyperlink.empty())
|
||||
{
|
||||
const auto clickCount = _numberOfClicks(pixelPosition, timestamp);
|
||||
@@ -182,9 +214,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
else if (_canSendVTMouseInput(modifiers))
|
||||
{
|
||||
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
|
||||
const auto adjustment = _core->ScrollOffset() > 0 ? _core->BufferHeight() - _core->ScrollOffset() - _core->ViewHeight() : 0;
|
||||
// If the click happened outside the active region, just don't send any mouse event
|
||||
if (const auto adjustedY = terminalPosition.y() - adjustment; adjustedY >= 0)
|
||||
{
|
||||
_core->SendMouseEvent({ terminalPosition.x(), adjustedY }, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
|
||||
}
|
||||
}
|
||||
else if (buttonState.isLeftButtonDown)
|
||||
else if (WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown))
|
||||
{
|
||||
const auto clickCount = _numberOfClicks(pixelPosition, timestamp);
|
||||
// This formula enables the number of clicks to cycle properly
|
||||
@@ -219,7 +256,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_singleClickTouchdownPos = std::nullopt;
|
||||
}
|
||||
}
|
||||
else if (buttonState.isRightButtonDown)
|
||||
else if (WI_IsFlagSet(buttonState, MouseButtonState::IsRightButtonDown))
|
||||
{
|
||||
// CopyOnSelect right click always pastes
|
||||
if (_core->CopyOnSelect() || !_core->HasSelection())
|
||||
@@ -238,7 +275,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_touchAnchor = contactPoint;
|
||||
}
|
||||
|
||||
void ControlInteractivity::PointerMoved(TerminalInput::MouseButtonState buttonState,
|
||||
void ControlInteractivity::PointerMoved(Control::MouseButtonState buttonState,
|
||||
const unsigned int pointerUpdateKind,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const bool focused,
|
||||
@@ -249,9 +286,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Short-circuit isReadOnly check to avoid warning dialog
|
||||
if (focused && !_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
|
||||
{
|
||||
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
|
||||
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
|
||||
}
|
||||
else if (focused && buttonState.isLeftButtonDown)
|
||||
else if (focused && WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown))
|
||||
{
|
||||
if (_singleClickTouchdownPos)
|
||||
{
|
||||
@@ -279,7 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
SetEndSelectionPoint(pixelPosition);
|
||||
}
|
||||
|
||||
_core->UpdateHoveredCell(terminalPosition);
|
||||
_core->SetHoveredCell(terminalPosition);
|
||||
}
|
||||
|
||||
void ControlInteractivity::TouchMoved(const til::point newTouchPoint,
|
||||
@@ -319,7 +356,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void ControlInteractivity::PointerReleased(TerminalInput::MouseButtonState buttonState,
|
||||
void ControlInteractivity::PointerReleased(Control::MouseButtonState buttonState,
|
||||
const unsigned int pointerUpdateKind,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const til::point pixelPosition)
|
||||
@@ -328,7 +365,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Short-circuit isReadOnly check to avoid warning dialog
|
||||
if (!_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
|
||||
{
|
||||
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
|
||||
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -366,7 +403,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const int32_t delta,
|
||||
const til::point pixelPosition,
|
||||
const TerminalInput::MouseButtonState state)
|
||||
const Control::MouseButtonState buttonState)
|
||||
{
|
||||
const til::point terminalPosition = _getTerminalPosition(pixelPosition);
|
||||
|
||||
@@ -382,7 +419,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
WM_MOUSEWHEEL,
|
||||
modifiers,
|
||||
::base::saturated_cast<short>(delta),
|
||||
state);
|
||||
toInternalMouseState(buttonState));
|
||||
}
|
||||
|
||||
const auto ctrlPressed = modifiers.IsCtrlPressed();
|
||||
@@ -398,7 +435,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
else
|
||||
{
|
||||
_mouseScrollHandler(delta, pixelPosition, state.isLeftButtonDown);
|
||||
_mouseScrollHandler(delta, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -557,4 +594,36 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Convert the location in pixels to characters within the current viewport.
|
||||
return til::point{ pixelPosition / fontSize };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates an automation peer for the Terminal Control, enabling
|
||||
// accessibility on our control.
|
||||
// - Our implementation implements the ITextProvider pattern, and the
|
||||
// IControlAccessibilityInfo, to connect to the UiaEngine, which must be
|
||||
// attached to the core's renderer.
|
||||
// - The TermControlAutomationPeer will connect this to the UI tree.
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - The automation peer for our control
|
||||
Control::InteractivityAutomationPeer ControlInteractivity::OnCreateAutomationPeer()
|
||||
try
|
||||
{
|
||||
auto autoPeer = winrt::make_self<implementation::InteractivityAutomationPeer>(this);
|
||||
|
||||
_uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(autoPeer.get());
|
||||
_core->AttachUiaEngine(_uiaEngine.get());
|
||||
return *autoPeer;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
::Microsoft::Console::Types::IUiaData* ControlInteractivity::GetUiaData() const
|
||||
{
|
||||
return _core->GetUiaData();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,11 +23,6 @@
|
||||
|
||||
#include "ControlCore.h"
|
||||
|
||||
namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
struct MouseButtonState;
|
||||
}
|
||||
|
||||
namespace ControlUnitTests
|
||||
{
|
||||
class ControlCoreTests;
|
||||
@@ -42,20 +37,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
ControlInteractivity(IControlSettings settings,
|
||||
TerminalConnection::ITerminalConnection connection);
|
||||
|
||||
void GainFocus();
|
||||
void GotFocus();
|
||||
void LostFocus();
|
||||
void UpdateSettings();
|
||||
void Initialize();
|
||||
winrt::com_ptr<ControlCore> GetCore();
|
||||
Control::ControlCore Core();
|
||||
|
||||
Control::InteractivityAutomationPeer OnCreateAutomationPeer();
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
|
||||
|
||||
#pragma region Input Methods
|
||||
void PointerPressed(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
|
||||
void PointerPressed(Control::MouseButtonState buttonState,
|
||||
const unsigned int pointerUpdateKind,
|
||||
const uint64_t timestamp,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const til::point pixelPosition);
|
||||
void TouchPressed(const til::point contactPoint);
|
||||
|
||||
void PointerMoved(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
|
||||
void PointerMoved(Control::MouseButtonState buttonState,
|
||||
const unsigned int pointerUpdateKind,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const bool focused,
|
||||
@@ -63,7 +62,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void TouchMoved(const til::point newTouchPoint,
|
||||
const bool focused);
|
||||
|
||||
void PointerReleased(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
|
||||
void PointerReleased(Control::MouseButtonState buttonState,
|
||||
const unsigned int pointerUpdateKind,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const til::point pixelPosition);
|
||||
@@ -72,7 +71,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const int32_t delta,
|
||||
const til::point pixelPosition,
|
||||
const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state);
|
||||
const Control::MouseButtonState state);
|
||||
|
||||
void UpdateScrollbar(const double newValue);
|
||||
|
||||
@@ -83,7 +82,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void RequestPasteTextFromClipboard();
|
||||
void SetEndSelectionPoint(const til::point pixelPosition);
|
||||
|
||||
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
|
||||
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
|
||||
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
|
||||
|
||||
private:
|
||||
// NOTE: _uiaEngine must be ordered before _core.
|
||||
//
|
||||
// ControlCore::AttachUiaEngine receives a IRenderEngine as a raw pointer, which we own.
|
||||
// We must ensure that we first destroy the ControlCore before the UiaEngine instance
|
||||
// in order to safely resolve this unsafe pointer dependency. Otherwise a deallocated
|
||||
// IRenderEngine is accessed when ControlCore calls Renderer::TriggerTeardown.
|
||||
// (C++ class members are destroyed in reverse order.)
|
||||
std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine;
|
||||
|
||||
winrt::com_ptr<ControlCore> _core{ nullptr };
|
||||
unsigned int _rowsToScroll;
|
||||
double _internalScrollbarPosition{ 0.0 };
|
||||
@@ -129,10 +141,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _sendPastedTextToConnection(std::wstring_view wstr);
|
||||
til::point _getTerminalPosition(const til::point& pixelPosition);
|
||||
|
||||
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
|
||||
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
|
||||
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
|
||||
|
||||
friend class ControlUnitTests::ControlCoreTests;
|
||||
friend class ControlUnitTests::ControlInteractivityTests;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@ import "ICoreState.idl";
|
||||
import "IControlSettings.idl";
|
||||
import "ControlCore.idl";
|
||||
import "EventArgs.idl";
|
||||
import "InteractivityAutomationPeer.idl";
|
||||
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
@@ -13,5 +15,51 @@ namespace Microsoft.Terminal.Control
|
||||
{
|
||||
ControlInteractivity(IControlSettings settings,
|
||||
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
|
||||
ControlCore Core { get; };
|
||||
void UpdateSettings();
|
||||
void Initialize();
|
||||
void GotFocus();
|
||||
void LostFocus();
|
||||
|
||||
InteractivityAutomationPeer OnCreateAutomationPeer();
|
||||
|
||||
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
|
||||
void RequestPasteTextFromClipboard();
|
||||
void SetEndSelectionPoint(Microsoft.Terminal.Core.Point point);
|
||||
|
||||
void PointerPressed(MouseButtonState buttonState,
|
||||
UInt32 pointerUpdateKind,
|
||||
UInt64 timestamp,
|
||||
Microsoft.Terminal.Core.ControlKeyStates modifiers,
|
||||
Microsoft.Terminal.Core.Point pixelPosition);
|
||||
void TouchPressed(Microsoft.Terminal.Core.Point contactPoint);
|
||||
|
||||
void PointerMoved(MouseButtonState buttonState,
|
||||
UInt32 pointerUpdateKind,
|
||||
Microsoft.Terminal.Core.ControlKeyStates modifiers,
|
||||
Boolean focused,
|
||||
Microsoft.Terminal.Core.Point pixelPosition);
|
||||
void TouchMoved(Microsoft.Terminal.Core.Point newTouchPoint,
|
||||
Boolean focused);
|
||||
|
||||
void PointerReleased(MouseButtonState buttonState,
|
||||
UInt32 pointerUpdateKind,
|
||||
Microsoft.Terminal.Core.ControlKeyStates modifiers,
|
||||
Microsoft.Terminal.Core.Point pixelPosition);
|
||||
void TouchReleased();
|
||||
|
||||
Boolean MouseWheel(Microsoft.Terminal.Core.ControlKeyStates modifiers,
|
||||
Int32 delta,
|
||||
Microsoft.Terminal.Core.Point pixelPosition,
|
||||
MouseButtonState state);
|
||||
|
||||
void UpdateScrollbar(Double newValue);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user