mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-07 06:39:44 +00:00
Compare commits
238 Commits
dev/lhecke
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee8082050e | ||
|
|
c365792868 | ||
|
|
4dd9493135 | ||
|
|
cc89787c34 | ||
|
|
c18a4febe7 | ||
|
|
10c6206bfe | ||
|
|
ae7595b8e1 | ||
|
|
23d45a7e3a | ||
|
|
18d4f1ace8 | ||
|
|
4feeef2155 | ||
|
|
e88e0be190 | ||
|
|
6abd72177b | ||
|
|
8c28e132b5 | ||
|
|
1da6131cb2 | ||
|
|
20eabb35ba | ||
|
|
97a617a909 | ||
|
|
c4944c3a23 | ||
|
|
70e44c7915 | ||
|
|
0d6642ac6d | ||
|
|
bfcdc64ab1 | ||
|
|
fc90045cc3 | ||
|
|
4d5962e7b5 | ||
|
|
ca5834e922 | ||
|
|
a49b5a6045 | ||
|
|
6030616d27 | ||
|
|
2e3d5e658e | ||
|
|
405fb51201 | ||
|
|
e413a4148e | ||
|
|
4ebc383cb6 | ||
|
|
fea6eeddfd | ||
|
|
adbe4a0d0c | ||
|
|
7e9f09f495 | ||
|
|
06dc975a0e | ||
|
|
def3742a2e | ||
|
|
5ed3c76dcb | ||
|
|
e491141bd9 | ||
|
|
0d1540bbd2 | ||
|
|
2cfd73d819 | ||
|
|
ee05307379 | ||
|
|
478834756e | ||
|
|
210414e5a8 | ||
|
|
2aefb30355 | ||
|
|
ffda8c4a95 | ||
|
|
2fd33ba510 | ||
|
|
2c165438ef | ||
|
|
0e86ce559e | ||
|
|
35b9e75574 | ||
|
|
da0a6d468a | ||
|
|
c2dd6143ac | ||
|
|
27bcf7e41c | ||
|
|
2c16e7c07b | ||
|
|
1825ca104e | ||
|
|
e106c095a5 | ||
|
|
52171d2dab | ||
|
|
9b960bc88c | ||
|
|
1d354d0f5c | ||
|
|
eb725e9993 | ||
|
|
19069e03be | ||
|
|
21464fe41c | ||
|
|
789b0b065f | ||
|
|
72d0566fa6 | ||
|
|
f671f065bf | ||
|
|
10bdadffbd | ||
|
|
56d451ded7 | ||
|
|
508adbb1ec | ||
|
|
b4f65030e3 | ||
|
|
90bbd2927d | ||
|
|
ea44375f6d | ||
|
|
7fbd3be8c3 | ||
|
|
de09671d8a | ||
|
|
c7498a4269 | ||
|
|
083fc647bb | ||
|
|
6f8ef58673 | ||
|
|
fe66ba5f58 | ||
|
|
e73362d45b | ||
|
|
a98a0cf2c6 | ||
|
|
5db8af6277 | ||
|
|
5f70920491 | ||
|
|
62448969b3 | ||
|
|
ecb5e37a7d | ||
|
|
aea0477bda | ||
|
|
06526cac0c | ||
|
|
9dfdf2afa3 | ||
|
|
2a839d8c5a | ||
|
|
da995a014f | ||
|
|
47a17cf2d7 | ||
|
|
c0f14567f3 | ||
|
|
5de1fd9a7b | ||
|
|
17cf44fa71 | ||
|
|
06174a9cb3 | ||
|
|
0656afcf13 | ||
|
|
0d38d17299 | ||
|
|
7ddd98de0a | ||
|
|
0105807be2 | ||
|
|
e6a3fa8d4e | ||
|
|
bbd4d1b1e4 | ||
|
|
984b03ca33 | ||
|
|
7b0aca444f | ||
|
|
dd63a0590b | ||
|
|
fc95802531 | ||
|
|
da3a33f3bc | ||
|
|
9514c1191a | ||
|
|
17a5b77335 | ||
|
|
34aa6aa0d4 | ||
|
|
c7816fdb05 | ||
|
|
d9efdae982 | ||
|
|
c4d029829a | ||
|
|
f20cd3a9d3 | ||
|
|
f5e9e8ea77 | ||
|
|
36c6b7748e | ||
|
|
b34444f40a | ||
|
|
f06cd1759f | ||
|
|
0f7d1f4568 | ||
|
|
7a2e4f8d9b | ||
|
|
2acdc9d7e2 | ||
|
|
f3a722e0e9 | ||
|
|
e0046a4cca | ||
|
|
5a34d92cb5 | ||
|
|
e1079d8f55 | ||
|
|
b9248fa903 | ||
|
|
bee22f3ec8 | ||
|
|
b6bb3e0a80 | ||
|
|
c5c15e86f3 | ||
|
|
00af187a97 | ||
|
|
2810155046 | ||
|
|
5c9f756891 | ||
|
|
7383b260e1 | ||
|
|
4ca19623ca | ||
|
|
51661487c2 | ||
|
|
65640f6fe3 | ||
|
|
5b434dcda4 | ||
|
|
8c17475a9f | ||
|
|
6c80390de7 | ||
|
|
7562c81066 | ||
|
|
931aa8c87e | ||
|
|
eb871bf8c0 | ||
|
|
fe2220e07b | ||
|
|
3e7e8d59f2 | ||
|
|
599b550817 | ||
|
|
cf87590b31 | ||
|
|
814e44bf45 | ||
|
|
a5fc302716 | ||
|
|
2cd280eeef | ||
|
|
c4c046595e | ||
|
|
e1145c362f | ||
|
|
9dcdcac0bb | ||
|
|
6a8bba96b2 | ||
|
|
c08742ff18 | ||
|
|
b6e6dd861d | ||
|
|
e4bba3cd9a | ||
|
|
9bab7d5d80 | ||
|
|
143127d6c4 | ||
|
|
4903cfd484 | ||
|
|
42e8de3b52 | ||
|
|
8100d24bd0 | ||
|
|
ddc349be81 | ||
|
|
282c583731 | ||
|
|
fc960e3327 | ||
|
|
6a610334af | ||
|
|
7d0baa7946 | ||
|
|
a0e830cc1a | ||
|
|
a4cf4e2761 | ||
|
|
3dd40791c9 | ||
|
|
8f1960d0b4 | ||
|
|
72be9a95af | ||
|
|
596d0c5155 | ||
|
|
79eb9b3d3c | ||
|
|
eab1c239a9 | ||
|
|
96a9dd5209 | ||
|
|
6278a2d9bf | ||
|
|
16fe2e5905 | ||
|
|
f2b82cd054 | ||
|
|
7813953b23 | ||
|
|
47f38e31a1 | ||
|
|
0fe0868f98 | ||
|
|
179bb9bded | ||
|
|
90485e4c79 | ||
|
|
29ef49252b | ||
|
|
5614116c6e | ||
|
|
a1865b9cf7 | ||
|
|
c79298d3fd | ||
|
|
8e041692b3 | ||
|
|
4c7879bfb5 | ||
|
|
f3439e201e | ||
|
|
d3264964a9 | ||
|
|
dd2736f334 | ||
|
|
020746d93d | ||
|
|
dc6d82e9bc | ||
|
|
239b4d16b9 | ||
|
|
fb485a2b40 | ||
|
|
45a36cf83a | ||
|
|
09273be1c8 | ||
|
|
f1090a07f5 | ||
|
|
b7e537e5e7 | ||
|
|
7b9ec0ed6a | ||
|
|
06baead9ea | ||
|
|
21a62c5fef | ||
|
|
2d66dc44f5 | ||
|
|
547349af77 | ||
|
|
7ab0e982c7 | ||
|
|
165d3edde9 | ||
|
|
a3c7bc3349 | ||
|
|
772ed3a7b7 | ||
|
|
dbc27ab041 | ||
|
|
286fdfea79 | ||
|
|
decbc0f5a4 | ||
|
|
f9a36f1ff7 | ||
|
|
6a763bcd41 | ||
|
|
69728632eb | ||
|
|
3a122bf420 | ||
|
|
28d28cb469 | ||
|
|
274bbf0e44 | ||
|
|
68cce101bc | ||
|
|
58ce22d484 | ||
|
|
2668273616 | ||
|
|
a5f9c85c39 | ||
|
|
84bb98bc81 | ||
|
|
4d27a05318 | ||
|
|
86aa666903 | ||
|
|
031271f824 | ||
|
|
a5c5b8a50e | ||
|
|
c5d417fcaf | ||
|
|
b9c3b5cd1e | ||
|
|
79c47f64a2 | ||
|
|
c0e4689e77 | ||
|
|
da2b80bc0a | ||
|
|
32bf894f14 | ||
|
|
b89c8ced22 | ||
|
|
4d9a266c12 | ||
|
|
391abafc2e | ||
|
|
3c78e01ab5 | ||
|
|
4bbe3a388c | ||
|
|
62ffa4ba41 | ||
|
|
37aa29545b | ||
|
|
52cc523ed2 | ||
|
|
0eff8c06e3 | ||
|
|
437914807a | ||
|
|
d1fbbb8a83 |
@@ -1,57 +1,25 @@
|
||||
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Microsoft
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
#AllowShortLambdasOnASingleLine: Inline
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakInheritanceList: AfterColon
|
||||
ColumnLimit: 0
|
||||
CommentPragmas: "suppress"
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
@@ -63,35 +31,13 @@ IncludeCategories:
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: "BEGIN_TEST_METHOD_PROPERTIES|BEGIN_MODULE|BEGIN_TEST_CLASS|BEGIN_TEST_METHOD"
|
||||
MacroBlockEnd: "END_TEST_METHOD_PROPERTIES|END_MODULE|END_TEST_CLASS|END_TEST_METHOD"
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
PointerAlignment: Left
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
|
||||
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,20 +1,14 @@
|
||||
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
|
||||
## Summary of the Pull Request
|
||||
|
||||
<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? -->
|
||||
## References
|
||||
## References and Relevant Issues
|
||||
|
||||
<!-- Please review the items on the PR checklist before submitting-->
|
||||
## PR Checklist
|
||||
* [ ] Closes #xxx
|
||||
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
|
||||
* [ ] Tests added/passed
|
||||
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
|
||||
* [ ] Schema updated.
|
||||
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx
|
||||
|
||||
<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
|
||||
## Detailed Description of the Pull Request / Additional comments
|
||||
|
||||
<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
|
||||
## Validation Steps Performed
|
||||
|
||||
## PR Checklist
|
||||
- [ ] Closes #xxx
|
||||
- [ ] Tests added/passed
|
||||
- [ ] Documentation updated
|
||||
- If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
|
||||
- [ ] Schema updated (if necessary)
|
||||
|
||||
9
.github/actions/spelling/allow/allow.txt
vendored
9
.github/actions/spelling/allow/allow.txt
vendored
@@ -2,6 +2,7 @@ admins
|
||||
allcolors
|
||||
Apc
|
||||
apc
|
||||
backpressure
|
||||
breadcrumb
|
||||
breadcrumbs
|
||||
bsd
|
||||
@@ -12,18 +13,23 @@ clickable
|
||||
clig
|
||||
CMMI
|
||||
copyable
|
||||
CtrlDToClose
|
||||
cybersecurity
|
||||
dalet
|
||||
Dcs
|
||||
dcs
|
||||
deselection
|
||||
dialytika
|
||||
diffing
|
||||
dje
|
||||
downside
|
||||
downsides
|
||||
dze
|
||||
dzhe
|
||||
DTo
|
||||
EDDB
|
||||
EDDC
|
||||
Emacspeak
|
||||
Enum'd
|
||||
Fitt
|
||||
formattings
|
||||
@@ -60,6 +66,7 @@ lol
|
||||
lorem
|
||||
Lorigin
|
||||
maxed
|
||||
megathread
|
||||
minimalistic
|
||||
mkmk
|
||||
mnt
|
||||
@@ -85,6 +92,7 @@ shcha
|
||||
slnt
|
||||
Sos
|
||||
ssh
|
||||
stakeholders
|
||||
timeline
|
||||
timelines
|
||||
timestamped
|
||||
@@ -92,6 +100,7 @@ TLDR
|
||||
tokenizes
|
||||
tonos
|
||||
toolset
|
||||
truthiness
|
||||
tshe
|
||||
ubuntu
|
||||
uiatextrange
|
||||
|
||||
9
.github/actions/spelling/allow/apis.txt
vendored
9
.github/actions/spelling/allow/apis.txt
vendored
@@ -34,7 +34,6 @@ DNE
|
||||
DONTADDTORECENT
|
||||
DWMSBT
|
||||
DWMWA
|
||||
DWMWA
|
||||
DWORDLONG
|
||||
endfor
|
||||
ENDSESSION
|
||||
@@ -55,6 +54,8 @@ GETMOUSEHOVERTIME
|
||||
Hashtable
|
||||
HIGHCONTRASTON
|
||||
HIGHCONTRASTW
|
||||
hinternet
|
||||
HINTERNET
|
||||
hotkeys
|
||||
href
|
||||
hrgn
|
||||
@@ -158,6 +159,7 @@ rcx
|
||||
REGCLS
|
||||
RETURNCMD
|
||||
rfind
|
||||
RLO
|
||||
ROOTOWNER
|
||||
roundf
|
||||
RSHIFT
|
||||
@@ -212,8 +214,11 @@ userenv
|
||||
USEROBJECTFLAGS
|
||||
Viewbox
|
||||
virtualalloc
|
||||
vsnwprintf
|
||||
wcsstr
|
||||
wcstoui
|
||||
WDJ
|
||||
winhttp
|
||||
winmain
|
||||
winsta
|
||||
winstamin
|
||||
@@ -221,6 +226,7 @@ wmemcmp
|
||||
wpc
|
||||
WSF
|
||||
wsregex
|
||||
WWH
|
||||
wwinmain
|
||||
xchg
|
||||
XDocument
|
||||
@@ -246,3 +252,4 @@ xtree
|
||||
xutility
|
||||
YIcon
|
||||
YMax
|
||||
zwstring
|
||||
|
||||
10
.github/actions/spelling/allow/microsoft.txt
vendored
10
.github/actions/spelling/allow/microsoft.txt
vendored
@@ -9,9 +9,11 @@ appxbundle
|
||||
appxerror
|
||||
appxmanifest
|
||||
ATL
|
||||
autoexec
|
||||
backplating
|
||||
bitmaps
|
||||
BOMs
|
||||
COMPUTERNAME
|
||||
CPLs
|
||||
cpptools
|
||||
cppvsdbg
|
||||
@@ -26,6 +28,7 @@ dotnetfeed
|
||||
DTDs
|
||||
DWINRT
|
||||
enablewttlogging
|
||||
HOMESHARE
|
||||
Intelli
|
||||
IVisual
|
||||
libucrt
|
||||
@@ -33,6 +36,7 @@ libucrtd
|
||||
LKG
|
||||
LOCKFILE
|
||||
Lxss
|
||||
makepri
|
||||
mfcribbon
|
||||
microsoft
|
||||
microsoftonline
|
||||
@@ -50,14 +54,19 @@ pgo
|
||||
pgosweep
|
||||
powerrename
|
||||
powershell
|
||||
priconfig
|
||||
PRIINFO
|
||||
propkey
|
||||
pscustomobject
|
||||
QWORD
|
||||
regedit
|
||||
resfiles
|
||||
robocopy
|
||||
SACLs
|
||||
segoe
|
||||
sdkddkver
|
||||
Shobjidl
|
||||
sid
|
||||
Skype
|
||||
SRW
|
||||
sxs
|
||||
@@ -70,6 +79,7 @@ tdbuildteamid
|
||||
ucrt
|
||||
ucrtd
|
||||
unvirtualized
|
||||
USERDNSDOMAIN
|
||||
VCRT
|
||||
vcruntime
|
||||
Virtualization
|
||||
|
||||
12
.github/actions/spelling/allow/names.txt
vendored
12
.github/actions/spelling/allow/names.txt
vendored
@@ -1,6 +1,6 @@
|
||||
Anup
|
||||
austdi
|
||||
arkthur
|
||||
austdi
|
||||
Ballmer
|
||||
bhoj
|
||||
Bhojwani
|
||||
@@ -23,6 +23,7 @@ Griese
|
||||
Hernan
|
||||
Howett
|
||||
Illhardt
|
||||
Imms
|
||||
iquilezles
|
||||
italo
|
||||
jantari
|
||||
@@ -30,10 +31,11 @@ jerrysh
|
||||
Kaiyu
|
||||
kimwalisch
|
||||
KMehrain
|
||||
KODELIFE
|
||||
Kodelife
|
||||
KODELIFE
|
||||
Kourosh
|
||||
kowalczyk
|
||||
leonardder
|
||||
leonmsft
|
||||
Lepilleur
|
||||
lhecker
|
||||
@@ -59,6 +61,7 @@ oising
|
||||
oldnewthing
|
||||
opengl
|
||||
osgwiki
|
||||
Ottosson
|
||||
pabhojwa
|
||||
panos
|
||||
paulcam
|
||||
@@ -77,15 +80,18 @@ sonpham
|
||||
stakx
|
||||
talo
|
||||
thereses
|
||||
Thysell
|
||||
Walisch
|
||||
WDX
|
||||
Wellons
|
||||
Westerman
|
||||
Wirt
|
||||
Wojciech
|
||||
zadjii
|
||||
Zamor
|
||||
Zamora
|
||||
zamora
|
||||
Zamora
|
||||
zljubisic
|
||||
Zoey
|
||||
zorio
|
||||
Zverovich
|
||||
|
||||
2
.github/actions/spelling/excludes.txt
vendored
2
.github/actions/spelling/excludes.txt
vendored
@@ -109,8 +109,10 @@
|
||||
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
|
||||
^src/tools/lnkd/lnkd\.bat$
|
||||
^src/tools/pixels/pixels\.bat$
|
||||
^src/tools/RenderingTests/main.cpp$
|
||||
^src/tools/texttests/fira\.txt$
|
||||
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
|
||||
^src/types/ColorFix.cpp
|
||||
^src/types/ut_types/UtilsTests.cpp$
|
||||
^tools/ReleaseEngineering/ServicingPipeline.ps1$
|
||||
ignore$
|
||||
|
||||
1
.github/actions/spelling/expect/alphabet.txt
vendored
1
.github/actions/spelling/expect/alphabet.txt
vendored
@@ -18,6 +18,7 @@ BBBBBBBB
|
||||
BBBBBCCC
|
||||
BBBBCCCCC
|
||||
BBGGRR
|
||||
efg
|
||||
EFG
|
||||
EFGh
|
||||
QQQQQQQQQQABCDEFGHIJ
|
||||
|
||||
63
.github/actions/spelling/expect/expect.txt
vendored
63
.github/actions/spelling/expect/expect.txt
vendored
@@ -2,6 +2,7 @@ aabbcc
|
||||
ABANDONFONT
|
||||
abbcc
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXY
|
||||
ABCF
|
||||
abgr
|
||||
abi
|
||||
ABORTIFHUNG
|
||||
@@ -37,10 +38,12 @@ ansicpg
|
||||
ANSISYS
|
||||
ANSISYSRC
|
||||
ANSISYSSC
|
||||
answerback
|
||||
antialiasing
|
||||
ANull
|
||||
anycpu
|
||||
APARTMENTTHREADED
|
||||
APCA
|
||||
APCs
|
||||
APIENTRY
|
||||
apiset
|
||||
@@ -61,7 +64,6 @@ argb
|
||||
ARRAYSIZE
|
||||
ARROWKEYS
|
||||
asan
|
||||
ASBRST
|
||||
ASBSET
|
||||
ASDF
|
||||
asdfghjkl
|
||||
@@ -176,7 +178,6 @@ CConsole
|
||||
CConversion
|
||||
CCRT
|
||||
cdd
|
||||
CDeclaration
|
||||
CEdit
|
||||
CELLSIZE
|
||||
cfae
|
||||
@@ -194,7 +195,8 @@ chh
|
||||
chk
|
||||
CHT
|
||||
Cic
|
||||
CLA
|
||||
cielab
|
||||
Cielab
|
||||
Clcompile
|
||||
CLE
|
||||
cleartype
|
||||
@@ -252,11 +254,13 @@ conattrs
|
||||
conbufferout
|
||||
concfg
|
||||
conclnt
|
||||
concretizations
|
||||
conddkrefs
|
||||
condrv
|
||||
conechokey
|
||||
conemu
|
||||
configurability
|
||||
confusables
|
||||
conhost
|
||||
conime
|
||||
conimeinfo
|
||||
@@ -311,7 +315,6 @@ CPLINFO
|
||||
cplusplus
|
||||
CPPCORECHECK
|
||||
cppcorecheckrules
|
||||
cpprest
|
||||
cpprestsdk
|
||||
cppwinrt
|
||||
CProc
|
||||
@@ -332,7 +335,7 @@ Cspace
|
||||
csrmsg
|
||||
CSRSS
|
||||
csrutil
|
||||
cstyle
|
||||
CSTYLE
|
||||
CSwitch
|
||||
CTerminal
|
||||
CText
|
||||
@@ -409,21 +412,26 @@ DECAUPSS
|
||||
DECAWM
|
||||
DECBKM
|
||||
DECCARA
|
||||
DECCIR
|
||||
DECCKM
|
||||
DECCKSR
|
||||
DECCOLM
|
||||
DECCRA
|
||||
DECCTR
|
||||
DECDHL
|
||||
decdld
|
||||
DECDMAC
|
||||
DECDWL
|
||||
DECEKBD
|
||||
DECERA
|
||||
DECFRA
|
||||
DECID
|
||||
DECINVM
|
||||
DECKPAM
|
||||
DECKPM
|
||||
DECKPNM
|
||||
DECLRMM
|
||||
DECMSR
|
||||
DECNKM
|
||||
DECNRCM
|
||||
DECOM
|
||||
@@ -433,9 +441,13 @@ DECRARA
|
||||
DECRC
|
||||
DECREQTPARM
|
||||
DECRLM
|
||||
DECRPM
|
||||
DECRQCRA
|
||||
DECRQM
|
||||
DECRQPSR
|
||||
DECRQSS
|
||||
DECRQTSR
|
||||
DECRSPS
|
||||
decrst
|
||||
DECSACE
|
||||
DECSASD
|
||||
@@ -453,8 +465,10 @@ DECSLRM
|
||||
DECSMKR
|
||||
DECSR
|
||||
DECSTBM
|
||||
DECSTGLT
|
||||
DECSTR
|
||||
DECSWL
|
||||
DECTABSR
|
||||
DECTCEM
|
||||
DECXCPR
|
||||
DEFAPP
|
||||
@@ -474,7 +488,6 @@ defterm
|
||||
DELAYLOAD
|
||||
DELETEONRELEASE
|
||||
Delt
|
||||
demoable
|
||||
depersist
|
||||
deprioritized
|
||||
deserializers
|
||||
@@ -531,7 +544,6 @@ DSSCL
|
||||
DSwap
|
||||
DTest
|
||||
DTTERM
|
||||
DUMMYUNIONNAME
|
||||
dup'ed
|
||||
dvi
|
||||
dwl
|
||||
@@ -575,6 +587,7 @@ ENU
|
||||
ENUMLOGFONT
|
||||
ENUMLOGFONTEX
|
||||
enumranges
|
||||
EOK
|
||||
eplace
|
||||
EPres
|
||||
EQU
|
||||
@@ -584,7 +597,6 @@ ETW
|
||||
EUDC
|
||||
EVENTID
|
||||
eventing
|
||||
everytime
|
||||
evflags
|
||||
evt
|
||||
execd
|
||||
@@ -606,8 +618,12 @@ FACESIZE
|
||||
FAILIFTHERE
|
||||
fastlink
|
||||
fcharset
|
||||
FDEA
|
||||
fdw
|
||||
FECF
|
||||
FEEF
|
||||
fesb
|
||||
FFAF
|
||||
FFDE
|
||||
FFrom
|
||||
fgbg
|
||||
@@ -804,6 +820,8 @@ hkl
|
||||
HKLM
|
||||
hlocal
|
||||
hlsl
|
||||
HMB
|
||||
HMK
|
||||
hmod
|
||||
hmodule
|
||||
hmon
|
||||
@@ -811,6 +829,7 @@ HORZ
|
||||
hostable
|
||||
hostlib
|
||||
HPA
|
||||
hpcon
|
||||
HPCON
|
||||
hpj
|
||||
HPR
|
||||
@@ -892,7 +911,6 @@ INSERTMODE
|
||||
INTERACTIVITYBASE
|
||||
INTERCEPTCOPYPASTE
|
||||
INTERNALNAME
|
||||
inthread
|
||||
intsafe
|
||||
INVALIDARG
|
||||
INVALIDATERECT
|
||||
@@ -1111,6 +1129,7 @@ Mip
|
||||
MMBB
|
||||
mmcc
|
||||
MMCPL
|
||||
MMIX
|
||||
mmsystem
|
||||
MNC
|
||||
MNOPQ
|
||||
@@ -1249,14 +1268,12 @@ ntm
|
||||
nto
|
||||
ntrtl
|
||||
ntstatus
|
||||
ntsubauth
|
||||
NTSYSCALLAPI
|
||||
nttree
|
||||
nturtl
|
||||
ntuser
|
||||
NTVDM
|
||||
ntverp
|
||||
NTWIN
|
||||
nugetversions
|
||||
nullability
|
||||
nullness
|
||||
@@ -1277,6 +1294,8 @@ OEMFONT
|
||||
OEMFORMAT
|
||||
OEMs
|
||||
offboarded
|
||||
oklab
|
||||
Oklab
|
||||
OLEAUT
|
||||
OLECHAR
|
||||
onecore
|
||||
@@ -1295,8 +1314,6 @@ opencode
|
||||
opencon
|
||||
openconsole
|
||||
openconsoleproxy
|
||||
OPENIF
|
||||
OPENLINK
|
||||
openps
|
||||
openvt
|
||||
ORIGINALFILENAME
|
||||
@@ -1349,9 +1366,7 @@ pcg
|
||||
pch
|
||||
PCIDLIST
|
||||
PCIS
|
||||
PCLIENT
|
||||
PCLONG
|
||||
PCOBJECT
|
||||
pcon
|
||||
PCONSOLE
|
||||
PCONSOLEENDTASK
|
||||
@@ -1363,7 +1378,6 @@ pcshell
|
||||
PCSHORT
|
||||
PCSR
|
||||
PCSTR
|
||||
PCUNICODE
|
||||
PCWCH
|
||||
PCWCHAR
|
||||
PCWSTR
|
||||
@@ -1412,7 +1426,6 @@ PLOGICAL
|
||||
pnm
|
||||
PNMLINK
|
||||
pntm
|
||||
PNTSTATUS
|
||||
POBJECT
|
||||
Podcast
|
||||
POINTSLIST
|
||||
@@ -1430,9 +1443,7 @@ PPEB
|
||||
ppf
|
||||
ppguid
|
||||
ppidl
|
||||
pplx
|
||||
PPROC
|
||||
PPROCESS
|
||||
ppropvar
|
||||
ppsi
|
||||
ppsl
|
||||
@@ -1496,7 +1507,6 @@ ptrs
|
||||
ptsz
|
||||
PTYIn
|
||||
PUCHAR
|
||||
PUNICODE
|
||||
pwch
|
||||
PWDDMCONSOLECONTEXT
|
||||
pws
|
||||
@@ -1558,7 +1568,6 @@ REGISTEROS
|
||||
REGISTERVDM
|
||||
regkey
|
||||
REGSTR
|
||||
reingest
|
||||
RELBINPATH
|
||||
remoting
|
||||
renamer
|
||||
@@ -1570,6 +1579,7 @@ replatformed
|
||||
Replymessage
|
||||
repositorypath
|
||||
Requiresx
|
||||
rerasterize
|
||||
rescap
|
||||
Resequence
|
||||
RESETCONTENT
|
||||
@@ -1766,10 +1776,13 @@ srcsrv
|
||||
SRCSRVTRG
|
||||
srctool
|
||||
srect
|
||||
srgb
|
||||
Srgb
|
||||
srv
|
||||
srvinit
|
||||
srvpipe
|
||||
ssa
|
||||
startdir
|
||||
STARTF
|
||||
STARTUPINFO
|
||||
STARTUPINFOEX
|
||||
@@ -1779,6 +1792,7 @@ STARTWPARMS
|
||||
STARTWPARMSA
|
||||
STARTWPARMSW
|
||||
Statusline
|
||||
stb
|
||||
stdafx
|
||||
STDAPI
|
||||
stdc
|
||||
@@ -1818,6 +1832,7 @@ SYSCOMMAND
|
||||
SYSDEADCHAR
|
||||
SYSKEYDOWN
|
||||
SYSKEYUP
|
||||
SYSLIB
|
||||
SYSLINK
|
||||
SYSMENU
|
||||
sysparams
|
||||
@@ -1851,6 +1866,7 @@ TDP
|
||||
TEAMPROJECT
|
||||
tearoff
|
||||
Teb
|
||||
Techo
|
||||
tellp
|
||||
teraflop
|
||||
terminalcore
|
||||
@@ -1942,6 +1958,7 @@ trx
|
||||
tsattrs
|
||||
tsf
|
||||
tsgr
|
||||
tsm
|
||||
TStr
|
||||
TSTRFORMAT
|
||||
TSub
|
||||
@@ -1993,7 +2010,6 @@ unittesting
|
||||
unittests
|
||||
unk
|
||||
unknwn
|
||||
unmark
|
||||
UNORM
|
||||
unparseable
|
||||
unregistering
|
||||
@@ -2106,7 +2122,6 @@ WDDMCONSOLECONTEXT
|
||||
wdm
|
||||
webpage
|
||||
websites
|
||||
websockets
|
||||
wekyb
|
||||
wex
|
||||
wextest
|
||||
@@ -2183,6 +2198,7 @@ wnd
|
||||
WNDALLOC
|
||||
WNDCLASS
|
||||
WNDCLASSEX
|
||||
WNDCLASSEXW
|
||||
WNDCLASSW
|
||||
Wndproc
|
||||
WNegative
|
||||
@@ -2288,6 +2304,7 @@ YOffset
|
||||
YSubstantial
|
||||
YVIRTUALSCREEN
|
||||
YWalk
|
||||
zabcd
|
||||
Zabcdefghijklmnopqrstuvwxyz
|
||||
ZCmd
|
||||
ZCtrl
|
||||
|
||||
2
.github/actions/spelling/patterns/0_t.txt
vendored
2
.github/actions/spelling/patterns/0_t.txt
vendored
@@ -10,4 +10,4 @@
|
||||
\\tests(?![a-z])
|
||||
\\thread(?![a-z])
|
||||
\\tools(?![a-z])
|
||||
\\types(?![a-z])
|
||||
\\types?(?![a-z])
|
||||
|
||||
@@ -35,7 +35,7 @@ ROY\sG\.\sBIV
|
||||
# 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,})
|
||||
(?:^|[\t ,"'`=(])-[X](?!aml)(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
|
||||
# hit-count: 41 file-count: 28
|
||||
# version suffix <word>v#
|
||||
|
||||
1
.github/workflows/addToProject.yml
vendored
1
.github/workflows/addToProject.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
|
||||
24
.github/workflows/winget.yml
vendored
Normal file
24
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Publish to Winget
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
REGEX: 'Microsoft\.WindowsTerminal(?:Preview)?_([\d.]+)_8wekyb3d8bbwe\.msixbundle$'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest # Action can only run on Windows
|
||||
steps:
|
||||
- name: Publish Windows Terminal ${{ github.event.release.prerelease && 'Preview' || 'Stable' }}
|
||||
run: |
|
||||
$assets = '${{ toJSON(github.event.release.assets) }}' | ConvertFrom-Json
|
||||
$wingetRelevantAsset = $assets | Where-Object { $_.name -like '*.msixbundle' } | Select-Object -First 1
|
||||
$regex = [Regex]::New($env:REGEX)
|
||||
$version = $regex.Match($wingetRelevantAsset.name).Groups[1].Value
|
||||
|
||||
$wingetPackage = "Microsoft.WindowsTerminal${{ github.event.release.prerelease && '.Preview' || '' }}"
|
||||
|
||||
& curl.exe -JLO https://aka.ms/wingetcreate/latest
|
||||
& .\wingetcreate.exe update $wingetPackage -s -v $version -u $wingetRelevantAsset.browser_download_url -t "${{ secrets.WINGET_TOKEN }}"
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -6,7 +6,7 @@
|
||||
"C_Cpp.loggingLevel": "None",
|
||||
"files.associations": {
|
||||
"xstring": "cpp",
|
||||
"*.idl": "cpp",
|
||||
"*.idl": "midl3",
|
||||
"array": "cpp",
|
||||
"future": "cpp",
|
||||
"istream": "cpp",
|
||||
@@ -106,4 +106,4 @@
|
||||
"**/packages/**": true,
|
||||
"**/Generated Files/**": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
.vsconfig
45
.vsconfig
@@ -1,36 +1,35 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.Component.MSBuild",
|
||||
"Microsoft.Net.Component.4.5.TargetingPack",
|
||||
"Microsoft.Net.Component.4.TargetingPack",
|
||||
"Microsoft.Net.ComponentGroup.DevelopmentPrerequisites",
|
||||
"Microsoft.NetCore.Component.DevelopmentTools",
|
||||
"Microsoft.VisualStudio.Component.CoreEditor",
|
||||
"Microsoft.VisualStudio.Component.Debugger.JustInTime",
|
||||
"Microsoft.VisualStudio.Component.DiagnosticTools",
|
||||
"Microsoft.VisualStudio.Component.Graphics",
|
||||
"Microsoft.VisualStudio.Component.ManagedDesktop.Core",
|
||||
"Microsoft.VisualStudio.Workload.CoreEditor",
|
||||
"Microsoft.VisualStudio.Workload.Universal",
|
||||
"Microsoft.VisualStudio.Workload.NativeDesktop",
|
||||
"Microsoft.VisualStudio.Workload.ManagedDesktop",
|
||||
"Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites",
|
||||
"Microsoft.VisualStudio.Component.NuGet",
|
||||
"Microsoft.VisualStudio.Component.Roslyn.Compiler",
|
||||
"Microsoft.VisualStudio.Component.Roslyn.LanguageServices",
|
||||
"Microsoft.VisualStudio.Component.UWP.VC.ARM64",
|
||||
"Microsoft.VisualStudio.Component.VC.ASAN",
|
||||
"Microsoft.VisualStudio.Component.VC.CoreIde",
|
||||
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.ARM64",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.VC.v143.ARM64",
|
||||
"Microsoft.VisualStudio.Component.VC.v143.x86.x64",
|
||||
"Microsoft.Net.ComponentGroup.DevelopmentPrerequisites",
|
||||
"Microsoft.Component.MSBuild",
|
||||
"Microsoft.VisualStudio.Component.ManagedDesktop.Core",
|
||||
"Microsoft.Net.Component.4.TargetingPack",
|
||||
"Microsoft.Net.Component.4.5.TargetingPack",
|
||||
"Microsoft.VisualStudio.Component.DiagnosticTools",
|
||||
"Microsoft.VisualStudio.Component.Debugger.JustInTime",
|
||||
"Microsoft.VisualStudio.Component.Windows11SDK.22621",
|
||||
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.Support",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC.v143",
|
||||
"Microsoft.VisualStudio.Component.VC.CoreIde",
|
||||
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
|
||||
"Microsoft.VisualStudio.Component.Graphics",
|
||||
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.ARM64",
|
||||
"Microsoft.VisualStudio.Component.VC.ASAN",
|
||||
"Microsoft.VisualStudio.Component.VC.v143.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.VC.v143.ARM64",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC",
|
||||
"Microsoft.VisualStudio.Workload.CoreEditor",
|
||||
"Microsoft.VisualStudio.Workload.ManagedDesktop",
|
||||
"Microsoft.VisualStudio.Workload.NativeDesktop",
|
||||
"Microsoft.VisualStudio.Workload.Universal"
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC.v143",
|
||||
"Microsoft.VisualStudio.Component.UWP.VC.ARM64"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ If you don't have any additional info/context to add but would like to indicate
|
||||
|
||||
If you're able & willing to help fix issues and/or implement features, we'd love your contribution!
|
||||
|
||||
The best place to start is the list of ["Easy Starter"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+label%3A%22Easy+Starter%22+) issues. These are bugs or tasks that we on the team believe would be easier to implement for someone without any prior experience in the codebase. Once you're feeling more comfortable in the codebase, feel free to just use the ["Help Wanted"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+) label, or just find an issue your interested in and hop in!
|
||||
The best place to start is the list of ["good first issue"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22++label%3A%22good+first+issue%22+)s. These are bugs or tasks that we on the team believe would be easier to implement for someone without any prior experience in the codebase. Once you're feeling more comfortable in the codebase, feel free to just use the ["Help Wanted"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+) label, or just find an issue you're interested in and hop in!
|
||||
|
||||
Generally, we categorize issues in the following way, which is largely derived from our old internal work tracking system:
|
||||
* ["Bugs"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Issue-Bug%22+) are parts of the Terminal & Console that are not quite working the right way. There's code to already support some scenario, but it's not quite working right. Fixing these is generally a matter of debugging the broken functionality and fixing the wrong code.
|
||||
|
||||
68
NOTICE.md
68
NOTICE.md
@@ -276,41 +276,53 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
```
|
||||
|
||||
## ConEmu
|
||||
**Source**: [https://github.com/Maximus5/ConEmu](https://github.com/Maximus5/ConEmu)
|
||||
## stb
|
||||
|
||||
**Source**: [https://github.com/nothings/stb](https://github.com/nothings/stb)
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
BSD 3-Clause License
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
|
||||
Copyright (c) 2009-2017, Maximus5 <ConEmu.Maximus5@gmail.com>
|
||||
All rights reserved.
|
||||
## Oklab
|
||||
**Source**: [https://bottosson.github.io/posts/oklab/](https://bottosson.github.io/posts/oklab/)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
### License
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
Copyright (c) 2020 Björn Ottosson
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
# Microsoft Open Source
|
||||
|
||||
809
OpenConsole.sln
809
OpenConsole.sln
File diff suppressed because it is too large
Load Diff
@@ -55,14 +55,7 @@ Copy-Item "build\helix\runtests.cmd" $payloadDir
|
||||
Copy-Item "build\helix\InstallTestAppDependencies.ps1" "$payloadDir"
|
||||
Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir"
|
||||
|
||||
# Copy the APPX package from the 'drop' artifact dir and Windows Kits
|
||||
Copy-Item "$repoDirectory\Artifacts\$ArtifactName\appx\CascadiaPackage_0.0.1.0_$Platform.msix" $payloadDir\CascadiaPackage.zip
|
||||
Copy-Item "C:\program files (x86)\Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.VCLibs.Desktop\14.0\Appx\Retail\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx" $payloadDir\VCLibs.zip
|
||||
|
||||
# Rename it to extension of ZIP because Expand-Archive is real sassy on the build machines
|
||||
# and refuses to unzip it because of its file extension while on a desktop, it just
|
||||
# does the job without complaining.
|
||||
|
||||
# Extract the APPX package
|
||||
Expand-Archive -LiteralPath $payloadDir\CascadiaPackage.zip -DestinationPath $payloadDir\appx
|
||||
Expand-Archive -LiteralPath $payloadDir\VCLibs.zip -DestinationPath $payloadDir\appx -Force
|
||||
# Extract the unpackaged distribution of Windows Terminal to the payload directory,
|
||||
# where it will create a subdirectory named terminal-0.0.1.0
|
||||
# This is referenced in TerminalApp.cs later as part of the test harness.
|
||||
& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\WindowsTerminalDev_0.0.1.0_x64.zip" -C "$payloadDir"
|
||||
|
||||
@@ -70,7 +70,7 @@ foreach ($testRun in $testRuns.value)
|
||||
|
||||
foreach ($testResult in $testResults.value)
|
||||
{
|
||||
$info = ConvertFrom-Json $testResult.comment
|
||||
$info = ConvertFrom-Json ([System.Web.HttpUtility]::HtmlDecode($testResult.comment))
|
||||
$helixJobId = $info.HelixJobId
|
||||
$helixWorkItemName = $info.HelixWorkItemName
|
||||
|
||||
|
||||
72
build/config/ESRPSigning_Terminal.json
Normal file
72
build/config/ESRPSigning_Terminal.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"Version": "1.0.0",
|
||||
"UseMinimatch": false,
|
||||
"SignBatches": [
|
||||
{
|
||||
"MatchedPath": [
|
||||
// Namespaced DLLs
|
||||
"Microsoft.Terminal.*.dll",
|
||||
"Microsoft.Terminal.*.winmd",
|
||||
|
||||
// ConPTY and DefTerm
|
||||
"OpenConsole.exe",
|
||||
"OpenConsoleProxy.dll",
|
||||
|
||||
// VCRT Forwarders
|
||||
"*_app.dll",
|
||||
|
||||
// Legacy DLLs with old names
|
||||
"TerminalApp.dll",
|
||||
"TerminalApp.winmd",
|
||||
"TerminalConnection.dll",
|
||||
"TerminalThemeHelpers.dll",
|
||||
"WindowsTerminalShellExt.dll",
|
||||
|
||||
// The rest
|
||||
"TerminalAzBridge.exe",
|
||||
"wt.exe",
|
||||
"WindowsTerminal.exe",
|
||||
"elevate-shim.exe"
|
||||
],
|
||||
"SigningInfo": {
|
||||
"Operations": [
|
||||
{
|
||||
"KeyCode": "CP-230012",
|
||||
"OperationSetCode": "SigntoolSign",
|
||||
"Parameters": [
|
||||
{
|
||||
"parameterName": "OpusName",
|
||||
"parameterValue": "Microsoft"
|
||||
},
|
||||
{
|
||||
"parameterName": "OpusInfo",
|
||||
"parameterValue": "http://www.microsoft.com"
|
||||
},
|
||||
{
|
||||
"parameterName": "FileDigest",
|
||||
"parameterValue": "/fd \"SHA256\""
|
||||
},
|
||||
{
|
||||
"parameterName": "PageHash",
|
||||
"parameterValue": "/NPH"
|
||||
},
|
||||
{
|
||||
"parameterName": "TimeStamp",
|
||||
"parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
|
||||
}
|
||||
],
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-230012",
|
||||
"OperationSetCode": "SigntoolVerify",
|
||||
"Parameters": [],
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
|
||||
<package id="Microsoft.Taef" version="10.60.210621002" targetFramework="native" />
|
||||
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
|
||||
<!-- This cannot be included in another project that depends on XAML (as it would be a duplicate package ID) -->
|
||||
<package id="Microsoft.UI.Xaml" version="2.7.3" targetFramework="native" />
|
||||
<package id="Microsoft.Debugging.Tools.PdbStr" version="20220617.1556.0" targetFramework="native" />
|
||||
<package id="Microsoft.Debugging.Tools.SrcTool" version="20220617.1556.0" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -56,14 +56,10 @@ parameters:
|
||||
- x64
|
||||
- x86
|
||||
- arm64
|
||||
- name: buildWindowsVersions
|
||||
type: object
|
||||
default:
|
||||
- Win10
|
||||
- Win11
|
||||
|
||||
variables:
|
||||
TerminalInternalPackageVersion: "0.0.7"
|
||||
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\MakeAppx.exe'
|
||||
TerminalInternalPackageVersion: "0.0.8"
|
||||
# If we are building a branch called "release-*", change the NuGet suffix
|
||||
# to "preview". If we don't do that, XES will set the suffix to "release1"
|
||||
# because it truncates the value after the first period.
|
||||
@@ -86,13 +82,6 @@ variables:
|
||||
NuGetPackBetaVersion: preview
|
||||
${{ elseif eq(variables['Build.SourceBranchName'], 'main') }}:
|
||||
NuGetPackBetaVersion: experimental
|
||||
# The NuGet packages have to use *somebody's* DLLs. We used to force them to
|
||||
# use the Win10 build outputs, but if there isn't a Win10 build we should use
|
||||
# the Win11 one.
|
||||
${{ if containsValue(parameters.buildWindowsVersions, 'Win10') }}:
|
||||
TerminalBestVersionForNuGetPackages: Win10
|
||||
${{ else }}:
|
||||
TerminalBestVersionForNuGetPackages: Win11
|
||||
|
||||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
resources:
|
||||
@@ -106,11 +95,9 @@ jobs:
|
||||
matrix:
|
||||
${{ each config in parameters.buildConfigurations }}:
|
||||
${{ each platform in parameters.buildPlatforms }}:
|
||||
${{ each windowsVersion in parameters.buildWindowsVersions }}:
|
||||
${{ config }}_${{ platform }}_${{ windowsVersion }}:
|
||||
BuildConfiguration: ${{ config }}
|
||||
BuildPlatform: ${{ platform }}
|
||||
TerminalTargetWindowsVersion: ${{ windowsVersion }}
|
||||
${{ config }}_${{ platform }}:
|
||||
BuildConfiguration: ${{ config }}
|
||||
BuildPlatform: ${{ platform }}
|
||||
displayName: Build
|
||||
timeoutInMinutes: 240
|
||||
cancelTimeoutInMinutes: 1
|
||||
@@ -184,10 +171,6 @@ jobs:
|
||||
arguments: -MarkdownNoticePath .\NOTICE.md -OutputPath .\src\cascadia\CascadiaPackage\NOTICE.html
|
||||
pwsh: true
|
||||
- ${{ if eq(parameters.buildTerminal, true) }}:
|
||||
- pwsh: |-
|
||||
./build/scripts/Patch-ManifestsToWindowsVersion.ps1 -NewWindowsVersion "10.0.22000.0"
|
||||
displayName: Update manifest target version to Win11 (if necessary)
|
||||
condition: and(succeeded(), eq(variables['TerminalTargetWindowsVersion'], 'Win11'))
|
||||
- task: VSBuild@1
|
||||
displayName: Build solution **\OpenConsole.sln
|
||||
condition: true
|
||||
@@ -204,7 +187,7 @@ jobs:
|
||||
continueOnError: True
|
||||
inputs:
|
||||
PathtoPublish: $(Build.SourcesDirectory)\msbuild.binlog
|
||||
ArtifactName: binlog-$(BuildPlatform)-$(TerminalTargetWindowsVersion)
|
||||
ArtifactName: binlog-$(BuildPlatform)
|
||||
- task: PowerShell@2
|
||||
displayName: Check MSIX for common regressions
|
||||
inputs:
|
||||
@@ -253,16 +236,12 @@ jobs:
|
||||
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
|
||||
- ${{ if eq(parameters.buildTerminal, true) }}:
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy *.appx/*.msix to Artifacts
|
||||
displayName: Copy *.msix and symbols to Artifacts
|
||||
inputs:
|
||||
Contents: >-
|
||||
**/*.appx
|
||||
|
||||
**/*.msix
|
||||
|
||||
**/*.appxsym
|
||||
|
||||
!**/Microsoft.VCLibs*.appx
|
||||
TargetFolder: $(Build.ArtifactStagingDirectory)/appx
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
@@ -270,11 +249,43 @@ jobs:
|
||||
displayName: 'Generate SBOM manifest'
|
||||
inputs:
|
||||
BuildDropPath: '$(System.ArtifactsDirectory)/appx'
|
||||
|
||||
- pwsh: |-
|
||||
$Package = (Get-ChildItem "$(Build.ArtifactStagingDirectory)/appx" -Recurse -Filter "Cascadia*.msix" | Select -First 1)
|
||||
$PackageFilename = $Package.FullName
|
||||
Write-Host "##vso[task.setvariable variable=WindowsTerminalPackagePath]${PackageFilename}"
|
||||
& "$(MakeAppxPath)" unpack /p $PackageFilename /d "$(Build.SourcesDirectory)\UnpackedTerminalPackage"
|
||||
displayName: Unpack the new Terminal package for signing
|
||||
|
||||
- task: EsrpCodeSigning@1
|
||||
displayName: Submit Terminal's binaries for signing
|
||||
inputs:
|
||||
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
|
||||
FolderPath: '$(Build.SourcesDirectory)\UnpackedTerminalPackage'
|
||||
signType: batchSigning
|
||||
batchSignPolicyFile: '$(Build.SourcesDirectory)\build\config\ESRPSigning_Terminal.json'
|
||||
|
||||
- pwsh: |-
|
||||
$PackageFilename = "$(WindowsTerminalPackagePath)"
|
||||
Remove-Item "$(Build.SourcesDirectory)\UnpackedTerminalPackage\CodeSignSummary*"
|
||||
& "$(MakeAppxPath)" pack /h SHA256 /o /p $PackageFilename /d "$(Build.SourcesDirectory)\UnpackedTerminalPackage"
|
||||
displayName: Re-pack the new Terminal package after signing
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Publish Artifact (appx)
|
||||
inputs:
|
||||
PathtoPublish: $(Build.ArtifactStagingDirectory)/appx
|
||||
ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)-$(TerminalTargetWindowsVersion)
|
||||
ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)
|
||||
|
||||
- pwsh: |-
|
||||
$XamlAppxPath = (Get-Item "src\cascadia\CascadiaPackage\AppPackages\*\Dependencies\$(BuildPlatform)\Microsoft.UI.Xaml*.appx").FullName
|
||||
& .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $(WindowsTerminalPackagePath) -XamlAppX $XamlAppxPath -Destination "$(Build.ArtifactStagingDirectory)/unpackaged"
|
||||
displayName: Build Unpackaged Distribution
|
||||
|
||||
- publish: $(Build.ArtifactStagingDirectory)/unpackaged
|
||||
artifact: unpackaged-$(BuildPlatform)-$(BuildConfiguration)
|
||||
displayName: Publish Artifact (unpackaged)
|
||||
|
||||
- ${{ if eq(parameters.buildConPTY, true) }}:
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy ConPTY to Artifacts
|
||||
@@ -292,7 +303,7 @@ jobs:
|
||||
displayName: Publish Artifact (ConPTY)
|
||||
inputs:
|
||||
PathtoPublish: $(Build.ArtifactStagingDirectory)/conpty
|
||||
ArtifactName: conpty-dll-$(BuildPlatform)-$(BuildConfiguration)-$(TerminalTargetWindowsVersion)
|
||||
ArtifactName: conpty-dll-$(BuildPlatform)-$(BuildConfiguration)
|
||||
- ${{ if eq(parameters.buildWPF, true) }}:
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy PublicTerminalCore.dll to Artifacts
|
||||
@@ -306,7 +317,7 @@ jobs:
|
||||
displayName: Publish Artifact (PublicTerminalCore)
|
||||
inputs:
|
||||
PathtoPublish: $(Build.ArtifactStagingDirectory)/wpf
|
||||
ArtifactName: wpf-dll-$(BuildPlatform)-$(BuildConfiguration)-$(TerminalTargetWindowsVersion)
|
||||
ArtifactName: wpf-dll-$(BuildPlatform)-$(BuildConfiguration)
|
||||
|
||||
- task: PublishSymbols@2
|
||||
displayName: Publish symbols path
|
||||
@@ -324,11 +335,6 @@ jobs:
|
||||
|
||||
- ${{ if eq(parameters.buildTerminal, true) }}:
|
||||
- job: BundleAndSign
|
||||
strategy:
|
||||
matrix:
|
||||
${{ each windowsVersion in parameters.buildWindowsVersions }}:
|
||||
${{ windowsVersion }}:
|
||||
TerminalTargetWindowsVersion: ${{ windowsVersion }}
|
||||
displayName: Create and sign AppX/MSIX bundles
|
||||
variables:
|
||||
${{ if eq(parameters.branding, 'Release') }}:
|
||||
@@ -350,9 +356,9 @@ jobs:
|
||||
disableOutputRedirect: true
|
||||
- ${{ each platform in parameters.buildPlatforms }}:
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download Artifacts ${{ platform }} $(TerminalTargetWindowsVersion)
|
||||
displayName: Download Artifacts ${{ platform }}
|
||||
inputs:
|
||||
artifactName: appx-${{ platform }}-Release-$(TerminalTargetWindowsVersion)
|
||||
artifactName: appx-${{ platform }}-Release
|
||||
# Add 3000 to the major version component, but only for the bundle.
|
||||
# This is to ensure that it is newer than "2022.xx.yy.zz" or whatever the original bundle versions were before
|
||||
# we switched to uniform naming.
|
||||
@@ -362,7 +368,7 @@ jobs:
|
||||
$Components[0] = ([int]$Components[0] + $VersionEpoch)
|
||||
$BundleVersion = $Components -Join "."
|
||||
New-Item -Type Directory "$(System.ArtifactsDirectory)\bundle"
|
||||
.\build\scripts\Create-AppxBundle.ps1 -InputPath "$(System.ArtifactsDirectory)" -ProjectName CascadiaPackage -BundleVersion $BundleVersion -OutputPath "$(System.ArtifactsDirectory)\bundle\$(BundleStemName)_$(TerminalTargetWindowsVersion)_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
|
||||
.\build\scripts\Create-AppxBundle.ps1 -InputPath "$(System.ArtifactsDirectory)" -ProjectName CascadiaPackage -BundleVersion $BundleVersion -OutputPath "$(System.ArtifactsDirectory)\bundle\$(BundleStemName)_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
|
||||
displayName: Create WindowsTerminal*.msixbundle
|
||||
- task: EsrpCodeSigning@1
|
||||
displayName: Submit *.msixbundle to ESRP for code signing
|
||||
@@ -403,7 +409,7 @@ jobs:
|
||||
displayName: 'Publish Artifact: appxbundle-signed'
|
||||
inputs:
|
||||
PathtoPublish: $(System.ArtifactsDirectory)\bundle
|
||||
ArtifactName: appxbundle-signed-$(TerminalTargetWindowsVersion)
|
||||
ArtifactName: appxbundle-signed
|
||||
|
||||
- ${{ if eq(parameters.buildConPTY, true) }}:
|
||||
- job: PackageAndSignConPTY
|
||||
@@ -428,7 +434,7 @@ jobs:
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download ${{ platform }} ConPTY binaries
|
||||
inputs:
|
||||
artifactName: conpty-dll-${{ platform }}-$(BuildConfiguration)-$(TerminalBestVersionForNuGetPackages)
|
||||
artifactName: conpty-dll-${{ platform }}-$(BuildConfiguration)
|
||||
downloadPath: bin\${{ platform }}\$(BuildConfiguration)\
|
||||
extractTars: false
|
||||
- task: PowerShell@2
|
||||
@@ -519,7 +525,7 @@ jobs:
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download ${{ platform }} PublicTerminalCore
|
||||
inputs:
|
||||
artifactName: wpf-dll-${{ platform }}-$(BuildConfiguration)-$(TerminalBestVersionForNuGetPackages)
|
||||
artifactName: wpf-dll-${{ platform }}-$(BuildConfiguration)
|
||||
itemPattern: '**/*.dll'
|
||||
downloadPath: bin\${{ platform }}\$(BuildConfiguration)\
|
||||
extractTars: false
|
||||
@@ -617,11 +623,10 @@ jobs:
|
||||
|
||||
# Download the appx-PLATFORM-CONFIG-VERSION artifact for every platform/version combo
|
||||
- ${{ each platform in parameters.buildPlatforms }}:
|
||||
- ${{ each windowsVersion in parameters.buildWindowsVersions }}:
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download Symbols ${{ platform }} ${{ windowsVersion }}
|
||||
inputs:
|
||||
artifactName: appx-${{ platform }}-Release-${{ windowsVersion }}
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download Symbols ${{ platform }}
|
||||
inputs:
|
||||
artifactName: appx-${{ platform }}-Release
|
||||
|
||||
# It seems easier to do this -- download every appxsym -- then enumerate all the PDBs in the build directory for the
|
||||
# public symbol push. Otherwise, we would have to list all of the PDB files one by one.
|
||||
@@ -681,7 +686,7 @@ jobs:
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: Download Build Artifacts
|
||||
inputs:
|
||||
artifactName: appxbundle-signed-Win11
|
||||
artifactName: appxbundle-signed
|
||||
extractTars: false
|
||||
- task: PowerShell@2
|
||||
displayName: Rename and stage packages for vpack
|
||||
@@ -690,7 +695,7 @@ jobs:
|
||||
script: >-
|
||||
# Rename to known/fixed name for Windows build system
|
||||
|
||||
Get-ChildItem Microsoft.WindowsTerminal_Win11_*.msixbundle | Rename-Item -NewName { 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle' }
|
||||
Get-ChildItem Microsoft.WindowsTerminal_*.msixbundle | Rename-Item -NewName { 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle' }
|
||||
|
||||
|
||||
# Create vpack directory and place item inside
|
||||
@@ -698,13 +703,13 @@ jobs:
|
||||
mkdir WindowsTerminal.app
|
||||
|
||||
mv Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle .\WindowsTerminal.app\
|
||||
workingDirectory: $(System.ArtifactsDirectory)\appxbundle-signed-Win11
|
||||
workingDirectory: $(System.ArtifactsDirectory)\appxbundle-signed
|
||||
- task: PkgESVPack@12
|
||||
displayName: 'Package ES - VPack'
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
sourceDirectory: $(System.ArtifactsDirectory)\appxbundle-signed-Win11\WindowsTerminal.app
|
||||
sourceDirectory: $(System.ArtifactsDirectory)\appxbundle-signed\WindowsTerminal.app
|
||||
description: VPack for the Windows Terminal Application
|
||||
pushPkgName: WindowsTerminal.app
|
||||
owner: conhost
|
||||
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
inputs:
|
||||
TargetPattern: guardianGlob
|
||||
# See https://aka.ms/gdn-globs for how to do match patterns
|
||||
AnalyzeTargetGlob: $(Build.SourcesDirectory)\bin\**\*.dll;$(Build.SourcesDirectory)\bin\**\*.exe;-:file|**\Microsoft.UI.Xaml.dll;-:file|**\Microsoft.Toolkit.Win32.UI.XamlHost.dll;-:file|**\vcruntime*.dll;-:file|**\vcomp*.dll;-:file|**\vccorlib*.dll;-:file|**\vcamp*.dll;-:file|**\msvcp*.dll;-:file|**\concrt*.dll;-:file|**\TerminalThemeHelpers*.dll;-:file|**\cpprest*.dll
|
||||
AnalyzeTargetGlob: $(Build.SourcesDirectory)\bin\**\*.dll;$(Build.SourcesDirectory)\bin\**\*.exe;-:file|**\Microsoft.UI.Xaml.dll;-:file|**\Microsoft.Toolkit.Win32.UI.XamlHost.dll;-:file|**\vcruntime*.dll;-:file|**\vcomp*.dll;-:file|**\vccorlib*.dll;-:file|**\vcamp*.dll;-:file|**\msvcp*.dll;-:file|**\concrt*.dll;-:file|**\TerminalThemeHelpers*.dll
|
||||
continueOnError: true
|
||||
|
||||
# Set XES_SERIALPOSTBUILDREADY to run Security and Compliance task once per build
|
||||
|
||||
@@ -64,17 +64,24 @@ steps:
|
||||
Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}"
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy *.appx/*.msix to Artifacts (Non-PR builds only)'
|
||||
displayName: 'Copy *.msix to Artifacts'
|
||||
inputs:
|
||||
Contents: |
|
||||
**/*.appx
|
||||
**/*.msix
|
||||
**/*.appxsym
|
||||
!**/Microsoft.VCLibs*.appx
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/appx'
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
condition: succeeded()
|
||||
|
||||
- pwsh: |-
|
||||
$TerminalMsixPath = (Get-Item "$(Build.ArtifactStagingDirectory)\appx\Cascadia*.msix").FullName
|
||||
$XamlAppxPath = (Get-Item "src\cascadia\CascadiaPackage\AppPackages\*\Dependencies\$(BuildPlatform)\Microsoft.UI.Xaml*.appx").FullName
|
||||
& .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $TerminalMsixPath -XamlAppX $XamlAppxPath -Destination "$(Build.ArtifactStagingDirectory)/unpackaged"
|
||||
displayName: Build Unpackaged Distribution
|
||||
|
||||
- publish: $(Build.ArtifactStagingDirectory)/unpackaged
|
||||
artifact: unpackaged-$(BuildPlatform)-$(BuildConfiguration)
|
||||
displayName: Publish Artifact (unpackaged)
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy outputs needed for test runs to Artifacts'
|
||||
|
||||
@@ -14,8 +14,8 @@ parameters:
|
||||
platform: ''
|
||||
# if 'useBuildOutputFromBuildId' is set, we will default to using a build from this pipeline:
|
||||
useBuildOutputFromPipeline: $(System.DefinitionId)
|
||||
openHelixTargetQueues: 'windows.10.amd64.client21h1.open.xaml'
|
||||
closedHelixTargetQueues: 'windows.10.amd64.client21h1.xaml'
|
||||
openHelixTargetQueues: 'windows.11.amd64.client.open.reunion'
|
||||
closedHelixTargetQueues: 'windows.11.amd64.client.reunion'
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
|
||||
@@ -6,6 +6,10 @@ steps:
|
||||
|
||||
- task: NuGetAuthenticate@0
|
||||
|
||||
- script: |-
|
||||
echo ##vso[task.setvariable variable=NUGET_RESTORE_MSBUILD_ARGS]/p:Platform=$(BuildPlatform)
|
||||
displayName: Ensure NuGet restores for $(BuildPlatform)
|
||||
|
||||
# In the Microsoft Azure DevOps tenant, NuGetCommand is ambiguous.
|
||||
# This should be `task: NuGetCommand@2`
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
|
||||
16
build/rules/Microsoft.UI.Xaml.Additional.targets
Normal file
16
build/rules/Microsoft.UI.Xaml.Additional.targets
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!--
|
||||
This file contains targets that override behavior in Microsoft.UI.Xaml and
|
||||
related packages.
|
||||
|
||||
For example: All XAML needs is a reference to WebView2; it does not need the
|
||||
DLL and it does not need for us to copy the WinMD into the output folder. It
|
||||
also doesn't require the WebView2 loader since we're not actually using
|
||||
WebView2. Therefore, we can get away with *not including the WebView2
|
||||
package* and only adding a reference to its winmd.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<Reference Include="$(WebView2PackageRoot)\lib\Microsoft.Web.WebView2.Core.winmd" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
78
build/scripts/Merge-PriFiles.ps1
Normal file
78
build/scripts/Merge-PriFiles.ps1
Normal file
@@ -0,0 +1,78 @@
|
||||
Param(
|
||||
[Parameter(Mandatory,
|
||||
HelpMessage="List of PRI files or XML dumps (detailed only) to merge")]
|
||||
[string[]]
|
||||
$Path,
|
||||
|
||||
[Parameter(Mandatory,
|
||||
HelpMessage="Output Path")]
|
||||
[string]
|
||||
$OutputPath,
|
||||
|
||||
[Parameter(HelpMessage="Name of index in output file; defaults to 'Application'")]
|
||||
[string]
|
||||
$IndexName = "Application",
|
||||
|
||||
[Parameter(HelpMessage="Path to makepri.exe")]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$MakePriPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakePri.exe"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp"
|
||||
New-Item -ItemType Directory -Path $tempDir | Out-Null
|
||||
$priConfig = Join-Path $tempDir "priconfig.xml"
|
||||
$priListFile = Join-Path $tempDir "pri.resfiles"
|
||||
$dumpListFile = Join-Path $tempDir "dump.resfiles"
|
||||
|
||||
@"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources targetOsVersion="10.0.0" majorVersion="1">
|
||||
<index root="\" startIndexAt="dump.resfiles">
|
||||
<default>
|
||||
<qualifier name="Language" value="en-US" />
|
||||
<qualifier name="Contrast" value="standard" />
|
||||
<qualifier name="Scale" value="200" />
|
||||
<qualifier name="HomeRegion" value="001" />
|
||||
<qualifier name="TargetSize" value="256" />
|
||||
<qualifier name="LayoutDirection" value="LTR" />
|
||||
<qualifier name="DXFeatureLevel" value="DX9" />
|
||||
<qualifier name="Configuration" value="" />
|
||||
<qualifier name="AlternateForm" value="" />
|
||||
<qualifier name="Platform" value="UAP" />
|
||||
</default>
|
||||
<indexer-config type="PRIINFO" />
|
||||
<indexer-config type="RESFILES" qualifierDelimiter="." />
|
||||
</index>
|
||||
<index root="\" startIndexAt="pri.resfiles">
|
||||
<default>
|
||||
<qualifier name="Language" value="en-US" />
|
||||
<qualifier name="Contrast" value="standard" />
|
||||
<qualifier name="Scale" value="200" />
|
||||
<qualifier name="HomeRegion" value="001" />
|
||||
<qualifier name="TargetSize" value="256" />
|
||||
<qualifier name="LayoutDirection" value="LTR" />
|
||||
<qualifier name="DXFeatureLevel" value="DX9" />
|
||||
<qualifier name="Configuration" value="" />
|
||||
<qualifier name="AlternateForm" value="" />
|
||||
<qualifier name="Platform" value="UAP" />
|
||||
</default>
|
||||
<indexer-config type="PRI" />
|
||||
<indexer-config type="RESFILES" qualifierDelimiter="." />
|
||||
</index>
|
||||
</resources>
|
||||
"@ | Out-File -Encoding:utf8NoBOM $priConfig
|
||||
|
||||
$Path | Where { $_ -Like "*.pri" } | ForEach-Object {
|
||||
Get-Item $_ | Select -Expand FullName
|
||||
} | Out-File -Encoding:utf8NoBOM $priListFile
|
||||
|
||||
$Path | Where { $_ -Like "*.xml" } | ForEach-Object {
|
||||
Get-Item $_ | Select -Expand FullName
|
||||
} | Out-File -Encoding:utf8NoBOM $dumpListFile
|
||||
|
||||
& $MakePriPath new /pr $tempDir /cf $priConfig /o /in $IndexName /of $OutputPath
|
||||
|
||||
Remove-Item -Recurse -Force $tempDir
|
||||
47
build/scripts/Merge-TerminalAndXamlResources.ps1
Normal file
47
build/scripts/Merge-TerminalAndXamlResources.ps1
Normal file
@@ -0,0 +1,47 @@
|
||||
Param(
|
||||
[Parameter(Mandatory,
|
||||
HelpMessage="Root directory of extracted Terminal AppX")]
|
||||
[string[]]
|
||||
$TerminalRoot,
|
||||
|
||||
[Parameter(Mandatory,
|
||||
HelpMessage="Root directory of extracted Xaml AppX")]
|
||||
[string[]]
|
||||
$XamlRoot,
|
||||
|
||||
[Parameter(Mandatory,
|
||||
HelpMessage="Output Path")]
|
||||
[string]
|
||||
$OutputPath,
|
||||
|
||||
[Parameter(HelpMessage="Path to makepri.exe")]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$MakePriPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakePri.exe"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp"
|
||||
New-Item -ItemType Directory -Path $tempDir | Out-Null
|
||||
|
||||
$terminalDump = Join-Path $tempDir "terminal.pri.xml"
|
||||
|
||||
& $MakePriPath dump /if (Join-Path $TerminalRoot "resources.pri") /of $terminalDump /dt detailed
|
||||
|
||||
Write-Verbose "Removing Microsoft.UI.Xaml node from Terminal to prevent a collision with XAML"
|
||||
$terminalXMLDocument = [xml](Get-Content $terminalDump)
|
||||
$resourceMap = $terminalXMLDocument.PriInfo.ResourceMap
|
||||
$fileSubtree = $resourceMap.ResourceMapSubtree | Where-Object { $_.Name -eq "Files" }
|
||||
$subtrees = $fileSubtree.ResourceMapSubtree
|
||||
$xamlSubtreeChild = ($subtrees | Where-Object { $_.Name -eq "Microsoft.UI.Xaml" })
|
||||
if ($Null -Ne $xamlSubtreeChild) {
|
||||
$null = $fileSubtree.RemoveChild($xamlSubtreeChild)
|
||||
$terminalXMLDocument.Save($terminalDump)
|
||||
}
|
||||
|
||||
$indexName = $terminalXMLDocument.PriInfo.ResourceMap.name
|
||||
|
||||
& (Join-Path $PSScriptRoot "Merge-PriFiles.ps1") -Path $terminalDump, (Join-Path $XamlRoot "resources.pri") -IndexName $indexName -OutputPath $OutputPath -MakePriPath $MakePriPath
|
||||
|
||||
Remove-Item -Recurse -Force $tempDir
|
||||
140
build/scripts/New-UnpackagedTerminalDistribution.ps1
Normal file
140
build/scripts/New-UnpackagedTerminalDistribution.ps1
Normal file
@@ -0,0 +1,140 @@
|
||||
[CmdletBinding(DefaultParameterSetName = 'AppX')]
|
||||
Param(
|
||||
[Parameter(Mandatory, HelpMessage="Path to Terminal AppX", ParameterSetName = 'AppX')]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$TerminalAppX,
|
||||
|
||||
[Parameter(Mandatory, HelpMessage="Path to Terminal Layout Deployment", ParameterSetName='Layout')]
|
||||
[ValidateScript({Test-Path $_ -Type Container})]
|
||||
[string]
|
||||
$TerminalLayout,
|
||||
|
||||
[Parameter(Mandatory, HelpMessage="Path to Xaml AppX", ParameterSetName='AppX')]
|
||||
[Parameter(Mandatory, HelpMessage="Path to Xaml AppX", ParameterSetName='Layout')]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$XamlAppX,
|
||||
|
||||
[Parameter(HelpMessage="Output Directory", ParameterSetName='AppX')]
|
||||
[Parameter(HelpMessage="Output Directory", ParameterSetName='Layout')]
|
||||
[string]
|
||||
$Destination = ".",
|
||||
|
||||
[Parameter(HelpMessage="Path to makeappx.exe", ParameterSetName='AppX')]
|
||||
[Parameter(HelpMessage="Path to makeappx.exe", ParameterSetName='Layout')]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakeAppx.exe"
|
||||
)
|
||||
|
||||
$filesToRemove = @("*.xml", "*.winmd", "Appx*", "Images/*Tile*", "Images/*Logo*") # Remove from Terminal
|
||||
$filesToKeep = @("Microsoft.Terminal.Remoting.winmd") # ... except for these
|
||||
$filesToCopyFromXaml = @("Microsoft.UI.Xaml.dll", "Microsoft.UI.Xaml") # We don't need the .winmd
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
If ($null -Eq (Get-Item $MakeAppxPath -EA:SilentlyContinue)) {
|
||||
Write-Error "Could not find MakeAppx.exe at `"$MakeAppxPath`".`nMake sure that -MakeAppxPath points to a valid SDK."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "tmp$([Convert]::ToString((Get-Random 65535),16).PadLeft(4,'0')).tmp"
|
||||
New-Item -ItemType Directory -Path $tempDir | Out-Null
|
||||
|
||||
$XamlAppX = Get-Item $XamlAppX | Select-Object -Expand FullName
|
||||
|
||||
########
|
||||
# Reading the AppX Manifest for preliminary info
|
||||
########
|
||||
|
||||
If ($TerminalAppX) {
|
||||
$appxManifestPath = Join-Path $tempDir AppxManifest.xml
|
||||
& tar.exe -x -f "$TerminalAppX" -C $tempDir AppxManifest.xml
|
||||
} ElseIf($TerminalLayout) {
|
||||
$appxManifestPath = Join-Path $TerminalLayout AppxManifest.xml
|
||||
}
|
||||
$manifest = [xml](Get-Content $appxManifestPath)
|
||||
$pfn = $manifest.Package.Identity.Name
|
||||
$version = $manifest.Package.Identity.Version
|
||||
$architecture = $manifest.Package.Identity.ProcessorArchitecture
|
||||
|
||||
$distributionName = "{0}_{1}_{2}" -f ($pfn, $version, $architecture)
|
||||
$terminalDir = "terminal-{0}" -f ($version)
|
||||
|
||||
########
|
||||
# Unpacking Terminal and XAML
|
||||
########
|
||||
|
||||
$terminalAppPath = Join-Path $tempdir $terminalDir
|
||||
|
||||
If ($TerminalAppX) {
|
||||
$TerminalAppX = Get-Item $TerminalAppX | Select-Object -Expand FullName
|
||||
New-Item -ItemType Directory -Path $terminalAppPath | Out-Null
|
||||
& $MakeAppxPath unpack /p $TerminalAppX /d $terminalAppPath /o | Out-Null
|
||||
If ($LASTEXITCODE -Ne 0) {
|
||||
Throw "Unpacking $TerminalAppX failed"
|
||||
}
|
||||
} ElseIf ($TerminalLayout) {
|
||||
Copy-Item -Recurse -Path $TerminalLayout -Destination $terminalAppPath
|
||||
}
|
||||
|
||||
$xamlAppPath = Join-Path $tempdir "xaml"
|
||||
New-Item -ItemType Directory -Path $xamlAppPath | Out-Null
|
||||
& $MakeAppxPath unpack /p $XamlAppX /d $xamlAppPath /o | Out-Null
|
||||
If ($LASTEXITCODE -Ne 0) {
|
||||
Throw "Unpacking $XamlAppX failed"
|
||||
}
|
||||
|
||||
########
|
||||
# Some sanity checking
|
||||
########
|
||||
|
||||
$xamlManifest = [xml](Get-Content (Join-Path $xamlAppPath "AppxManifest.xml"))
|
||||
If ($xamlManifest.Package.Identity.Name -NotLike "Microsoft.UI.Xaml*") {
|
||||
Throw "$XamlAppX is not a XAML package (instead, it looks like $($xamlManifest.Package.Identity.Name))"
|
||||
}
|
||||
If ($xamlManifest.Package.Identity.ProcessorArchitecture -Ne $architecture) {
|
||||
Throw "$XamlAppX is not built for $architecture (instead, it is built for $($xamlManifest.Package.Identity.ProcessorArchitecture))"
|
||||
}
|
||||
|
||||
########
|
||||
# Preparation of source files
|
||||
########
|
||||
|
||||
$itemsToRemove = $filesToRemove | ForEach-Object {
|
||||
Get-Item (Join-Path $terminalAppPath $_) -EA:SilentlyContinue | Where-Object {
|
||||
$filesToKeep -NotContains $_.Name
|
||||
}
|
||||
} | Sort-Object FullName -Unique
|
||||
$itemsToRemove | Remove-Item -Recurse
|
||||
|
||||
$filesToCopyFromXaml | ForEach-Object {
|
||||
Get-Item (Join-Path $xamlAppPath $_)
|
||||
} | Copy-Item -Recurse -Destination $terminalAppPath
|
||||
|
||||
########
|
||||
# Resource Management
|
||||
########
|
||||
|
||||
$finalTerminalPriFile = Join-Path $terminalAppPath "resources.pri"
|
||||
& (Join-Path $PSScriptRoot "Merge-TerminalAndXamlResources.ps1") `
|
||||
-TerminalRoot $terminalAppPath `
|
||||
-XamlRoot $xamlAppPath `
|
||||
-OutputPath $finalTerminalPriFile `
|
||||
-Verbose:$Verbose | Out-Host
|
||||
|
||||
########
|
||||
# Packaging
|
||||
########
|
||||
|
||||
If ($PSCmdlet.ParameterSetName -Eq "AppX") {
|
||||
# We only produce a ZIP when we're combining two AppX directories.
|
||||
New-Item -ItemType Directory -Path $Destination -ErrorAction:SilentlyContinue | Out-Null
|
||||
$outputZip = (Join-Path $Destination ("{0}.zip" -f ($distributionName)))
|
||||
& tar -c --format=zip -f $outputZip -C $tempDir $terminalDir
|
||||
Remove-Item -Recurse -Force $tempDir -EA:SilentlyContinue
|
||||
Get-Item $outputZip
|
||||
} ElseIf ($PSCmdlet.ParameterSetName -Eq "Layout") {
|
||||
Get-Item $terminalAppPath
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT license.
|
||||
|
||||
Param(
|
||||
[string]$NewWindowsVersion = "10.0.22000.0"
|
||||
)
|
||||
|
||||
Get-ChildItem src/cascadia/CascadiaPackage -Recurse -Filter *.appxmanifest | ForEach-Object {
|
||||
$xml = [xml](Get-Content $_.FullName)
|
||||
$xml.Package.Dependencies.TargetDeviceFamily | Where-Object Name -Like "Windows*" | ForEach-Object {
|
||||
$_.MinVersion = $NewWindowsVersion
|
||||
}
|
||||
$xml.Save($_.FullName)
|
||||
}
|
||||
@@ -70,23 +70,24 @@ Try {
|
||||
|
||||
$dependencies = $Manifest.Package.Dependencies.PackageDependency.Name
|
||||
$depsHasVclibsDesktop = ("Microsoft.VCLibs.140.00.UWPDesktop" -in $dependencies) -or ("Microsoft.VCLibs.140.00.Debug.UWPDesktop" -in $dependencies)
|
||||
$depsHasVcLibsAppX = ("Microsoft.VCLibs.140.00" -in $dependencies) -or ("Microsoft.VCLibs.140.00.Debug" -in $dependencies)
|
||||
$depsHasVclibsAppX = ("Microsoft.VCLibs.140.00" -in $dependencies) -or ("Microsoft.VCLibs.140.00.Debug" -in $dependencies)
|
||||
$filesHasVclibsDesktop = ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140.dll" -EA:Ignore)) -or ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140d.dll" -EA:Ignore))
|
||||
$filesHasVclibsAppX = ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140_app.dll" -EA:Ignore)) -or ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140d_app.dll" -EA:Ignore))
|
||||
|
||||
If ($depsHasVclibsDesktop -Eq $filesHasVclibsDesktop) {
|
||||
$eitherBoth = if ($depsHasVclibsDesktop) { "both" } else { "neither" }
|
||||
$neitherNor = if ($depsHasVclibsDesktop) { "and" } else { "nor" }
|
||||
Throw "Package has $eitherBoth Dependency $neitherNor Integrated Desktop VCLibs"
|
||||
If ($filesHasVclibsDesktop) {
|
||||
Throw "Package contains the desktop VCLibs"
|
||||
}
|
||||
|
||||
If ($depsHasVclibsAppx -Eq $filesHasVclibsAppx) {
|
||||
if ($depsHasVclibsAppx) {
|
||||
# We've shipped like this forever, so downgrade to warning.
|
||||
Write-Warning "Package has both Dependency and Integrated AppX VCLibs"
|
||||
} else {
|
||||
Throw "Package has neither Dependency nor Integrated AppX VCLibs"
|
||||
}
|
||||
If ($depsHasVclibsDesktop) {
|
||||
Throw "Package has a dependency on the desktop VCLibs"
|
||||
}
|
||||
|
||||
If ($filesHasVclibsAppX) {
|
||||
Throw "Package contains the AppX VCLibs"
|
||||
}
|
||||
|
||||
If ($depsHasVclibsAppX) {
|
||||
Throw "Package has a dependency on the AppX VCLibs"
|
||||
}
|
||||
|
||||
### Check that we have an App.xbf (which is a proxy for our resources having been merged)
|
||||
@@ -96,11 +97,6 @@ Try {
|
||||
Throw "Failed to find App.xbf (TerminalApp project) in resources.pri"
|
||||
}
|
||||
|
||||
If (($null -eq (Get-Item "$AppxPackageRootPath\cpprest142_2_10.dll" -EA:Ignore)) -And
|
||||
($null -eq (Get-Item "$AppxPackageRootPath\cpprest142_2_10d.dll" -EA:Ignore))) {
|
||||
Throw "Failed to find cpprest142_2_10.dll -- check the WAP packaging project"
|
||||
}
|
||||
|
||||
If (($null -eq (Get-Item "$AppxPackageRootPath\wtd.exe" -EA:Ignore)) -And
|
||||
($null -eq (Get-Item "$AppxPackageRootPath\wt.exe" -EA:Ignore))) {
|
||||
Throw "Failed to find wt.exe/wtd.exe -- check the WAP packaging project"
|
||||
|
||||
@@ -10,22 +10,4 @@
|
||||
<OpenConsoleDir>$(MSBuildThisFileDirectory)</OpenConsoleDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
For the Windows 10 build, we're targeting the prerelease version of Microsoft.UI.Xaml.
|
||||
This version emits every XAML DLL directly into our package.
|
||||
This is a workaround for us not having deliverable MSFT-21242953 on this version of Windows.
|
||||
|
||||
This version should be tracked in all project packages.config files for projects that depend on Xaml.
|
||||
-->
|
||||
<TerminalMUXVersion>2.7.3-prerelease.220816001</TerminalMUXVersion>
|
||||
<!--
|
||||
For the Windows 11-specific build, we're targeting the public version of Microsoft.UI.Xaml.
|
||||
This version emits a package dependency instead of embedding the dependency in our own package.
|
||||
|
||||
This version should be tracked in build/packages.config.
|
||||
-->
|
||||
<TerminalMUXVersion Condition="'$(TerminalTargetWindowsVersion)'=='Win11'">2.7.3</TerminalMUXVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
16
custom.props
16
custom.props
@@ -2,22 +2,10 @@
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- This file is read by XES, which we use in our Release builds. -->
|
||||
<PropertyGroup Label="Version">
|
||||
<!--
|
||||
The Windows 11 build is going to have the same package name, so it *must* have a different version.
|
||||
The easiest way for us to do this is to add 1 to the revision field.
|
||||
In short, for a given Terminal build 1.11, we will emit two different versions (assume this is build
|
||||
4 on day 23 of the year):
|
||||
- 1.11.234.0 for Windows 10
|
||||
- 1.11.235.0 for Windows 11
|
||||
This presents a potential for conflicts if we want to ship two builds produced back to back on the
|
||||
same day... which is terribly unlikely.
|
||||
-->
|
||||
<VersionBuildRevision Condition="'$(TerminalTargetWindowsVersion)'=='Win11' and '$(VersionBuildRevision)'!=''">$([MSBuild]::Add($(VersionBuildRevision), 1))</VersionBuildRevision>
|
||||
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2022</XesBaseYearForStoreVersion>
|
||||
<XesBaseYearForStoreVersion>2023</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>17</VersionMinor>
|
||||
<VersionMinor>18</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
<!-- The packages.config acts as the global version for all of the NuGet packages contained within. -->
|
||||
<packages>
|
||||
<!-- Native packages -->
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
|
||||
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
|
||||
<package id="Microsoft.Taef" version="10.60.210621002" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
|
||||
<package id="vcpkg-cpprestsdk" version="2.10.14" targetFramework="native" />
|
||||
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
|
||||
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.6.220404001" targetFramework="native" />
|
||||
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.7.3-prerelease.220816001" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.8.3" targetFramework="native" />
|
||||
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220201.1" targetFramework="native" developmentDependency="true" />
|
||||
|
||||
<!-- Managed packages -->
|
||||
|
||||
@@ -145,7 +145,7 @@ Our only backport successes really come from corporations with massive addressab
|
||||
|
||||
It's also costly in terms of time, effort, and testing for us to validate a modification to a released OS. We have a mindbogglingly massive amount of automated machinery dedicated to processing and validating the things that we check in while developing the current OS builds. But it's a special costly ask to spin up some to all of those activities to validate backported fixes. We do it all the time for Patch Tuesday, but in those patches, they only pass through the minimum number of fixes required to maximize the restoration of productivity/security/revenue/etc. because every additional fix adds additional complexity and additional risk.
|
||||
|
||||
So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere everytime we make a change. But that's just not how it is today.
|
||||
So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere every time we make a change. But that's just not how it is today.
|
||||
|
||||
Original Source: https://github.com/microsoft/terminal/issues/279#issuecomment-439179675
|
||||
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
"pattern": "^(-?\\d+)?(,\\s?(-?\\d+)?)?$",
|
||||
"type": "string"
|
||||
},
|
||||
"CSSLengthPercentage": {
|
||||
"pattern": "^[+-]?\\d+(?:\\.\\d+)?(?:%|ch|pt|px)?$",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"DynamicProfileSource": {
|
||||
"enum": [
|
||||
"Windows.Terminal.Wsl",
|
||||
@@ -82,7 +89,14 @@
|
||||
"properties": {
|
||||
"colorScheme": {
|
||||
"description": "The name of a color scheme to use when unfocused.",
|
||||
"type": "string"
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/$defs/SchemePair"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"foreground": {
|
||||
"$ref": "#/$defs/Color",
|
||||
@@ -233,6 +247,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SchemePair": {
|
||||
"description": "Contains both a light and dark color scheme for the Terminal to use, depending on the theme of the application.",
|
||||
"properties": {
|
||||
"light": {
|
||||
"default": "Campbell",
|
||||
"description": "Name of the scheme to use when the app is using light theme",
|
||||
"type": "string"
|
||||
},
|
||||
"dark": {
|
||||
"default": "Campbell",
|
||||
"description": "Name of the scheme to use when the app is using dark theme",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FontConfig": {
|
||||
"properties": {
|
||||
"face": {
|
||||
@@ -292,6 +321,14 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"cellWidth": {
|
||||
"$ref": "#/$defs/CSSLengthPercentage",
|
||||
"description": "Override the width of the terminal's cells. The override works similar to CSS' letter-spacing. It defaults to the natural glyph advance width of the primary font rounded to the nearest pixel."
|
||||
},
|
||||
"cellHeight": {
|
||||
"$ref": "#/$defs/CSSLengthPercentage",
|
||||
"description": "Override the height of the terminal's cells. The override works similar to CSS' line-height. Defaults to the sum of the natural glyph ascend, descend and line-gap of the primary font rounded to the nearest pixel. The default is usually quite close to setting this to 1.2."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -539,6 +576,160 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NewTabMenuEntryType": {
|
||||
"enum": [
|
||||
"source",
|
||||
"profile",
|
||||
"folder",
|
||||
"separator",
|
||||
"remainingProfiles",
|
||||
"matchProfiles"
|
||||
]
|
||||
},
|
||||
"NewTabMenuEntry": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "The type of menu entry",
|
||||
"$ref": "#/$defs/NewTabMenuEntryType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FolderEntryInlining": {
|
||||
"enum": [
|
||||
"never",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
"FolderEntry": {
|
||||
"description": "A folder entry in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "folder"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The name of the folder to show in the menu"
|
||||
},
|
||||
"icon": {
|
||||
"$ref": "#/$defs/Icon"
|
||||
},
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"description": "The entries to put inside this folder",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
}
|
||||
},
|
||||
"inline": {
|
||||
"description": "When set to auto and the folder only has a single entry, the entry will show directly and no folder will be rendered",
|
||||
"default": "never",
|
||||
"$ref": "#/$defs/FolderEntryInlining"
|
||||
},
|
||||
"allowEmpty": {
|
||||
"description": "Whether to render a folder without entries, or to hide it",
|
||||
"default": "false",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SeparatorEntry": {
|
||||
"description": "A separator in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "separator"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ProfileEntry": {
|
||||
"description": "A profile in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "profile"
|
||||
},
|
||||
"profile": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The name or GUID of the profile to show in this entry"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RemainingProfilesEntry": {
|
||||
"description": "The set of profiles that are not yet explicitly included in another entry, such as the profile or source entries. This entry can be used at most one time!",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "remainingProfiles"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"MatchProfilesEntry": {
|
||||
"description": "A set of profiles all matching the given name, source, or command line, to show in the new tab dropdown",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/NewTabMenuEntry"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "matchProfiles"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The name of the profiles to match"
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The source of the profiles to match"
|
||||
},
|
||||
"commandline": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The command line of the profiles to match"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SwitchToAdjacentTabArgs": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -1546,6 +1737,146 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShowCloseButton": {
|
||||
"enum": [
|
||||
"always",
|
||||
"hover",
|
||||
"never",
|
||||
"activeOnly"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThemeColor": {
|
||||
"description": "A special kind of color for use in themes. Can be an #rrggbb color, #rrggbbaa color, or a special value. 'accent' is evaluated as the user's selected Accent color in the OS, and 'terminalBackground' will be evaluated as the background color of the active terminal pane.",
|
||||
"oneOf": [
|
||||
{
|
||||
"pattern": "^#[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{2})?)?$",
|
||||
"type": "string",
|
||||
"format": "color",
|
||||
"default": "#000000ff"
|
||||
},
|
||||
{
|
||||
"const": "accent",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"const": "terminalBackground",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"TabTheme": {
|
||||
"additionalProperties": false,
|
||||
"description": "A set of properties for customizing the appearance of the tabs",
|
||||
"properties": {
|
||||
"background": {
|
||||
"description": "The color of a tab when it is the active tab",
|
||||
"$ref": "#/$defs/ThemeColor"
|
||||
},
|
||||
"unfocusedBackground": {
|
||||
"description": "The color of a tab when it is not the active tab",
|
||||
"$ref": "#/$defs/ThemeColor"
|
||||
},
|
||||
"showCloseButton": {
|
||||
"description": "Controls the visibility of the close button on the tab",
|
||||
"$ref": "#/$defs/ShowCloseButton"
|
||||
}
|
||||
}
|
||||
},
|
||||
"TabRowTheme": {
|
||||
"additionalProperties": false,
|
||||
"description": "A set of properties for customizing the appearance of the tab row",
|
||||
"properties": {
|
||||
"background": {
|
||||
"description": "The color of the tab row when the window is the foreground window.",
|
||||
"$ref": "#/$defs/ThemeColor"
|
||||
},
|
||||
"unfocusedBackground": {
|
||||
"description": "The color of the tab row when the window is inactive",
|
||||
"$ref": "#/$defs/ThemeColor"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WindowTheme": {
|
||||
"additionalProperties": false,
|
||||
"description": "A set of properties for customizing the appearance of the window itself",
|
||||
"properties": {
|
||||
"applicationTheme": {
|
||||
"description": "Which UI theme the Terminal should use for controls",
|
||||
"enum": [ "light", "dark", "system" ],
|
||||
"type": "string"
|
||||
},
|
||||
"useMica": {
|
||||
"description": "True if the Terminal should use a Mica backdrop for the window. This will apply underneath all controls (including the terminal panes and the titlebar)",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"Theme": {
|
||||
"additionalProperties": false,
|
||||
"description": "A set of properties for customizing the appearance of the window. This controls things like the titlebar, the tabs, the application theme.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the theme. This will be displayed in the settings UI.",
|
||||
"not": {
|
||||
"enum": [ "light", "dark", "system" ]
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"$ref": "#/$defs/TabTheme"
|
||||
},
|
||||
"tabRow": {
|
||||
"$ref": "#/$defs/TabRowTheme"
|
||||
},
|
||||
"window": {
|
||||
"$ref": "#/$defs/WindowTheme"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ThemePair": {
|
||||
"additionalProperties": false,
|
||||
"description": "A pair of Theme names, to allow the Terminal to switch theme based on the OS theme",
|
||||
"properties": {
|
||||
"light": {
|
||||
"type": "string",
|
||||
"description": "The name of the theme to use when the OS is in Light theme",
|
||||
"default": "light"
|
||||
},
|
||||
"dark": {
|
||||
"type": "string",
|
||||
"description": "The name of the theme to use when the OS is in Dark theme",
|
||||
"default": "dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NewTabMenu": {
|
||||
"description": "Defines the order and structure of the 'new tab' menu. It can consist of e.g. profiles, folders, and separators.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/$defs/FolderEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/SeparatorEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/ProfileEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/MatchProfilesEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/RemainingProfilesEntry"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -1762,6 +2093,11 @@
|
||||
"description": "When set to true, the terminal will focus the pane on mouse hover.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"compatibility.isolatedMode": {
|
||||
"default": false,
|
||||
"description": "When set to true, Terminal windows will not be able to interact with each other (including global hotkeys, tab drag/drop, running commandlines in existing windows, etc.). This is a compatibility escape hatch for users who are running into certain windowing issues.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyFormatting": {
|
||||
"default": true,
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
|
||||
@@ -1834,6 +2170,11 @@
|
||||
"description": "When set to true, the background image for the currently focused profile is expanded to encompass the entire window, beneath other panes.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"compatibility.reloadEnvironmentVariables": {
|
||||
"default": true,
|
||||
"description": "When set to true, when opening a new tab or pane it will get reloaded environment variables.",
|
||||
"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.",
|
||||
@@ -1924,20 +2265,32 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"newTabMenu": {
|
||||
"$ref": "#/$defs/NewTabMenu"
|
||||
},
|
||||
"language": {
|
||||
"default": "",
|
||||
"description": "Sets an override for the app's preferred language, expressed as a BCP-47 language tag like en-US.",
|
||||
"type": "string"
|
||||
},
|
||||
"theme": {
|
||||
"default": "system",
|
||||
"description": "Sets the theme of the application. The special value \"system\" refers to the active Windows system theme.",
|
||||
"enum": [
|
||||
"light",
|
||||
"dark",
|
||||
"system"
|
||||
],
|
||||
"type": "string"
|
||||
"default": "dark",
|
||||
"description": "Sets the theme of the application. This value should be the name of one of the themes defined in `themes`. The Terminal also includes the themes `dark`, `light`, and `system`.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/ThemePair"
|
||||
}
|
||||
]
|
||||
},
|
||||
"themes": {
|
||||
"description": "The list of available themes",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Theme"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"showTabsInTitlebar": {
|
||||
"default": true,
|
||||
@@ -2170,7 +2523,14 @@
|
||||
"colorScheme": {
|
||||
"default": "Campbell",
|
||||
"description": "Name of the terminal color scheme to use. Color schemes are defined under \"schemes\".",
|
||||
"type": "string"
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/$defs/SchemePair"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commandline": {
|
||||
"description": "Executable used in the profile.",
|
||||
@@ -2215,6 +2575,13 @@
|
||||
"default": false,
|
||||
"description": "When true, this profile should always open in an elevated context. If the window isn't running as an Administrator, then a new elevated window will be created."
|
||||
},
|
||||
"environment": {
|
||||
"description": "Key-value pairs representing environment variables to set. Environment variable names are not case sensitive. You can reference existing environment variable names by enclosing them in literal percent characters (e.g. %PATH%).",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"experimental.autoMarkPrompts": {
|
||||
"default": false,
|
||||
"description": "When set to true, prompts will automatically be marked.",
|
||||
|
||||
235
doc/color_nudging.html
Normal file
235
doc/color_nudging.html
Normal file
@@ -0,0 +1,235 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Perceptual Color Nudging</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
html {
|
||||
background-color: #0c0c0c;
|
||||
color: #cccccc;
|
||||
font-family: "Cascadia Code", "Cascadia Mono", monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body>div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
form,
|
||||
h2 {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
p,
|
||||
pre {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="flex: 2; align-items: flex-start; background-color: #0c0c0c">
|
||||
<form>
|
||||
<input id="background-color" name="background-color" type="color" value="#0c0c0c" />
|
||||
<label for="background-color">background color</label>
|
||||
</form>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Input</td>
|
||||
<td>WCAG21:<br>APCA:</td>
|
||||
<td id="stats-input"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ΔE2000<br>(ConEmu)</td>
|
||||
<td>WCAG21:<br>APCA:</td>
|
||||
<td id="stats-cielab"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ΔEOK</td>
|
||||
<td>WCAG21:<br>APCA:</td>
|
||||
<td id="stats-oklab"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="input" style="flex: 1">
|
||||
<h2>Input</h2>
|
||||
<pre style="font-size: 8pt">猫</pre>
|
||||
<pre style="font-size: 10pt">猫</pre>
|
||||
<pre style="font-size: 12pt">猫</pre>
|
||||
<pre style="font-size: 14pt">猫</pre>
|
||||
<pre style="font-size: 16pt">猫</pre>
|
||||
<pre style="font-size: 32pt">猫</pre>
|
||||
<pre style="font-size: 64pt">猫</pre>
|
||||
</div>
|
||||
<div id="cielab" style="flex: 1">
|
||||
<h2>ΔE2000 (ConEmu)</h2>
|
||||
<pre style="font-size: 8pt">猫</pre>
|
||||
<pre style="font-size: 10pt">猫</pre>
|
||||
<pre style="font-size: 12pt">猫</pre>
|
||||
<pre style="font-size: 14pt">猫</pre>
|
||||
<pre style="font-size: 16pt">猫</pre>
|
||||
<pre style="font-size: 32pt">猫</pre>
|
||||
<pre style="font-size: 64pt">猫</pre>
|
||||
</div>
|
||||
<div id="oklab" style="flex: 1">
|
||||
<h2>ΔEOK</h2>
|
||||
<pre style="font-size: 8pt">猫</pre>
|
||||
<pre style="font-size: 10pt">猫</pre>
|
||||
<pre style="font-size: 12pt">猫</pre>
|
||||
<pre style="font-size: 14pt">猫</pre>
|
||||
<pre style="font-size: 16pt">猫</pre>
|
||||
<pre style="font-size: 32pt">猫</pre>
|
||||
<pre style="font-size: 64pt">猫</pre>
|
||||
</div>
|
||||
<script type="module">
|
||||
import Color from "https://cdn.jsdelivr.net/npm/colorjs.io@0.4.3/+esm";
|
||||
|
||||
window.Color = Color;
|
||||
|
||||
const input = document.getElementById("input");
|
||||
const cielab = document.getElementById("cielab");
|
||||
const oklab = document.getElementById("oklab");
|
||||
|
||||
const statsInput = document.getElementById("stats-input");
|
||||
const statsCielab = document.getElementById("stats-cielab");
|
||||
const statsOklab = document.getElementById("stats-oklab");
|
||||
|
||||
let backgroundColor = new Color("#0c0c0c");
|
||||
let foregroundColor = new Color("#0c0c0c");
|
||||
let foregroundColorRange = null;
|
||||
let previousSecsIntegral = -1;
|
||||
|
||||
function saturate(val) {
|
||||
return val < 0 ? 0 : val > 1 ? 1 : val;
|
||||
}
|
||||
|
||||
function clipToSrgb(color) {
|
||||
return color.to("srgb").toGamut({ method: "clip" });
|
||||
}
|
||||
|
||||
function nudgeCielab(backgroundColor, foregroundColor) {
|
||||
const backgroundCielab = backgroundColor.to("lab-d65");
|
||||
const foregroundCielab = foregroundColor.to("lab-d65");
|
||||
|
||||
const de1 = Color.deltaE(foregroundColor, backgroundCielab, "2000");
|
||||
if (de1 >= 12.0) {
|
||||
return foregroundColor;
|
||||
}
|
||||
|
||||
for (let i = 0; i <= 1; i++) {
|
||||
const step = (i == 0) ? 5.0 : -5.0;
|
||||
foregroundCielab.l += step;
|
||||
|
||||
while (((i == 0) && foregroundCielab.l <= 100) || (i == 1 && foregroundCielab.l >= 0)) {
|
||||
const de2 = Color.deltaE(foregroundCielab, backgroundCielab, "2000");
|
||||
if (de2 >= 20.0) {
|
||||
return clipToSrgb(foregroundCielab);
|
||||
}
|
||||
foregroundCielab.l += step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nudgeOklab(backgroundColor, foregroundColor) {
|
||||
const backgroundOklab = backgroundColor.to("oklab");
|
||||
const foregroundOklab = foregroundColor.to("oklab");
|
||||
const deltaSquared = {
|
||||
l: (backgroundOklab.l - foregroundOklab.l) ** 2,
|
||||
a: (backgroundOklab.a - foregroundOklab.a) ** 2,
|
||||
b: (backgroundOklab.b - foregroundOklab.b) ** 2,
|
||||
};
|
||||
const distance = deltaSquared.l + deltaSquared.a + deltaSquared.b;
|
||||
|
||||
if (distance >= 0.25) {
|
||||
return foregroundColor;
|
||||
}
|
||||
|
||||
let deltaL = Math.sqrt(0.25 - deltaSquared.a - deltaSquared.b);
|
||||
if (foregroundOklab.l < backgroundOklab.l)
|
||||
{
|
||||
deltaL = -deltaL;
|
||||
}
|
||||
|
||||
foregroundOklab.l = backgroundOklab.l + deltaL;
|
||||
if (foregroundOklab.l < 0 || foregroundOklab.l > 1)
|
||||
{
|
||||
foregroundOklab.l = backgroundOklab.l - deltaL;
|
||||
}
|
||||
|
||||
return clipToSrgb(foregroundOklab);
|
||||
}
|
||||
|
||||
function contrastStringLevels(num, level0, level1) {
|
||||
const str = num.toFixed(1);
|
||||
if (num < level0) {
|
||||
return `<span style="color:crimson">${str}</span>`;
|
||||
}
|
||||
if (num < level1) {
|
||||
return `<span style="color:coral">${str}</span>`;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function contrastString(foregroundColor) {
|
||||
const contrastWCAG21 = contrastStringLevels(foregroundColor.contrast(backgroundColor, "WCAG21"), 3, 4.5);
|
||||
const contrastAPCA = contrastStringLevels(Math.abs(foregroundColor.contrast(backgroundColor, "APCA")), 45, 60);
|
||||
return `${contrastWCAG21}<br/>${contrastAPCA}`;
|
||||
}
|
||||
|
||||
function animate(time) {
|
||||
const timeScale = time / 1000;
|
||||
const secsIntegral = Math.trunc(timeScale);
|
||||
const secsFractional = timeScale % 1;
|
||||
|
||||
if (previousSecsIntegral != secsIntegral) {
|
||||
const foregroundColorTarget = new Color("srgb", backgroundColor.coords.map(c => saturate(c + Math.random() - 0.5)));
|
||||
foregroundColorRange = foregroundColor.range(foregroundColorTarget, { space: "srgb" });
|
||||
previousSecsIntegral = secsIntegral;
|
||||
}
|
||||
|
||||
foregroundColor = foregroundColorRange(secsFractional);
|
||||
input.style.color = foregroundColor.toString({ inGamut: false });
|
||||
|
||||
const foregroundCielabNudged = nudgeCielab(backgroundColor, foregroundColor);
|
||||
const foregroundOklabNudged = nudgeOklab(backgroundColor, foregroundColor);
|
||||
|
||||
cielab.style.color = foregroundCielabNudged;
|
||||
oklab.style.color = foregroundOklabNudged;
|
||||
|
||||
statsInput.innerHTML = contrastString(foregroundColor);
|
||||
statsCielab.innerHTML = contrastString(foregroundCielabNudged);
|
||||
statsOklab.innerHTML = contrastString(foregroundOklabNudged);
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
document.getElementById("background-color").addEventListener("input", event => {
|
||||
backgroundColor = new Color(event.target.value);
|
||||
document.documentElement.style.backgroundColor = backgroundColor;
|
||||
}, false);
|
||||
|
||||
document.documentElement.style.backgroundColor = backgroundColor;
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,410 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2021-03-03
|
||||
last updated: 2022-11-04
|
||||
issue id: #2634
|
||||
---
|
||||
|
||||
# Broadcast Input
|
||||
|
||||
## Abstract
|
||||
|
||||
"Broadcast Input" is a feature present on other terminals which allows the user
|
||||
to send the same input to multiple tabs or panes at the same time. This can make
|
||||
it simpler for the user to run the same command in multiple directories or
|
||||
servers at the same time.
|
||||
|
||||
With a viable prototype in [#9222], it's important that we have a well-defined
|
||||
plan for how we want this feature to be exposed before merging that PR. This
|
||||
spec is intended to be a lighter-than-usual spec to build consensus on the
|
||||
design of how the actions should be expressed.
|
||||
|
||||
## Background
|
||||
|
||||
### Inspiration
|
||||
|
||||
This spec is heavily inspired by the [iTerm2 implementation]. @carlos-zamora did
|
||||
a great job of breaking down how iTerm2 works in [this comment].
|
||||
|
||||
SecureCRT also implements a similar feature using a "chat window" that can send
|
||||
the input in the chat window to all tabs. This seemed like a less ergonomic
|
||||
solution, so it was not seriously considered.
|
||||
|
||||
Additionally, Terminator (on \*nix) allows for a similar feature through the use
|
||||
of "groups". From [@zljubisic]:
|
||||
|
||||
> In Linux terminator you can define groups, and than put each pane in one of
|
||||
> defined groups. Afterwards, you can choose broadcasting to all panes, only
|
||||
> certain group or no broadcast at all.
|
||||
|
||||
This also seemed like a less powerful version of broadcast input than the
|
||||
iterm2-like version, so it was also not further investigated.
|
||||
|
||||
### User Stories
|
||||
|
||||
iTerm2 supports the following actions:
|
||||
|
||||
* **Story A:** _Send input to current session only_: The default setting.
|
||||
* **Story B:** _Broadcast to all panes in all tabs_: Anything you type on the
|
||||
keyboard goes to all sessions in this window.
|
||||
* **Story C:** _Broadcast to all panes in current tab_: Anything you type on the
|
||||
keyboard goes to all sessions in this tab.
|
||||
* **Story D:** _Toggle broadcast input to current session_: Toggles whether this
|
||||
session receives broadcasted keystrokes within this window.
|
||||
|
||||
|
||||
## Solution Design
|
||||
|
||||
### Proposal 1: iTerm2-like Modal Input Broadcast
|
||||
|
||||
iTerm2 implements broadcast input as a type of "modal" system. The user is in
|
||||
one of the following modes:
|
||||
|
||||
* Broadcast to all panes in all tabs
|
||||
* Broadcast to all panes in the current tab
|
||||
* Broadcast to some set of panes within the current tab
|
||||
* Don't broadcast input at all (the default behavior)
|
||||
|
||||
These modes are vaguely per-tab state. There's a global "broadcast to all tabs &
|
||||
panes" property. Then, each tab also has a pair of values:
|
||||
* Should input be sent to all panes in this tab?
|
||||
* If not, which panes should input be sent to?
|
||||
|
||||
It's not possible to send input to one pane in tab A, then another pane in tab
|
||||
B, without enabling the global "broadcast to everyone" mode.
|
||||
|
||||
This seems to break down into the following actions:
|
||||
|
||||
```json
|
||||
{ "action": "toggleBroadcastInput", "scope": "window" },
|
||||
{ "action": "toggleBroadcastInput", "scope": "tab" },
|
||||
{ "action": "toggleBroadcastInput", "scope": "pane" },
|
||||
{ "action": "disableBroadcastInput" },
|
||||
```
|
||||
|
||||
Which would be accompanied by the following internal properties:
|
||||
* A window (`TerminalPage`-level) property for `broadcastToAllPanesAndTabs`
|
||||
* A per-tab property for `broadcastToAllPanes`
|
||||
* A per-tab set of panes to broadcast to
|
||||
|
||||
The scopes would work as follows:
|
||||
|
||||
* `"scope": "window"`: Toggle the window's "broadcast to all tabs and panes"
|
||||
setting.
|
||||
* `"scope": "tab"`: Toggle the tab's "broadcast to all panes in this tab"
|
||||
setting.
|
||||
- This does not modify the set of panes that the user is broadcasting to in
|
||||
the tab, merely toggles the tab's setting. If the user has a set of panes
|
||||
they're broadcasting to in this tab, then toggles this setting on and off,
|
||||
we'll return to broadcasting to that set.
|
||||
* `"scope": "pane"`: Add this pane to the set of panes being broadcasted to in
|
||||
this tab.
|
||||
- **TODO!: FOR DISCUSSION**: Should this disable the tab's
|
||||
"broadcastToAllPanes" setting? Or should it leave that alone?
|
||||
* `"disableBroadcastInput"`: Set the global setting to false, the tab's setting
|
||||
to false, and clear the set of panes being broadcasted to for this tab.
|
||||
- **TODO!** This could also just be `"action": "toggleBroadcastInput",
|
||||
"scope": "none"`
|
||||
|
||||
#### Pros
|
||||
* This is exactly how iTerm2 does it, so there's prior art.
|
||||
* If you're not globally broadcasting, then you're only ever broadcasting to
|
||||
some (sub)set of the panes in the current tab. So global broadcast mode is
|
||||
the only time a user would need to worry about input being to be sent to
|
||||
an inactive tab.
|
||||
* You can have a set of panes to broadcast to in the first tab, then a
|
||||
_separate_ set to broadcast to in a second tab. Broadcasting in one tab
|
||||
does not affect the other.
|
||||
|
||||
#### Cons
|
||||
* I frankly think the `tab`/`pane` interaction can be a little weird. Like for
|
||||
this scenario:
|
||||
- enable broadcast input for tab 1
|
||||
- switch to tab 2
|
||||
- enable broadcast input for a pane in tab 2
|
||||
|
||||
There's valid confusion to be had between the following two behaviors:
|
||||
1. input goes to all of tab 1 _and_ the pane in tab 2
|
||||
2. input only goes to the pane in tab 2
|
||||
* You can't broadcast to a subset of panes in inactive tabs, in addition to
|
||||
the active tab. All panes you want to broadcast to must be in the active
|
||||
tab.
|
||||
* Does creating a new split in a pane that's being broadcast to add that pane to
|
||||
the broadcast set?
|
||||
|
||||
#### What would this mean for PR #9222?
|
||||
|
||||
The prototype PR [#9222] basically just implemented `{ "action":
|
||||
"toggleBroadcastInput", "scope": "tab" }`. We could make `tab` the default
|
||||
`scope` if no other one is specified, and then the PR would need basically no
|
||||
modifications. Future PRs could add args to the `toggleBroadcastInput` action,
|
||||
without breaking users who bind a key to that action now.
|
||||
|
||||
### Proposal 2: Broadcast Set
|
||||
|
||||
This was the design I had originally came up with before investigating iTerm2
|
||||
much closer. This design involves a "broadcast set" of panes. All the panes in
|
||||
the broadcast set would also get the `KeySent` and `CharSent` events, in
|
||||
addition to the active pane. (The active pane may be a part of the broadcast
|
||||
set). If a pane is read-only in the broadcast set, then it won't handle those
|
||||
broadcasted events (obviously).
|
||||
|
||||
As far as actions, we're looking at something like:
|
||||
|
||||
* **A** Only send input to the active pane
|
||||
* Remove all the panes from the broadcast set
|
||||
* **B** send input to all panes in all tabs
|
||||
* If all the panes are in the broadcast set, remove them all. Otherwise, add
|
||||
all panes in all tabs to the broadcast set.
|
||||
* **C** send input to all panes in the current tab
|
||||
* If all the panes in the current tab are in the broadcast set, remove them
|
||||
from the broadcast set. Otherwise, add all the panes from this tab to the
|
||||
broadcast set.
|
||||
* **D** toggle sending input to the current pane
|
||||
* If this pane is in the broadcast set, remove it. Otherwise add it.
|
||||
This seems to break down into the following actions:
|
||||
|
||||
```json
|
||||
{ "action": "disableBroadcastInput" },
|
||||
{ "action": "toggleBroadcastInput", "scope": "window" },
|
||||
{ "action": "toggleBroadcastInput", "scope": "tab" },
|
||||
{ "action": "toggleBroadcastInput", "scope": "pane" },
|
||||
```
|
||||
|
||||
Which would be accompanied by the following internal properties:
|
||||
* A window (`TerminalPage`-level) set of panes to broadcast to.
|
||||
|
||||
#### Pros:
|
||||
* Mentally, you're either adding panes to the set of panes to broadcast to, or
|
||||
removing them.
|
||||
* You can broadcast to panes in multiple tabs, without broadcasting to _all_
|
||||
panes in all tabs.
|
||||
|
||||
#### Cons:
|
||||
* is _slightly_ different from iTerm2.
|
||||
* Does creating a new split in a pane that's being broadcast to add that pane to
|
||||
the broadcast set?
|
||||
* You can't have a set of panes to broadcast to in the one tab, and a different
|
||||
set in another tab. As an example:
|
||||
1. in tab 1, you add panes A and B to the broadcast set. Typing in either one
|
||||
goes to both A and B.
|
||||
2. in tab 1, switch to pane C. Now input goes to A, B and C.
|
||||
3. in tab 1, switch to pane D. Now input goes to A, B and D.
|
||||
4. switch to tab 2, pane E. Now input goes to A, B and E.
|
||||
|
||||
You can't have like, a set with A & B (in 1), then E & F (in 2). So if someone
|
||||
wants to type to both panes in 1, then both panes in 2, then both panes in 1,
|
||||
they need to keep toggling which panes are in the broadcast set.
|
||||
|
||||
#### What would this mean for PR #9222?
|
||||
|
||||
Similar to Proposal 1, we'd use `tab` as the default value for `scope`. In the
|
||||
future, when we add support for the other scopes, we'd change how the
|
||||
broadcasting works, to use a set of panes to broadcast to, instead of just the
|
||||
tab-level property.
|
||||
|
||||
### Proposal 3: It's iTerm2, but slightly different
|
||||
|
||||
While typing this up, I thought maybe it might make more sense if we took the
|
||||
iTerm2 version, and changed it slightly:
|
||||
|
||||
* `"scope": "tab"`: If all the panes are in the broadcast set for this tab, then
|
||||
remove them all. Otherwise, add all the panes in this tab to this tab's
|
||||
broadcast set.
|
||||
* `"scope": "pane"`: If this pane is in the broadcast set for a tab, then remove
|
||||
it. Otherwise, add it.
|
||||
|
||||
With this, we get rid of the tab-level setting for "broadcast to all the panes
|
||||
in this tab", and rely only on the broadcast set for that tab.
|
||||
|
||||
#### Pros:
|
||||
* All the pros from proposal A
|
||||
* Does away with the seemingly weird toggling between "all the panes in a tab"
|
||||
and "some of the panes in a tab" that's possible with proposal A
|
||||
|
||||
#### Cons:
|
||||
* You can't broadcast to a subset of panes in inactive tabs, in addition to
|
||||
the active tab. All panes you want to broadcast to must be in the active
|
||||
tab.
|
||||
* is _slightly_ different from iTerm2. Just _slightly_.
|
||||
* Does creating a new split in a pane that's being broadcast to add that pane to
|
||||
the broadcast set?
|
||||
|
||||
#### What would this mean for PR #9222?
|
||||
|
||||
Same as with proposal A, we wouldn't change anything in the current PR. A future
|
||||
PR that would add the other scope's to that action would need to change how the
|
||||
broadcasting within a tab works, to use a set of panes to broadcast to, instead
|
||||
of just the tab-level property.
|
||||
|
||||
## Conclusion
|
||||
|
||||
I'm proposing these settings for broader discussion. I'm not really sure which I
|
||||
like most at this point. 1 & 3 have the advantage of being most similar to the
|
||||
prior art, but 2 is more easily extendable to "groups" (see [Future
|
||||
Considerations](#Future-Considerations)).
|
||||
|
||||
**TODO!**: Make a decision.
|
||||
|
||||
_**Fortunately**_: All these proposals actually use the same set of actions. So
|
||||
it doesn't _really_ matter which we pick right now. We can unblock [#9222] as
|
||||
the implementation of the `"tab"` scope, and address other scopes in the future.
|
||||
We should still decide long-term which of these we'd like, but the actions seem
|
||||
universal.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
This is supposed to be a quick & dirty spec, so I'm LARGELY skipping this.
|
||||
|
||||
As far as indicators go, we'll throw something like:
|
||||
|
||||

|
||||
|
||||
in the tab when a pane is being broadcasted to. If all tabs are being
|
||||
broadcasted to, then they'll all have that icon. If a tab is inactive, and any
|
||||
pane in that tab is being broadcast to, then show the icon in the tab.
|
||||
|
||||
It probably makes the most sense to have pane titlebars ([#4998]) also display
|
||||
that icon.
|
||||
|
||||
In the original PR, it was suggested to use some variant of the [accent color]
|
||||
to on the borders of panes that are currently receiving broadcasted input. We're
|
||||
already using the accent color on the borders of the active pane.
|
||||
`SystemAccentColorLight*`/`SystemAccentColorDark*` would provide a way of using
|
||||
a similar hue with different lightness/saturation. This would be a decent visual
|
||||
indicator that they're _not_ the active pane, but they are going to receive
|
||||
input. Something a bit like:
|
||||
|
||||

|
||||
|
||||
This should obviously be able to be overridden in the user's theme, similar to
|
||||
the pane border colors.
|
||||
|
||||
iTerm2 also supports displaying "stripes" in the background of all the panes
|
||||
that are being broadcast too. That's certainly another way of indicating this
|
||||
feature to the user. I'm not sure how we'd layer it with the background image
|
||||
though. **I recommend we ignore this for now, and leave this as a follow-up**.
|
||||
|
||||
### Tab context menu items
|
||||
|
||||
For reference, refer to the following from iTerm2:
|
||||

|
||||
|
||||
We don't have a menu bar like on MacOS, but we do have a tab context menu. We
|
||||
could add these items as a nested entry under each tab. If we wanted to do this,
|
||||
we should also make sure to dynamically change the icon of the MenuItem to
|
||||
reflect the current broadcast state.
|
||||
|
||||
## Potential Issues
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
|
||||
[comment]: # Will the proposed change break existing code/behaviors? If so, how, and is the breaking change "worth it"?
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[comment]: # If there are any other potential issues, make sure to include them here.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
* [ ] Resurrect [#9222], and use that to implement `"scope": "tab"`. This is
|
||||
implemented the same, regardless of which proposal we chose.
|
||||
* [ ] Add a tab context menu entry for toggling broadcast input, with a dynamic
|
||||
icon based on the current state.
|
||||
* [ ] Implement `"scope": "window"`. Again, this is implemented the same regardless
|
||||
of which proposal we pursue.
|
||||
* [ ] Decide between the two proposals here.
|
||||
* [ ] Implement `"scope": "pane"`.
|
||||
|
||||
Doing the first element here is probably the most important one for most users,
|
||||
and can be done regardless of the proposal chosen here. As such, we could even
|
||||
suggest the default value of `scope` be `tab`. If we did that, then we wouldn't
|
||||
need to do any args at all in the initial version.
|
||||
|
||||
## Future Considerations
|
||||
|
||||
Let's look to iTerm2, who's supported this feature for years, for some
|
||||
inspiration of future things we should be worried about. If their users have
|
||||
asked for these features, then it's inevitable that our users will too 😉
|
||||
|
||||
* [iterm2#6709] - Broadcast Input to multiple windows
|
||||
- This is pretty straightforward. Would require coordination with the Monarch
|
||||
though, and I'm worried about the perf hit of tossing every keystroke across
|
||||
the process boundary.
|
||||
- I suppose this would be `{ "action": "toggleBroadcastInput", "scope":
|
||||
"global" }`
|
||||
* [iterm2#6451], [iterm2#5563] - "Broadcast commands"
|
||||
- iTerm2 has an action that lets the user manually clear the terminal-side
|
||||
buffer. (This is tracked on the Windows Terminal as [#1882]). It might make
|
||||
sense for there to be a mode where some _actions_ are also broadcast to
|
||||
panes, not just key strokes. But which actions would those be? Moving the
|
||||
selection anchors? Copy doesn't really make sense. Paste _does_ though.
|
||||
Maybe the open find dialog / next&prev search match actions?
|
||||
- This probably would require it's own spec.
|
||||
* [iterm2#6007] - Different stripe color for different broadcast modes
|
||||
- Have one color to indicate when broadcasting in `global` scope, another in
|
||||
`tab` scope, a third in `pane` scope.
|
||||
- This might mesh well with theming ([#3327]), for properties like
|
||||
`pane.broadcastBorderColor.globalScope`,
|
||||
`pane.broadcastBorderColor.paneScope`. Don't love those names, but you get
|
||||
the idea.
|
||||
* **[iterm2#5639]: Broadcast groups**, [iterm2#3372] - Broadcast Input to
|
||||
multiple but not all tabs
|
||||
- This is probably the most interesting request. I think this one identifies a
|
||||
major shortcoming of the above proposals. With proposal 2, there's only ever
|
||||
one top-level broadcast group. With proposals 1 & 3, there's per-tab
|
||||
broadcast groups. In neither proposal can you have multiple concurrent
|
||||
side-by-side broadcast groups.
|
||||
- Groups should probably work across tabs. This would suggest that Proposal 2
|
||||
is closer to how groups would work. Instead of there being one top-level
|
||||
set, there would be multiple. **I'm not sure how proposals 1&3 would
|
||||
seamlessly transition into also supporting groups**.
|
||||
- The major trick here is: how do we differentiate these different groups to
|
||||
the user? If we used the broadcast icon with a number, maybe in the corner
|
||||
of the tab? Like [📡: 1]? Can a pane be in multiple broadcast sets at the
|
||||
same time?
|
||||
- The natural arg idea would be `{ "action": "toggleBroadcastInput", "scope":
|
||||
"tab", "group": 1 }` to say "add all panes in the tab to broadcast group 1",
|
||||
or "remove all panes in the tab from broadcast group 1". If panes are in
|
||||
another group, they'd be moved to the specified group. If all panes are in
|
||||
that group, then remove them all.
|
||||
- The UI for this would certainly get complex fast.
|
||||
- This also matches the Terminator-style broadcasting to groups.
|
||||
* Re: stripes in the background of the tab. We could expose a pane's current
|
||||
broadcast state to the pixel shader, and a user could use a custom pixel
|
||||
shader to add stripes behind the text in the shader code. That's one possible
|
||||
solution.
|
||||
|
||||
## Resources
|
||||
|
||||
[comment]: # Be sure to add links to references, resources, footnotes, etc.
|
||||
|
||||
|
||||
### Footnotes
|
||||
|
||||
<a name="footnote-1"><a>[1]:
|
||||
|
||||
|
||||
[#1882]: https://github.com/microsoft/terminal/issues/1882
|
||||
[#2634]: https://github.com/microsoft/terminal/issues/2634
|
||||
[#4998]: https://github.com/microsoft/terminal/issues/4998
|
||||
[#3327]: https://github.com/microsoft/terminal/issues/3327
|
||||
[#9222]: https://github.com/microsoft/terminal/pull/9222
|
||||
[this comment]: https://github.com/microsoft/terminal/issues/2634#issuecomment-789116413
|
||||
[iTerm2 implementation]: https://iterm2.com/documentation-one-page.html#documentation-menu-items.html
|
||||
[@zljubisic]: https://github.com/microsoft/terminal/pull/9222#issuecomment-789143189
|
||||
[accent color]: https://docs.microsoft.com/en-us/windows/uwp/design/style/color#accent-color-palette
|
||||
|
||||
|
||||
[iterm2#6709]: https://gitlab.com/gnachman/iterm2/-/issues/6709
|
||||
[iterm2#6451]: https://gitlab.com/gnachman/iterm2/-/issues/6451
|
||||
[iterm2#6007]: https://gitlab.com/gnachman/iterm2/-/issues/6007
|
||||
[iterm2#5639]: https://gitlab.com/gnachman/iterm2/-/issues/5639
|
||||
[iterm2#5563]: https://gitlab.com/gnachman/iterm2/-/issues/5563
|
||||
[iterm2#3372]: https://gitlab.com/gnachman/iterm2/-/issues/3372
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 336 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,380 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2021-08-31
|
||||
last updated: 2021-08-31
|
||||
issue id: #642
|
||||
---
|
||||
|
||||
# Buffer Exporting and Logging
|
||||
|
||||
## Abstract
|
||||
|
||||
A common user need is the ability to export the history of a terminal session to
|
||||
a file, for later inspection or validation. This is something that could be
|
||||
triggered manually. Many terminal emulators provide the ability to automatically
|
||||
log the output of a session to a file, so the history is always captured. This
|
||||
spec will address improvements to the Windows Terminal to enable these kinds of
|
||||
exporting and logging scenarios.
|
||||
|
||||
## Background
|
||||
|
||||
### Inspiration
|
||||
|
||||
Below are screenshots from the settings pages of three different terminal
|
||||
emulators with similar features - PuTTY, SecureCRT, and ConEmu:
|
||||
|
||||

|
||||
|
||||
_figure 1: PuTTY settings_
|
||||
|
||||

|
||||
|
||||
_figure 2: SecureCRT settings_
|
||||
|
||||

|
||||
|
||||
_figure 3: ConEmu settings_
|
||||
|
||||
These applications all offer some settings in common. Primarily, the important
|
||||
feature is the ability to specify a path to a log file which contains some
|
||||
special string formatting. This allows the user to log to different files based
|
||||
on the time & date of the session, or based on the session name.
|
||||
|
||||
### User Stories
|
||||
|
||||
* **Story A**: The user is able to use a context menu entry on the tab to export
|
||||
the contents of the buffer to a file, which they are prompted for.
|
||||
- This is explicitly what was requested in [#642]
|
||||
* **Story B**: The user can bind an action to export the contents of the buffer
|
||||
to a file, which they are prompted for.
|
||||
- Very similar to **A**, but via the command palette or a keybinding.
|
||||
* **Story C**: The user can export to an explicit file via an action
|
||||
- similar to **B**, but allowing for declaring the path to a file rather than
|
||||
prompting at runtime.
|
||||
* **Story D**: The user can choose to append to a file when exporting, rather
|
||||
than overwriting.
|
||||
* **Story E**: The user can specify a format string in the path to the file to
|
||||
export to, which the Terminal will automatically replace with variables like
|
||||
the time, date, and profile name.
|
||||
* **Story F**: When opening a specific profile, the user can automatically log
|
||||
to a file
|
||||
* **Story G**: The user can execute an action to start or stop logging to a
|
||||
given file.
|
||||
|
||||
|
||||
## Solution Design
|
||||
|
||||
I'm proposing the following actions and profile settings
|
||||
|
||||
* New Action: `exportBuffer()`.
|
||||
- Export the contents of the buffer to a file.
|
||||
- `path` (string, defaults to `""`): When empty, prompt the user for a name of
|
||||
a file to export to, using a file picker. This path accepts special
|
||||
formatting strings that will be substituted with certain variables
|
||||
(discussed [below](#path-formatting)).
|
||||
- `append` (boolean, defaults to `false`): When `false`, the file's contents
|
||||
will be overwritten. When `true`, the buffer contents will be appended to
|
||||
the end of the file.
|
||||
* New Profile Settings object: `logSettings`
|
||||
- This is an object that describes a set of behavior for logging a profile.
|
||||
- `path`: Same as the `path` in the `ExportBufferArgs` above
|
||||
- `append`: Same as the `append` in the `ExportBufferArgs` above
|
||||
- `captureAllOutput`: (boolean, defaults to `false`) When true, don't log only
|
||||
printable characters, also log non-printable escape characters written to
|
||||
the Terminal.
|
||||
- `captureInput`: (boolean, defaults to `false`) Additionally log input to the
|
||||
Terminal to the file. Input will be formatted as the traditional VT
|
||||
sequences, rather than the full `win32-input` encoding.
|
||||
- `newFileEveryDay`: (boolean, defaults to `false`) This requires the `day` to
|
||||
be an element of the path format string. When logging with this setting,
|
||||
opens a new file at midnight and starts writing that one.
|
||||
<!-- TODO! - `flushFrequently`: (boolean, defaults to `true`) -->
|
||||
* New Profile setting: `logAutomatically` (boolean, default `false`). When true,
|
||||
terminals with this profile will begin logging automatically.
|
||||
* New Action: `toggleLogging()`.
|
||||
- Start or stop logging to the configured file. If the terminal is already
|
||||
logging with different settings than in this action, then stop logging
|
||||
regardless (don't just start logging to the new file)
|
||||
- This action accepts all the same args the profile's `logSettings` object.
|
||||
- If _any_ args are provided, use those args. If _none_ are provided, then use
|
||||
the logging settings present in the profile (if there are any).
|
||||
- If there's not path provided (either in the args to the action or in the
|
||||
profile), prompt the user to pick a file to log to.
|
||||
|
||||
### Examples
|
||||
```json
|
||||
{
|
||||
"actions": [
|
||||
{ "keys": "f1", "command": "exportBuffer" },
|
||||
{ "keys": "f2", "command": { "action": "exportBuffer", "path": "c:\\logs\\${year}-${month}-${date}\\{profile}.txt" } },
|
||||
|
||||
{ "keys": "f3", "command": "toggleLogging" },
|
||||
{ "keys": "f4", "command": { "action": "toggleLogging", "path": "c:\\logs\\${profile}.log", "append": true } },
|
||||
],
|
||||
"profiles": [
|
||||
{
|
||||
"name": "foo",
|
||||
"logging": {
|
||||
"path": "c:\\foo.txt",
|
||||
"append": true
|
||||
},
|
||||
"automaticallyLog": false
|
||||
},
|
||||
{
|
||||
"name": "bar",
|
||||
"logging": {
|
||||
"path": "c:\\logs\\${date}\\bar.txt",
|
||||
"append": false
|
||||
},
|
||||
"automaticallyLog": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Revisiting our original stories:
|
||||
|
||||
* **Story A**: This is already implemented in [#11062]
|
||||
* **Story B**: This is the action bound to <kbd>f1</kbd>.
|
||||
* **Story C**: This is the action bound to <kbd>f2</kbd>.
|
||||
* **Story D**: This is the `append` property in the actions, profile settings.
|
||||
* **Story E**: An example of this is in the action bound to <kbd>f2</kbd>,
|
||||
<kbd>f4</kbd>, and in the profile "bar"'s logging settings.
|
||||
* **Story F**: The profile "bar" is configured to automatically log when opened.
|
||||
* **Story G**: This is the action bound to <kbd>f4</kbd>.
|
||||
|
||||
In addition,
|
||||
* When opening the profile "foo", it will not automatically log to a file.
|
||||
- Pressing <kbd>f3</kbd> will begin logging to `c:\foo.txt`
|
||||
- Pressing <kbd>f4</kbd> will begin logging to `c:\logs\foo.log`
|
||||
|
||||
### Path formatting
|
||||
|
||||
[TODO!]: # TODO!
|
||||
|
||||
For discussion: What syntax do we want?
|
||||
* PuTTY uses `&Y`, `&M`, `&D`, `&T`, `&H`, `&P` for year, month, day, time, host
|
||||
and port respectively.
|
||||
* SecureCRT uses:
|
||||
- `%H` – hostname
|
||||
- `%S` – session name
|
||||
- `%Y` – four-digit year
|
||||
- `%M` – two-digit month
|
||||
- `%D` – two-digit day of the month
|
||||
- `%h` – two-digit hour
|
||||
- `%m` – two-digit minute
|
||||
- `%s` – two-digit seconds
|
||||
- `%t` – three-digit milliseconds
|
||||
- `%%` – percent (%)
|
||||
- `%envvar%` – environment variable (for instance `%USERNAME%`)
|
||||
|
||||
We have some precedent for formatting with `${braces}`, a la the iterable
|
||||
command in the Command Palette (e.g `${profile.name}`). Additionally, [#9287]
|
||||
implements support for environment variables in the Terminal with the
|
||||
`${env:VARIABLE}` syntax.
|
||||
|
||||
What variables do we want exposed, and how do we want users to be able to format
|
||||
them?
|
||||
|
||||
This doc was initially authored assuming we'd go with a `${braces}` syntax, like:
|
||||
- `${profile}` – profile name
|
||||
- `${year}` – four-digit year
|
||||
- `${month}` – two-digit month
|
||||
- `${day}` – two-digit day of the month
|
||||
- `${hour}` – two-digit hour
|
||||
- `${minute}` – two-digit minute
|
||||
- `${second}` – two-digit second
|
||||
- `${ms}` – three-digit milliseconds
|
||||
- `${env:variable}` – environment variable (for instance `${env:USERPROFILE}`)
|
||||
(inspired by [#9287])
|
||||
|
||||
### Exporting vs Logging
|
||||
As far as specific implementation details goes, exporting is the easier work to
|
||||
do. [#11062] already wires up the `TerminalApp` to retrieve the buffer contents
|
||||
from the `TermControl`, so writing them at request is easy.
|
||||
|
||||
Logging is harder. We don't want the `TermControl` telling the `TerminalApp`
|
||||
layer about every piece of output logged. Especially in the post-[#5000] world
|
||||
where that's a cross-process hop. Instead, we'll want the `ControlCore` /
|
||||
`ControlInteractivity` to do _logging_ themselves.
|
||||
|
||||
### Logging Mechanics
|
||||
|
||||
#### When do we log?
|
||||
|
||||
[TODO!]: # TODO!
|
||||
|
||||
When do we decide to actually log? Take for example typing in a `pwsh` or
|
||||
`bash` prompt. Imagine the user types
|
||||
<kbd>w</kbd><kbd>h</kbd><kbd>a</kbd><kbd>t</kbd>, then hits
|
||||
<kbd>Bksp</kbd><kbd>Bksp</kbd>, such that the prompt is just `wh`. What should
|
||||
the log contain? `what^h ^h^h ^h`<sup>[[1]](#footnote-1)</sup>? `wh`?
|
||||
|
||||
My worry with logging the backspaces is that conpty is sometimes a bit noisier
|
||||
than it needs to be with using `^H` as a cursor positioning sequence. Should we
|
||||
only log lines when the cursor newlines or otherwise moves from the line it is
|
||||
currently on?
|
||||
|
||||
I'll need to look at what PuTTY emits for the "Printable output" option.
|
||||
|
||||
#### What happens when we _start_ logging?
|
||||
|
||||
If the user has a terminal that did not start with logging enabled, but then
|
||||
started logging with `toggleLogging`, what should we log? All future output? Or
|
||||
should we log the current buffer contents as well?
|
||||
|
||||
I'm inclined to lean towards simply "all future output", and ignore any current
|
||||
buffer content. If the user rally wants to log the current buffer contents _and_
|
||||
start logging, they can use a `multipleActions` action ([#11045]) to
|
||||
`exportBuffer` to a file, then `toggleLogging` to that same file with
|
||||
`"append":true`.
|
||||
|
||||
## Potential Issues
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
|
||||
Since this functionality is entirely new, nothing here should negatively affect
|
||||
existing functionality.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Performance, Power, and Efficiency</strong></td>
|
||||
<td>
|
||||
|
||||
When logging, it's expected there will be a measurable performance hit. We can
|
||||
try to mitigate this by only writing to the file on a background thread,
|
||||
separate from the connection or rendering thread. Since auto-logging will only
|
||||
take place in the content process, we're not worried about the file writing
|
||||
occurring on the UI thread.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Also frequently requested is the ability to log timestamps of when commands are
|
||||
executed. I don't think that this is a valuable feature for the Terminal to
|
||||
implement ourselves. Windows Terminal is fundamentally just a _terminal
|
||||
emulator_, it doesn't really know what's going on behind the scenes with
|
||||
whatever client application (`cmd`, `powershell`, `bash`, `vim`) that is
|
||||
connected to it. WT doesn't know when the user is typing in commands to the
|
||||
shell, or if the user is just typing in text in `emacs` or something. There's no
|
||||
way for the terminal to know that. It's _typically_ the client application's
|
||||
responsibility to save it's own command history. `bash` and `powershell` both do
|
||||
a pretty good job of saving this to another file to restore across sessions,
|
||||
while `cmd.exe` doesn't.
|
||||
|
||||
Windows is a messy world and this model gets a little tricky here. `cmd.exe`
|
||||
isn't actually managing it's own command history _at all_. `conhost` is doing
|
||||
that work on behalf of the client applications. Some long time ago someone
|
||||
thought it would be a good idea to have the `readline` functionality baked
|
||||
directly into the console host. Whether that was a good idea or not remains to
|
||||
be seen - it's certainly made things like `python.exe`'s REPL easier to
|
||||
implement, since they don't need to maintain their own history buffer, but it
|
||||
makes it hard to de-tangle behavior like this from the console itself.
|
||||
|
||||
I'm not sure how it would be possible to add a keybinding to the Windows
|
||||
Terminal that would be able to save the console's _command_ history. Especially
|
||||
considering the Terminal might _not_ be connected to a console host session at
|
||||
all. If the Windows Terminal were directly running a `wsl` instance (something
|
||||
that's not possible today, but something we've considered adding in the future),
|
||||
then there wouldn't be a `conhost` in the process tree at all, and now
|
||||
requesting the command history from the console wouldn't work _mysteriously_.
|
||||
|
||||
Furthermore, shells can always be configured to emit timestamps in their prompts
|
||||
themselves. Since the Terminal has no knowledge of when a command is actually
|
||||
entered, but the _shell_ does, it makes the most sense to configure the user's
|
||||
_shell_ to emit that information. The Terminal will then dutifully log that
|
||||
output along with everything else.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
Below is a rough outline of how I'd go about implementing these features. Each
|
||||
lop-level checkbox could be its own PR, following from [#11062].
|
||||
|
||||
### Buffer exporting
|
||||
|
||||
* [ ] Add an `exportBuffer()` action that opens the file picker
|
||||
* [ ] Add a string `path` parameter to `exportBuffer()` that allows the user to
|
||||
press a key and immediately export the buffer to a whole path
|
||||
- default to `""`, which indicates "open the file picker"
|
||||
* [ ] add a boolean `append` (default to `false`) parameter to `exportBuffer`.
|
||||
When true, export to the file given by appending, not overwriting the file
|
||||
* [ ] Enable string formatting in the `path` parameter.
|
||||
- What format do we want? `yyyy-mm-dd`? `%Y-%m-%D`? `&Y-&m-&D`? `${year}-${month}-${day}`?
|
||||
- What are all the variables we want?
|
||||
- Year, month, day, hour, minute - those are easy
|
||||
- `WT_SESSION`, for a uuid for each session maybe?
|
||||
- Profile name perhaps? Commandline?
|
||||
* [ ] more...
|
||||
|
||||
### Automatic logging
|
||||
|
||||
* [ ] `toggleLogging()` Action for start/stop logging, with `path`, `append`
|
||||
properties (like `exportBuffer()`)
|
||||
- `ToggleLoggingArgs` contains a single member `LoggingSettings`, which
|
||||
contains `path` and `append` properties. This will make sense below.
|
||||
* [ ] add `LoggingSettings` property for "log all output" (default would just be
|
||||
"log printable output")
|
||||
* [ ] add `LoggingSettings` property for "log input" (Though, we'd probably want
|
||||
to log it as normal VT encoded, not as `win32-input` encoded)
|
||||
* [ ] Per-profile setting for `logSettings`, which can contain an entire
|
||||
`LoggingSettings` (like the `ToggleLoggingArgs`). When `toggleLogging` with no
|
||||
args, try to use the profile's `loggingSettings` instead.
|
||||
* [ ] Per-profile setting for `automaticallyLog`, which would log by default
|
||||
when the profile is opened
|
||||
* [ ] `LoggingSettings` property for "New file every day", which only works when
|
||||
the `{day}` is in the path string. When auto-logging with this setting, opens
|
||||
a new file at midnight and starts writing that one.
|
||||
<!-- * [ ] `LoggingSettings` property for "Flush log frequently", defaults to
|
||||
`true`(?). This causes us to flush all output to the file, instead of just...
|
||||
on close? on newline? It's unclear exactly when PuTTY flushes with this off.
|
||||
Need more coffee. -->
|
||||
|
||||
### Future Considerations
|
||||
|
||||
* When logging begins, the Terminal could display a toast for "Logging to
|
||||
{filename}", and a similar one for "Stopped logging to {filename}".
|
||||
* There's no good way of displaying a UI element to indicate that a pane is
|
||||
currently logging to a file. I don't believe PuTTY displays any sort of
|
||||
indicator. SecureCRT only displays a checkbox within the context menus of the
|
||||
application itself.
|
||||

|
||||
|
||||
Maybe when logging to a file, we could replace the "Export Text" context menu
|
||||
entry with "Stop Logging"
|
||||
* We could maybe add a setting to disable logging from the alt buffer. This
|
||||
might help make this setting more valuable for users who are using full-screen
|
||||
applications like `vim`. Since those applications redraw the entire viewport
|
||||
contents frequently, the log might be unnecessarily noisy. Disabling logging
|
||||
while in the alt buffer would show that the user opened vim, and then they did
|
||||
some things after vim exited.
|
||||
* Logging all output will be VERY helpful to us in the future for trying to
|
||||
recreate bugs on our end that users can repro but we can't!
|
||||
|
||||
## Resources
|
||||
|
||||
PuTTY Logging documentation: https://tartarus.org/~simon/putty-snapshots/htmldoc/Chapter4.html#config-logfilename
|
||||
ConEmu Logging documentation: https://conemu.github.io/en/AnsiLogFiles.html
|
||||
|
||||
### Footnotes
|
||||
|
||||
<a name="footnote-1"><a>[1]: Remember that `^H` is non-destructive, so the
|
||||
sequence `what^h ^h^h ^h` is can be read as:
|
||||
* print "what"
|
||||
* move the cursor back one
|
||||
* print a space (overwriting 't')
|
||||
* move the cursor back one (now it's on the space where 't' was)
|
||||
* move the cursor back one
|
||||
* print a space (overwriting 'a')
|
||||
* move the cursor back one (now it's on the space where 'a' was)
|
||||
|
||||
[#642]: https://github.com/microsoft/terminal/issues/642
|
||||
[#5000]: https://github.com/microsoft/terminal/issues/5000
|
||||
[#9287]: https://github.com/microsoft/terminal/pull/9287
|
||||
[#11045]: https://github.com/microsoft/terminal/pull/11045
|
||||
[#11062]: https://github.com/microsoft/terminal/pull/11062
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -60,7 +60,7 @@ it could host any arbitrary content.
|
||||
|
||||
### Security
|
||||
|
||||
I don't forsee this implementation by itself raising security concerns. This
|
||||
I don't foresee this implementation by itself raising security concerns. This
|
||||
feature is mostly concerned with adding support for arbitrary controls, not
|
||||
actually implementing some arbitrary controls.
|
||||
|
||||
|
||||
90
doc/specs/portable-mode-spec.md
Normal file
90
doc/specs/portable-mode-spec.md
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
author: Dustin L. Howett @DHowett
|
||||
created on: 2023-03-22
|
||||
last updated: 2023-03-22
|
||||
issue id: none
|
||||
---
|
||||
|
||||
# Windows Terminal "Portable" Mode
|
||||
|
||||
## Abstract
|
||||
|
||||
Since we are planning on officially supporting unpackaged execution, I propose a special mode where Terminal stores its
|
||||
settings in a `settings` folder next to `WindowsTerminal.exe`.
|
||||
|
||||
## Inspiration
|
||||
|
||||
- [PortableApps](https://portableapps.com)
|
||||
- "Embeddable" Python, which relies on the deployment of a specific file to the Python root
|
||||
|
||||
## Solution Design
|
||||
|
||||
- _If running without package identity,_ `CascadiaSettings` will look for the presence of a file called `.portable` next
|
||||
to `Microsoft.Terminal.Settings.Model.dll`.
|
||||
- If that file is present, it will change the settings and state paths to be rooted in a subfolder named `settings` next
|
||||
to `Microsoft.Terminal.Settings.Model.dll`.
|
||||
|
||||
Right now, _the only thing_ that makes Terminal not work in a "portable" manner is that it saves settings to
|
||||
`%LOCALAPPDATA%`.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
_No UI/UX impact is expected._
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Distributors could ship a self-contained and preconfigured Terminal installation.
|
||||
- Users could archive fully-working preconfigured versions of Terminal.
|
||||
- Developers (such as those on the team) could easily test multiple versions of Terminal without worrying about global
|
||||
settings pollution.
|
||||
|
||||
### Accessibility
|
||||
|
||||
_No change is expected._
|
||||
|
||||
### Security
|
||||
|
||||
_No change is expected._
|
||||
|
||||
### Reliability
|
||||
|
||||
More code always bears a risk.
|
||||
|
||||
### Compatibility
|
||||
|
||||
This is a net new feature, and it does not break any existing features. A distributor (or a user) can opt in (or out) by
|
||||
adding (or removing) the `.portable` file.
|
||||
|
||||
The following features may be impacted.
|
||||
|
||||
- **Dynamic Profiles** and **Fragment Extensions**
|
||||
- _No impact expected._ Dynamic profiles will still be generated. If a portable installation is moved to a machine without the dynamic profile source, that profile will disappear.
|
||||
- `firstWindowPreference` and `state.json`
|
||||
- _No impact expected._
|
||||
- State is stored next to settings, even for portable installations.
|
||||
- If a dynamic profile was saved in `state` and has been removed, Terminal will proceed as in non-portable mode.
|
||||
- Moving an install from Windows 10 to Windows 11 and back
|
||||
- _No impact expected._
|
||||
- "Machine-specific" settings, like those about rendering and repainting
|
||||
- _No impact expected._
|
||||
- Terminal does not distinguish settings that are specific to a machine. These settings will move along with the portable install.
|
||||
- The shell extension
|
||||
- _No impact expected._
|
||||
- The shell extension will not be registered with Windows.
|
||||
- If we choose to register the shell extension, it is already prepared for running a version of WT from the same directory. Registering the portable shell extension will make it launch portable Terminal.
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
_No change is expected._
|
||||
|
||||
## Potential Issues
|
||||
|
||||
- User confusion around where settings are stored.
|
||||
|
||||
## Future considerations
|
||||
|
||||
- In the future, perhaps `.portable` could itself contain a directory path into which we would store settings.
|
||||
- We could consider adding an indicator in the Settings UI.
|
||||
- Because we are using the module path of the Settings Model DLL, a future unpackaged version of the shell extension
|
||||
that supports profile loading would read the right settings file (assuming it used the settings model.)
|
||||
- If we choose to store the shell extension cache in the registry, we would need to avoid doing so in portable mode.
|
||||
217
doc/terminal-a11y-2023.md
Normal file
217
doc/terminal-a11y-2023.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Windows Terminal Accessibility
|
||||
## Abstract
|
||||
The release of Windows Terminal has served as a way to reinvigorate the command-line ecosystem on Windows by providing a modern experience that is consistently updated. This experience has caused faster innovation in the Windows command-line ecosystem that can be seen across various areas like expanded virtual terminal sequence support and enhanced accessibility. Command-line apps can now leverage these innovations to create a better experience for the end-user.
|
||||
|
||||
Since accessibility is a very broad area, this document is intended to present recent innovations in the accessibility space for the command-line ecosystem. Furthermore, it will propose additional improvements that can be made alongside key stakeholders that could benefit from such changes.
|
||||
|
||||
## Windows Command-line Ecosystem
|
||||
### First-party terminals
|
||||
For many years, Console Host (Conhost) was the only first-party terminal on Windows. In 2019, Windows Terminal was released to the world as an open source first-party terminal. Windows Terminal was distributed through the Microsoft Store and received regular updates throughout the year, much more frequently than Conhost. In October 2022, Windows Terminal was enabled as the default terminal on Windows.
|
||||
|
||||
A significant amount of code is shared between Conhost and Windows Terminal to create the terminal area. To enable an accessible experience for this area, a shared UI Automation provider was introduced in 2019[^1], enabling accessibility tools to navigate and read contents from the terminal area. In 2020, Windows Terminal was updated to dispatch UIA events signaling when the cursor position, text output, or selection changed; this left the work of identifying what changed in the output to the attached screen reader application[^2]. In 2022, Windows Terminal was updated to dispatch UIA notifications with a payload of what text was written to the screen[^3].
|
||||
|
||||
### Internal Partners
|
||||
There are many first-party command-line applications on Windows. The following are a few examples of those that are regularly updated:
|
||||
- [**GitHub CLI**](https://cli.github.com/): a tool that can be used to query and interact with GitHub repos (open source)
|
||||
- [**Winget**](https://github.com/microsoft/winget-cli): a tool to install applications and other packages
|
||||
- [**PSReadLine**](https://github.com/PowerShell/PSReadLine): a PowerShell module that enhances the input line experience
|
||||
- [**Windows Subsystem for Linux (WSL)**](https://learn.microsoft.com/en-us/windows/wsl/): a tool to manage and run GNU/Linux environments without a traditional virtual machine
|
||||
- [**PowerShell**](https://github.com/PowerShell/PowerShell): a cross-platform command-line shell (open source)
|
||||
|
||||
Additionally, CMD is a Windows-specific command-line shell. Though it is no longer being updated, it is still widely used.
|
||||
|
||||
### External Command-line Applications
|
||||
There are many third-party command-line applications on Windows that are regularly updated.
|
||||
The following examples take over the entire viewport:
|
||||
- [**Vim**](https://www.vim.org/): a modal-based text editor with an extensive plugin system
|
||||
- **Emacs**: a shortcut-oriented text editor with an extensive plugin system
|
||||
- [**Emacspeak**](https://emacspeak.sourceforge.net/): an Emacs subsystem that leverages Emacs' context-specific information to produce speech output
|
||||
- **Midnight Commander**: a file manager with an extensive text user interface
|
||||
- **Far Manager**: a file manager for Windows with an exclusive text user interface
|
||||
- **tmux**: a terminal multiplexer
|
||||
The following examples don't take over the entire viewport:
|
||||
- [**Oh My Posh**](https://ohmyposh.dev/): a tool to customize shell prompts
|
||||
- **git**: a tool for version control
|
||||
The following examples operate as command-line shells:
|
||||
- [**Bash**](https://www.gnu.org/software/bash/) is the default shell for most Linux distributions
|
||||
- [**Fish shell**](https://fishshell.com/) provides a rich shell experience with features like autosuggestion support and VGA colors
|
||||
- [**Z shell**](https://zsh.sourceforge.io/) is an extended Bourne shell
|
||||
|
||||
## Accessibility tools and features
|
||||
### Screen Readers
|
||||
Windows is predominantly dominated by three major screen readers: Narrator, JAWS, and NVDA.
|
||||
- **Narrator**: a free first-party screen reader that ships with Windows.
|
||||
- **JAWS**: a third-party screen reader that requires an annual license to use.
|
||||
- **NVDA**: a free third-party screen reader that is open source.
|
||||
It's important to note that most users generally use NVDA or JAWS[^4], so it's very important to consider the experience under both of those screen readers.
|
||||
|
||||
### Miscellaneous User Tools
|
||||
Windows has many built-in features that enable a more accessible experiences. The following is a list of Windows accessibility settings and tools that may have an impact on apps:
|
||||
- **Text size setting**: dictates the default size of text throughout all of Windows
|
||||
- **Always show scrollbars setting**: forces the scrollbars to always be expanded and shown. Generally, this setting is automatically respected when using the Windows UI library.
|
||||
- **Transparency effects setting**: allows transparency effects within the Windows shell
|
||||
- **Animation effects setting**: allows animation effects
|
||||
- **Text cursor indicator**: displays a colorful UI over the text cursor to make it stand out more and easier to find. Note, this is powered by UI Automation selection changed events because an empty selection is the cursor's position.
|
||||
- **Magnifier**: zooms the monitor to provide a closer look at the current mouse and cursor position. Note, this is powered by UI Automation selection changed events because an empty selection is the cursor's position.
|
||||
- **Color filters**: manipulates displayed colors to address color-blindness
|
||||
- **High contrast themes**: ensures all displayed content has a high contrast ratio. Generally, this setting is automatically respected when using the Windows UI library. However, non-WinUI surfaces (like the terminal area) need to do additional work to respect this.
|
||||
- **Voice access**: enables users to interact with their PC using their voice. This is powered by UI Automation to identify what controls can be interacted with and how.
|
||||
|
||||
### Verification Tools
|
||||
[Accessibility Insights](https://accessibilityinsights.io/) is a modern tool that can be used to test and understand accessibility issues on Windows. It comes with a built-in color contrast analyzer, a UI Automation event listener, and a UI Automation tree explorer. Additionally, it can be used to explore and interact with different control patterns using the same API screen readers and other accessibility tools rely on. Accessibility Insights is also capable of running automated tests in CI and locally to detect common and simple issues.
|
||||
|
||||
## Proposed Accessibility Improvements
|
||||
|
||||
### Respect Text Size OS setting
|
||||
Windows Terminal has a profile setting for the font size (`"font"."size": <integer>`) which is then used when instantiating new terminal sessions. There should be a way to make the terminal font size respect the text size setting in Windows found in the Settings App > Accessibility > Text Size.
|
||||
|
||||
A possible solution is to scale the session's text size at runtime based on the OS setting. Furthermore, the Windows Terminal's settings UI should warn the user when they are attempting to modify the font size profile setting.
|
||||
|
||||
This is tracked by [Windows accessibility "text size" parameter ignored by terminal · Issue #13600](https://github.com/microsoft/terminal/issues/13600)
|
||||
|
||||
### Respect High Contrast Mode
|
||||
When high contrast mode is enabled, any WinUI surfaces are automatically changed to respect the newly desired contrast mode. However, the terminal area does not. In 2021, the Delta E algorithm was added to Windows Terminal as a tool to improve color contrast in the terminal area if desired[^5]. In 2022, it was exposed via an `adjustIndistinguishableColors` profile setting with a variety of possible configurations[^6].
|
||||
|
||||
There are several possible solutions to this issue, which all should be exposed to the user. Such solutions include the following:
|
||||
- Enabling `adjustIndistinguishableColors` automatically when high contrast mode is enabled
|
||||
- This leverages the Delta E algorithm to force an adequate contrast ratio. The algorithm would need to be expanded to expose the threshold.
|
||||
- Reducing the colors used to match the Windows Contrast Theme colors
|
||||
- Edge uses this heuristic and it has the added benefit that Windows Terminal is respecting the user's high contrast theme set in the OS.
|
||||
- Implementing the `DECSTGLT` escape sequence which enables switching between different color modes (one of which is monochrome)
|
||||
Additionally, we should automatically make the terminal fully opaque and ignore the acrylic or traditional transparency setting.
|
||||
|
||||
This is tracked by [[Epic] Improved High Contrast support · Issue #12999](https://github.com/microsoft/terminal/issues/12999)
|
||||
|
||||
### Enhanced UI Automation movement mechanisms
|
||||
UI Automation has an `ITextRangeProvider` interface that can be used to represent a span of text. These text ranges can then be manipulated to explore the text area. Such manipulations include moving either (or both) endpoint(s) to encompass a text unit or move by unit. UI Automation supports the following text units:
|
||||
- Character
|
||||
- Format
|
||||
- Word
|
||||
- Line
|
||||
- Paragraph
|
||||
- Page
|
||||
- Document
|
||||
Windows Terminal and Conhost implement this `ITextRangeProvider` interface. However, this implementation doesn't support all of the available text units. Though this is standard across other implementations, this provides an opportunity for growth.
|
||||
|
||||
#### UI Automation: Movement by page
|
||||
Movement by page is a relatively abstract concept in terminals. Arguably, the viewport could be leveraged to be considered a "page", which would provide users with a quick way to navigate the buffer.
|
||||
|
||||
This is tracked by [UIA: support movement by page · Issue #13756](https://github.com/microsoft/terminal/issues/13756)
|
||||
|
||||
#### UI Automation: Movement by format
|
||||
The terminal area supports various text decorations including but not limited to underlined, bold, and italic text. These text attributes were exposed via the `ITextRangeProvider` interface in [PR #10366](https://github.com/microsoft/terminal/pull/10336). UI automation has support for navigating through contiguous spans adhering to a text attribute, which could be useful to expose to UIA clients.
|
||||
|
||||
This is tracked by [UIA Formatted Text Navigation · Issue #3567](https://github.com/microsoft/terminal/issues/3567).
|
||||
|
||||
#### Movement by prompt
|
||||
[PR #12948](https://github.com/microsoft/terminal/pull/12948) added support for scrollbar marks in Windows Terminal. As a part of this, an `experimental.autoMarkPrompts` profile setting registers scrollbar marks with each prompt when enabled. This is a fantastic way to navigate through the scroll history quickly and easily.
|
||||
|
||||
Though UI automation doesn't have a text unit for prompts, we could define a "paragraph" as the space between two prompts.
|
||||
|
||||
This issue is tracked by [megathread: Scrollbar Marks · Issue #11000](https://github.com/microsoft/terminal/issues/11000).
|
||||
|
||||
### Mark Mode support for degenerate range
|
||||
[PR #13053](https://github.com/microsoft/terminal/pull/13053) added support for mark mode in Windows Terminal. Mark mode allows users to create and modify selections by exclusively using the keyboard. However, screen reader users have reported it as a strange experience because it always has a cell of text selected; this results in the screen reader reading "x selected, y unselected" as opposed to the expected "x" when moving the cursor around.
|
||||
|
||||
Unfortunately, the changes required to fix this are very extensive because selections are stored as two inclusive terminal coordinates, which makes it impossible to represent an empty selection.
|
||||
|
||||
This is tracked by [A11y: windows terminal emits selection/deselection events in mark mode when navigating with arrow keys · Issue #13447](https://github.com/microsoft/terminal/issues/13447).
|
||||
|
||||
### Search improvements
|
||||
The search dialog can be used to perform a runtime query of the text buffer. However, in its current form, it does not count the total number of matches like other developer tools (i.e. Visual Studio Code). This work item tracks two things:
|
||||
1. Support for counting the total number of matches
|
||||
2. Displaying the number of results in the search box and announcing it via a UIA notification to the screen reader
|
||||
Additional search features may fall into this including but not limited to:
|
||||
- Highlight the search results in the scroll bar
|
||||
- Successful searches should read the line where text was found (similar to VS Code experience)
|
||||
|
||||
It seems that UI Automation has no guidance for a consistent, good search experience on Windows. Noting that VS Code and Microsoft Word have two different search experiences, it may be valuable to create documentation and guidance for other developer tools on Windows to follow, thus helping to create a more consistent search experience.
|
||||
|
||||
This is tracked by [Search should display the number of results it finds · Issue #6319](https://github.com/microsoft/terminal/issues/6319) and [[JAWS] Search results read "results found" instead of something useful · Issue #14153](https://github.com/microsoft/terminal/issues/14153).
|
||||
|
||||
### Shell suggestions
|
||||
Shells provide different forms of autocompletion. However, that autocompletion isn't necessarily an accessible experience. For example, CMD's standard autocompletion experience rewrites the "word". The screen reader rereads the new "word" properly, however, the user must cycle between the options until the desired one is shown. As another example,
|
||||
|
||||
PowerShell supports menu completion, where possible completions are displayed as a table of text and relevant help text is displayed. However, screen readers struggle with this because the entire menu is redrawn every time, making it harder to understand what exactly is "selected" (as the concept of selection in this instance is a shell-side concept represented by visual manipulation).
|
||||
|
||||
A possible solution is to introduce a new VT sequence to have the shell provide a payload. This payload can contain things like the suggested completion as well as associated help text. Such data can then be leveraged by Windows Terminal to create UI elements. Doing so leverages WinUI's accessible design. By designing a new VT sequence, other terminal emulators and CLI apps can opt-in to this functionality too.
|
||||
|
||||
This is tracked by [Enhance shell autocompletion with a cool new user interface and shell completion protocol · Issue #3121](https://github.com/microsoft/terminal/issues/3121).
|
||||
|
||||
### Scripts Panel
|
||||
Common command-line experiences revolve around inputting a command into the shell and executing it. Expert command-line users can remember complex commands off the top of their heads. Windows Terminal could help users by storing complex commands. Furthermore, these commands should be able to be easily shared between users.
|
||||
|
||||
From an accessibility standpoint, this can be very useful for users with mobility issues, as it is particularly difficult to write complex commands quickly.
|
||||
|
||||
This is tracked by [Feature Request - Scripts Panel · Issue #1595](https://github.com/microsoft/terminal/issues/1595).
|
||||
|
||||
### Broadcast Input
|
||||
Windows Terminal supports running multiple sessions at once across various panes, tabs, and windows. The broadcast input feature allows users to send input to multiple sessions at once.
|
||||
|
||||
From an accessibility standpoint, this can be very useful for users with mobility issues, as it is time consuming to write commands to multiple sessions quickly, particularly if those commands are complex.
|
||||
|
||||
This is tracked by [Support broadcast input? · Issue #2634](https://github.com/microsoft/terminal/issues/2634).
|
||||
|
||||
### UI Automation Notification Related Improvements
|
||||
In 2022, Windows Terminal added UI Automation notifications that contained a payload of text output[^3]. This was done for various reasons. For one, Narrator was not reading new output by the terminal, and would require changes on their end to accomplish this. Another reason is that NVDA is unable to handle too many text changed events, which Conhost and Windows Terminal are both culprits of since they dispatch an event when text is written to the terminal output.
|
||||
|
||||
UIA notifications have provided many compatibility benefits since screen readers automatically read notifications they receive. Additionally, this has provided the possibility for major performance enhancements as screen readers may no longer be required to diff the text buffer and figure out what has changed. NVDA has prototyped listening to notifications and ignoring text changed events entirely[^7]. However, it reveals underlying challenges with this new model such as how to handle passwords. The proposals listed in this section are intended to have Windows Terminal achieve improved performance and accessibility quality.
|
||||
|
||||
#### VT Screen Reader Control
|
||||
Some command-line applications are simply too difficult to create a consistent accessible experience. Applications that draw decorative content, for example, may have that content read by a screen reader.
|
||||
|
||||
In 2019, Daniel Imms wrote a spec proposing a VT sequence that can partially control the attached screen reader[^8]. This VT sequence consists of three main formats:
|
||||
1. Stop announcing incoming data to the screen reader. The screen reader will resume announcing incoming data if any key is pressed.
|
||||
2. Resume announcing incoming data to the screen reader.
|
||||
3. Announce the associated string payload immediately.
|
||||
Additionally, all three formats include a string payload that will be announced immediately by the screen reader (as is done with the third format).
|
||||
|
||||
JAWS and Narrator both immediately read UIA notifications as that is how Windows Terminal presents newly output text. As described earlier, NVDA currently has notifications disabled, but is prototyping moving towards a world where they can drop text diffing entirely in favor of this.
|
||||
|
||||
With Windows Terminal now dispatching UIA notifications, it would be relatively trivial for Windows Terminal to extract the string payload from the relevant VT sequences and present it in a new UIA notification. Additionally, an internal flag would be enabled to suppress UIA notifications for text output until the flag is flipped via a key press or the relevant VT sequence is received.
|
||||
|
||||
This is tracked by [Unable to use applications that hook the arrow keys using Windows Console Host. · Issue #13666](https://github.com/microsoft/terminal/issues/13666)
|
||||
|
||||
#### Screen Reader Backpressure Control Proposal
|
||||
NVDA has reported issues where Windows Terminal sends too may text changed events at once, causing NVDA to hang. Several considerations have been made in this area to address this issue:
|
||||
- **Batch notifications**: unhelpful because Terminal doesn't know how much text is intended to be output
|
||||
- **Diff text before sending a notification**: same problem as above
|
||||
- **Provide an API to throttle notifications**: causes a security risk for denial of service attacks
|
||||
Surprisingly, UI Automation doesn't have a built-in way to handle backpressure. However, the appropriate fix here seems to be either on the UI Automation side to fix this for all UIA clients, or on the NVDA side to improve event handling.
|
||||
|
||||
This is tracked by [UIA: reduce number of extraneous text change events in conhost · Issue #10822](https://github.com/microsoft/terminal/issues/10822)
|
||||
|
||||
## Proposed Priorities
|
||||
The following table assigns priorities to the aforementioned work items. These priorities are solely based on accessibility impact to improve the Windows command-line ecosystem.
|
||||
|
||||
| Priority | Work Item | Reasoning |
|
||||
|----------|-----------|-----------|
|
||||
| 1 | VT Screen Reader Control | Several partners (both CLI apps and UIA clients) would benefit from this feature and have expressed interest in adopting it. |
|
||||
| 1 | Shell suggestions | Several partners (both CLI apps and UIA clients) would benefit from this feature and have expressed interest in adopting it. |
|
||||
| 1 | Search improvements | Search is very difficult to use in its current implementation. |
|
||||
| 1 | Mark Mode support for degenerate range | Experience is barely usable and very unfriendly for non-sighted users. This is the only non-mouse method to select text. |
|
||||
| 2 | Respect High Contrast Mode | Clear workarounds exist, but the golden path scenario fails where a user enables high contrast mode and content does not respect it. |
|
||||
| 2 | Text Size OS Setting | Clear workarounds exist, but the golden path scenario fails where a user enables an OS setting and content does not respect it. |
|
||||
| 2 | Broadcast Input | Users with mobility issues would greatly benefit from this. |
|
||||
| 3 | Scripts Panel | Send-input commands currently exist. This would just expose it better. |
|
||||
| 3 | UIA: Move to prompt | Additional UIA text unit support is good. |
|
||||
| 3 | UIA: Move by page | Additional UIA text unit support is good. |
|
||||
| 3 | UIA: Move by format | Additional UIA text unit support is good. |
|
||||
|
||||
Generally, the reasoning behind these priorities can be broken down as follows:
|
||||
- Priority 1 bucket:
|
||||
- These work items provide access to features within the command-line ecosystem that are practically unusable for users with accessibility needs.
|
||||
- Priority 2 bucket:
|
||||
- Active workarounds exist for these issues, but they could be cumbersome.
|
||||
- Priority 3 bucket:
|
||||
- Purely innovative ideas that provide an exceptional experience for users.
|
||||
|
||||
## References
|
||||
[^1]: [Accessibility: Set-up UIA Tree by carlos-zamora · Pull Request #1691](https://github.com/microsoft/terminal/pull/1691)
|
||||
[^2]: [Fire UIA Events for Output and Cursor Movement by carlos-zamora · Pull Request #4826](https://github.com/microsoft/terminal/pull/4826)
|
||||
[^3]: [Use UIA notifications for text output by carlos-zamora · Pull Request #12358](https://github.com/microsoft/terminal/pull/12358)
|
||||
[^4]: [WebAIM: Screen Reader User Survey #8 Results](https://webaim.org/projects/screenreadersurvey8/#:~:text=NVDA%20is%20now%20the%20most%20commonly%20used%20screen,respondents%20use%20more%20than%20one%20desktop%2Flaptop%20screen%20reader.)
|
||||
[^5]: [Implement the Delta E algorithm to improve color perception by PankajBhojwani · Pull Request #11095](https://github.com/microsoft/terminal/pull/11095)
|
||||
[^6]: [Change AdjustIndistinguishableColors to an enum setting instead of a boolean setting by PankajBhojwani · Pull Request #13512](https://github.com/microsoft/terminal/pull/13512)
|
||||
[^7]: [Prototype for Windows Terminal: Use notifications instead of monitoring for new text by leonardder · Pull Request #14047 · nvaccess/nvda (github.com)](https://github.com/nvaccess/nvda/pull/14047)
|
||||
[^8]: [Control Screen Reader from Applications (#18) · Issues · terminal-wg / specifications · GitLab](https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/18)
|
||||
37
oss/stb/LICENSE
Normal file
37
oss/stb/LICENSE
Normal file
@@ -0,0 +1,37 @@
|
||||
This software is available under 2 licenses -- choose whichever you prefer.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE A - MIT License
|
||||
Copyright (c) 2017 Sean Barrett
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
4
oss/stb/MAINTAINER_README.md
Normal file
4
oss/stb/MAINTAINER_README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
### Notes for Future Maintainers
|
||||
|
||||
Search for files prefixed with `stb_` in this project.
|
||||
At the time of writing, the only file being used is `stb_rect_pack.h`.
|
||||
15
oss/stb/cgmanifest.json
Normal file
15
oss/stb/cgmanifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/component-detection-manifest.json",
|
||||
"Registrations": [
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/nothings/stb",
|
||||
"commitHash": "5736b15f7ea0ffb08dd38af21067c314d6a3aae9"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 1
|
||||
}
|
||||
623
oss/stb/stb_rect_pack.h
Normal file
623
oss/stb/stb_rect_pack.h
Normal file
@@ -0,0 +1,623 @@
|
||||
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
|
||||
// Sean Barrett 2014
|
||||
//
|
||||
// Useful for e.g. packing rectangular textures into an atlas.
|
||||
// Does not do rotation.
|
||||
//
|
||||
// Before #including,
|
||||
//
|
||||
// #define STB_RECT_PACK_IMPLEMENTATION
|
||||
//
|
||||
// in the file that you want to have the implementation.
|
||||
//
|
||||
// Not necessarily the awesomest packing method, but better than
|
||||
// the totally naive one in stb_truetype (which is primarily what
|
||||
// this is meant to replace).
|
||||
//
|
||||
// Has only had a few tests run, may have issues.
|
||||
//
|
||||
// More docs to come.
|
||||
//
|
||||
// No memory allocations; uses qsort() and assert() from stdlib.
|
||||
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
|
||||
//
|
||||
// This library currently uses the Skyline Bottom-Left algorithm.
|
||||
//
|
||||
// Please note: better rectangle packers are welcome! Please
|
||||
// implement them to the same API, but with a different init
|
||||
// function.
|
||||
//
|
||||
// Credits
|
||||
//
|
||||
// Library
|
||||
// Sean Barrett
|
||||
// Minor features
|
||||
// Martins Mozeiko
|
||||
// github:IntellectualKitty
|
||||
//
|
||||
// Bugfixes / warning fixes
|
||||
// Jeremy Jaussaud
|
||||
// Fabian Giesen
|
||||
//
|
||||
// Version history:
|
||||
//
|
||||
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
|
||||
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
|
||||
// 0.99 (2019-02-07) warning fixes
|
||||
// 0.11 (2017-03-03) return packing success/fail result
|
||||
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
|
||||
// 0.09 (2016-08-27) fix compiler warnings
|
||||
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
|
||||
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
|
||||
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
|
||||
// 0.05: added STBRP_ASSERT to allow replacing assert
|
||||
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
|
||||
// 0.01: initial release
|
||||
//
|
||||
// LICENSE
|
||||
//
|
||||
// See end of file for license information.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// INCLUDE SECTION
|
||||
//
|
||||
|
||||
#ifndef STB_INCLUDE_STB_RECT_PACK_H
|
||||
#define STB_INCLUDE_STB_RECT_PACK_H
|
||||
|
||||
#define STB_RECT_PACK_VERSION 1
|
||||
|
||||
#ifdef STBRP_STATIC
|
||||
#define STBRP_DEF static
|
||||
#else
|
||||
#define STBRP_DEF extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct stbrp_context stbrp_context;
|
||||
typedef struct stbrp_node stbrp_node;
|
||||
typedef struct stbrp_rect stbrp_rect;
|
||||
|
||||
typedef int stbrp_coord;
|
||||
|
||||
#define STBRP__MAXVAL 0x7fffffff
|
||||
// Mostly for internal use, but this is the maximum supported coordinate value.
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
|
||||
// Assign packed locations to rectangles. The rectangles are of type
|
||||
// 'stbrp_rect' defined below, stored in the array 'rects', and there
|
||||
// are 'num_rects' many of them.
|
||||
//
|
||||
// Rectangles which are successfully packed have the 'was_packed' flag
|
||||
// set to a non-zero value and 'x' and 'y' store the minimum location
|
||||
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
|
||||
// if you imagine y increasing downwards). Rectangles which do not fit
|
||||
// have the 'was_packed' flag set to 0.
|
||||
//
|
||||
// You should not try to access the 'rects' array from another thread
|
||||
// while this function is running, as the function temporarily reorders
|
||||
// the array while it executes.
|
||||
//
|
||||
// To pack into another rectangle, you need to call stbrp_init_target
|
||||
// again. To continue packing into the same rectangle, you can call
|
||||
// this function again. Calling this multiple times with multiple rect
|
||||
// arrays will probably produce worse packing results than calling it
|
||||
// a single time with the full rectangle array, but the option is
|
||||
// available.
|
||||
//
|
||||
// The function returns 1 if all of the rectangles were successfully
|
||||
// packed and 0 otherwise.
|
||||
|
||||
struct stbrp_rect
|
||||
{
|
||||
// reserved for your use:
|
||||
int id;
|
||||
|
||||
// input:
|
||||
stbrp_coord w, h;
|
||||
|
||||
// output:
|
||||
stbrp_coord x, y;
|
||||
int was_packed; // non-zero if valid packing
|
||||
|
||||
}; // 16 bytes, nominally
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
|
||||
// Initialize a rectangle packer to:
|
||||
// pack a rectangle that is 'width' by 'height' in dimensions
|
||||
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
|
||||
//
|
||||
// You must call this function every time you start packing into a new target.
|
||||
//
|
||||
// There is no "shutdown" function. The 'nodes' memory must stay valid for
|
||||
// the following stbrp_pack_rects() call (or calls), but can be freed after
|
||||
// the call (or calls) finish.
|
||||
//
|
||||
// Note: to guarantee best results, either:
|
||||
// 1. make sure 'num_nodes' >= 'width'
|
||||
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
|
||||
//
|
||||
// If you don't do either of the above things, widths will be quantized to multiples
|
||||
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
|
||||
//
|
||||
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
|
||||
// may run out of temporary storage and be unable to pack some rectangles.
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
|
||||
// Optionally call this function after init but before doing any packing to
|
||||
// change the handling of the out-of-temp-memory scenario, described above.
|
||||
// If you call init again, this will be reset to the default (false).
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
|
||||
// Optionally select which packing heuristic the library should use. Different
|
||||
// heuristics will produce better/worse results for different data sets.
|
||||
// If you call init again, this will be reset to the default.
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP_HEURISTIC_Skyline_default=0,
|
||||
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
|
||||
STBRP_HEURISTIC_Skyline_BF_sortHeight
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// the details of the following structures don't matter to you, but they must
|
||||
// be visible so you can handle the memory allocations for them
|
||||
|
||||
struct stbrp_node
|
||||
{
|
||||
stbrp_coord x,y;
|
||||
stbrp_node *next;
|
||||
};
|
||||
|
||||
struct stbrp_context
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int align;
|
||||
int init_mode;
|
||||
int heuristic;
|
||||
int num_nodes;
|
||||
stbrp_node *active_head;
|
||||
stbrp_node *free_head;
|
||||
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPLEMENTATION SECTION
|
||||
//
|
||||
|
||||
#ifdef STB_RECT_PACK_IMPLEMENTATION
|
||||
#ifndef STBRP_SORT
|
||||
#include <stdlib.h>
|
||||
#define STBRP_SORT qsort
|
||||
#endif
|
||||
|
||||
#ifndef STBRP_ASSERT
|
||||
#include <assert.h>
|
||||
#define STBRP_ASSERT assert
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define STBRP__NOTUSED(v) (void)(v)
|
||||
#define STBRP__CDECL __cdecl
|
||||
#else
|
||||
#define STBRP__NOTUSED(v) (void)sizeof(v)
|
||||
#define STBRP__CDECL
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP__INIT_skyline = 1
|
||||
};
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
|
||||
{
|
||||
switch (context->init_mode) {
|
||||
case STBRP__INIT_skyline:
|
||||
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
|
||||
context->heuristic = heuristic;
|
||||
break;
|
||||
default:
|
||||
STBRP_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
|
||||
{
|
||||
if (allow_out_of_mem)
|
||||
// if it's ok to run out of memory, then don't bother aligning them;
|
||||
// this gives better packing, but may fail due to OOM (even though
|
||||
// the rectangles easily fit). @TODO a smarter approach would be to only
|
||||
// quantize once we've hit OOM, then we could get rid of this parameter.
|
||||
context->align = 1;
|
||||
else {
|
||||
// if it's not ok to run out of memory, then quantize the widths
|
||||
// so that num_nodes is always enough nodes.
|
||||
//
|
||||
// I.e. num_nodes * align >= width
|
||||
// align >= width / num_nodes
|
||||
// align = ceil(width/num_nodes)
|
||||
|
||||
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
|
||||
}
|
||||
}
|
||||
|
||||
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i < num_nodes-1; ++i)
|
||||
nodes[i].next = &nodes[i+1];
|
||||
nodes[i].next = NULL;
|
||||
context->init_mode = STBRP__INIT_skyline;
|
||||
context->heuristic = STBRP_HEURISTIC_Skyline_default;
|
||||
context->free_head = &nodes[0];
|
||||
context->active_head = &context->extra[0];
|
||||
context->width = width;
|
||||
context->height = height;
|
||||
context->num_nodes = num_nodes;
|
||||
stbrp_setup_allow_out_of_mem(context, 0);
|
||||
|
||||
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
|
||||
context->extra[0].x = 0;
|
||||
context->extra[0].y = 0;
|
||||
context->extra[0].next = &context->extra[1];
|
||||
context->extra[1].x = (stbrp_coord) width;
|
||||
context->extra[1].y = (1<<30);
|
||||
context->extra[1].next = NULL;
|
||||
}
|
||||
|
||||
// find minimum y position if it starts at x1
|
||||
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
|
||||
{
|
||||
stbrp_node *node = first;
|
||||
int x1 = x0 + width;
|
||||
int min_y, visited_width, waste_area;
|
||||
|
||||
STBRP__NOTUSED(c);
|
||||
|
||||
STBRP_ASSERT(first->x <= x0);
|
||||
|
||||
#if 0
|
||||
// skip in case we're past the node
|
||||
while (node->next->x <= x0)
|
||||
++node;
|
||||
#else
|
||||
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
|
||||
#endif
|
||||
|
||||
STBRP_ASSERT(node->x <= x0);
|
||||
|
||||
min_y = 0;
|
||||
waste_area = 0;
|
||||
visited_width = 0;
|
||||
while (node->x < x1) {
|
||||
if (node->y > min_y) {
|
||||
// raise min_y higher.
|
||||
// we've accounted for all waste up to min_y,
|
||||
// but we'll now add more waste for everything we've visted
|
||||
waste_area += visited_width * (node->y - min_y);
|
||||
min_y = node->y;
|
||||
// the first time through, visited_width might be reduced
|
||||
if (node->x < x0)
|
||||
visited_width += node->next->x - x0;
|
||||
else
|
||||
visited_width += node->next->x - node->x;
|
||||
} else {
|
||||
// add waste area
|
||||
int under_width = node->next->x - node->x;
|
||||
if (under_width + visited_width > width)
|
||||
under_width = width - visited_width;
|
||||
waste_area += under_width * (min_y - node->y);
|
||||
visited_width += under_width;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
*pwaste = waste_area;
|
||||
return min_y;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x,y;
|
||||
stbrp_node **prev_link;
|
||||
} stbrp__findresult;
|
||||
|
||||
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
|
||||
{
|
||||
int best_waste = (1<<30), best_x, best_y = (1 << 30);
|
||||
stbrp__findresult fr;
|
||||
stbrp_node **prev, *node, *tail, **best = NULL;
|
||||
|
||||
// align to multiple of c->align
|
||||
width = (width + c->align - 1);
|
||||
width -= width % c->align;
|
||||
STBRP_ASSERT(width % c->align == 0);
|
||||
|
||||
// if it can't possibly fit, bail immediately
|
||||
if (width > c->width || height > c->height) {
|
||||
fr.prev_link = NULL;
|
||||
fr.x = fr.y = 0;
|
||||
return fr;
|
||||
}
|
||||
|
||||
node = c->active_head;
|
||||
prev = &c->active_head;
|
||||
while (node->x + width <= c->width) {
|
||||
int y,waste;
|
||||
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
|
||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
|
||||
// bottom left
|
||||
if (y < best_y) {
|
||||
best_y = y;
|
||||
best = prev;
|
||||
}
|
||||
} else {
|
||||
// best-fit
|
||||
if (y + height <= c->height) {
|
||||
// can only use it if it first vertically
|
||||
if (y < best_y || (y == best_y && waste < best_waste)) {
|
||||
best_y = y;
|
||||
best_waste = waste;
|
||||
best = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
best_x = (best == NULL) ? 0 : (*best)->x;
|
||||
|
||||
// if doing best-fit (BF), we also have to try aligning right edge to each node position
|
||||
//
|
||||
// e.g, if fitting
|
||||
//
|
||||
// ____________________
|
||||
// |____________________|
|
||||
//
|
||||
// into
|
||||
//
|
||||
// | |
|
||||
// | ____________|
|
||||
// |____________|
|
||||
//
|
||||
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
|
||||
//
|
||||
// This makes BF take about 2x the time
|
||||
|
||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
|
||||
tail = c->active_head;
|
||||
node = c->active_head;
|
||||
prev = &c->active_head;
|
||||
// find first node that's admissible
|
||||
while (tail->x < width)
|
||||
tail = tail->next;
|
||||
while (tail) {
|
||||
int xpos = tail->x - width;
|
||||
int y,waste;
|
||||
STBRP_ASSERT(xpos >= 0);
|
||||
// find the left position that matches this
|
||||
while (node->next->x <= xpos) {
|
||||
prev = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
|
||||
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
|
||||
if (y + height <= c->height) {
|
||||
if (y <= best_y) {
|
||||
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
|
||||
best_x = xpos;
|
||||
STBRP_ASSERT(y <= best_y);
|
||||
best_y = y;
|
||||
best_waste = waste;
|
||||
best = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
tail = tail->next;
|
||||
}
|
||||
}
|
||||
|
||||
fr.prev_link = best;
|
||||
fr.x = best_x;
|
||||
fr.y = best_y;
|
||||
return fr;
|
||||
}
|
||||
|
||||
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
|
||||
{
|
||||
// find best position according to heuristic
|
||||
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
|
||||
stbrp_node *node, *cur;
|
||||
|
||||
// bail if:
|
||||
// 1. it failed
|
||||
// 2. the best node doesn't fit (we don't always check this)
|
||||
// 3. we're out of memory
|
||||
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
|
||||
res.prev_link = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
// on success, create new node
|
||||
node = context->free_head;
|
||||
node->x = (stbrp_coord) res.x;
|
||||
node->y = (stbrp_coord) (res.y + height);
|
||||
|
||||
context->free_head = node->next;
|
||||
|
||||
// insert the new node into the right starting point, and
|
||||
// let 'cur' point to the remaining nodes needing to be
|
||||
// stiched back in
|
||||
|
||||
cur = *res.prev_link;
|
||||
if (cur->x < res.x) {
|
||||
// preserve the existing one, so start testing with the next one
|
||||
stbrp_node *next = cur->next;
|
||||
cur->next = node;
|
||||
cur = next;
|
||||
} else {
|
||||
*res.prev_link = node;
|
||||
}
|
||||
|
||||
// from here, traverse cur and free the nodes, until we get to one
|
||||
// that shouldn't be freed
|
||||
while (cur->next && cur->next->x <= res.x + width) {
|
||||
stbrp_node *next = cur->next;
|
||||
// move the current node to the free list
|
||||
cur->next = context->free_head;
|
||||
context->free_head = cur;
|
||||
cur = next;
|
||||
}
|
||||
|
||||
// stitch the list back in
|
||||
node->next = cur;
|
||||
|
||||
if (cur->x < res.x + width)
|
||||
cur->x = (stbrp_coord) (res.x + width);
|
||||
|
||||
#ifdef _DEBUG
|
||||
cur = context->active_head;
|
||||
while (cur->x < context->width) {
|
||||
STBRP_ASSERT(cur->x < cur->next->x);
|
||||
cur = cur->next;
|
||||
}
|
||||
STBRP_ASSERT(cur->next == NULL);
|
||||
|
||||
{
|
||||
int count=0;
|
||||
cur = context->active_head;
|
||||
while (cur) {
|
||||
cur = cur->next;
|
||||
++count;
|
||||
}
|
||||
cur = context->free_head;
|
||||
while (cur) {
|
||||
cur = cur->next;
|
||||
++count;
|
||||
}
|
||||
STBRP_ASSERT(count == context->num_nodes+2);
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
|
||||
{
|
||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||
if (p->h > q->h)
|
||||
return -1;
|
||||
if (p->h < q->h)
|
||||
return 1;
|
||||
return (p->w > q->w) ? -1 : (p->w < q->w);
|
||||
}
|
||||
|
||||
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
|
||||
{
|
||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
|
||||
}
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
|
||||
{
|
||||
int i, all_rects_packed = 1;
|
||||
|
||||
// we use the 'was_packed' field internally to allow sorting/unsorting
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
rects[i].was_packed = i;
|
||||
}
|
||||
|
||||
// sort according to heuristic
|
||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
|
||||
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
if (rects[i].w == 0 || rects[i].h == 0) {
|
||||
rects[i].x = rects[i].y = 0; // empty rect needs no space
|
||||
} else {
|
||||
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
|
||||
if (fr.prev_link) {
|
||||
rects[i].x = (stbrp_coord) fr.x;
|
||||
rects[i].y = (stbrp_coord) fr.y;
|
||||
} else {
|
||||
rects[i].x = rects[i].y = STBRP__MAXVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unsort
|
||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
|
||||
|
||||
// set was_packed flags and all_rects_packed status
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
|
||||
if (!rects[i].was_packed)
|
||||
all_rects_packed = 0;
|
||||
}
|
||||
|
||||
// return the all_rects_packed status
|
||||
return all_rects_packed;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
This software is available under 2 licenses -- choose whichever you prefer.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE A - MIT License
|
||||
Copyright (c) 2017 Sean Barrett
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -6,14 +6,14 @@ SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
// Resolution of the shaderTexture
|
||||
float2 Resolution;
|
||||
// Background color as rgba
|
||||
float4 Background;
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
// Resolution of the shaderTexture
|
||||
float2 Resolution;
|
||||
// Background color as rgba
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
@@ -29,38 +29,19 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
// effect, read the colors offset on the left, right, top, bottom of this
|
||||
// fragment, as well as on the corners of this fragment.
|
||||
//
|
||||
// You could get away with fewer samples, but the resulting outlines will be
|
||||
// blurrier.
|
||||
|
||||
//left, right, top, bottom:
|
||||
float4 leftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, 0.0)/Resolution.y);
|
||||
float4 rightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, 0.0)/Resolution.y);
|
||||
float4 topColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 0.0, 1.0)/Resolution.y);
|
||||
float4 bottomColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 0.0, -1.0)/Resolution.y);
|
||||
|
||||
// Corners
|
||||
float4 topLeftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, 1.0)/Resolution.y);
|
||||
float4 topRightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, 1.0)/Resolution.y);
|
||||
float4 bottomLeftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, -1.0)/Resolution.y);
|
||||
float4 bottomRightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, -1.0)/Resolution.y);
|
||||
|
||||
|
||||
// Now, if any of those adjacent cells has text in it, then the *color vec4
|
||||
// will have a non-zero .w (which is used for alpha). Use that alpha value
|
||||
// to add some black to the current fragment.
|
||||
//
|
||||
// This will result in only coloring fragments adjacent to text, but leaving
|
||||
// background images (for example) untouched.
|
||||
float3 outlineColor = float3(0, 0, 0);
|
||||
float4 result = color;
|
||||
result = result + float4(outlineColor, leftColor.w);
|
||||
result = result + float4(outlineColor, rightColor.w);
|
||||
result = result + float4(outlineColor, topColor.w);
|
||||
result = result + float4(outlineColor, bottomColor.w);
|
||||
|
||||
result = result + float4(outlineColor, topLeftColor.w);
|
||||
result = result + float4(outlineColor, topRightColor.w);
|
||||
result = result + float4(outlineColor, bottomLeftColor.w);
|
||||
result = result + float4(outlineColor, bottomRightColor.w);
|
||||
return result;
|
||||
for (int dy = -2; dy <= 2; dy += 2) {
|
||||
for (int dx = -2; dx <= 2; dx += 2) {
|
||||
float4 neighbor = shaderTexture.Sample(samplerState, tex, int2(dx, dy));
|
||||
color.a += neighbor.a;
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
@@ -12,19 +12,12 @@ using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace winrt::Windows::UI::Xaml::Navigation;
|
||||
|
||||
namespace xaml = ::winrt::Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::SampleApp::implementation
|
||||
{
|
||||
App::App()
|
||||
{
|
||||
// This is the same trick that Initialize() is about to use to figure out whether we're coming
|
||||
// from a UWP context or from a Win32 context
|
||||
// See https://github.com/windows-toolkit/Microsoft.Toolkit.Win32/blob/52611c57d89554f357f281d0c79036426a7d9257/Microsoft.Toolkit.Win32.UI.XamlApplication/XamlApplication.cpp#L42
|
||||
const auto dispatcherQueue = ::winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
|
||||
if (dispatcherQueue)
|
||||
{
|
||||
_isUwp = true;
|
||||
}
|
||||
|
||||
Initialize();
|
||||
|
||||
// Disable XAML's automatic backplating of text when in High Contrast
|
||||
@@ -33,6 +26,44 @@ namespace winrt::SampleApp::implementation
|
||||
HighContrastAdjustment(::winrt::Windows::UI::Xaml::ApplicationHighContrastAdjustment::None);
|
||||
}
|
||||
|
||||
void App::Initialize()
|
||||
{
|
||||
const auto dispatcherQueue = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
|
||||
if (!dispatcherQueue)
|
||||
{
|
||||
_windowsXamlManager = xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
|
||||
}
|
||||
else
|
||||
{
|
||||
_isUwp = true;
|
||||
}
|
||||
}
|
||||
|
||||
void App::Close()
|
||||
{
|
||||
if (_bIsClosed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_bIsClosed = true;
|
||||
|
||||
if (_windowsXamlManager)
|
||||
{
|
||||
_windowsXamlManager.Close();
|
||||
}
|
||||
_windowsXamlManager = nullptr;
|
||||
|
||||
Exit();
|
||||
{
|
||||
MSG msg = {};
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
|
||||
{
|
||||
::DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SampleAppLogic App::Logic()
|
||||
{
|
||||
static SampleAppLogic logic;
|
||||
|
||||
@@ -12,12 +12,22 @@ namespace winrt::SampleApp::implementation
|
||||
{
|
||||
public:
|
||||
App();
|
||||
void Initialize();
|
||||
void Close();
|
||||
void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs const&);
|
||||
|
||||
bool IsDisposed() const
|
||||
{
|
||||
return _bIsClosed;
|
||||
}
|
||||
|
||||
SampleApp::SampleAppLogic Logic();
|
||||
|
||||
private:
|
||||
bool _isUwp = false;
|
||||
winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager _windowsXamlManager = nullptr;
|
||||
winrt::Windows::Foundation::Collections::IVector<winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider> _providers = winrt::single_threaded_vector<Windows::UI::Xaml::Markup::IXamlMetadataProvider>();
|
||||
bool _bIsClosed = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ namespace SampleApp
|
||||
{
|
||||
// ADD ARBITRARY APP LOGIC TO SampleAppLogic.idl, NOT HERE.
|
||||
// This is for XAML platform setup only.
|
||||
[default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
|
||||
[default_interface] runtimeclass App : Windows.UI.Xaml.Application, Windows.Foundation.IClosable
|
||||
{
|
||||
App();
|
||||
|
||||
SampleAppLogic Logic { get; };
|
||||
|
||||
Boolean IsDisposed { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<Toolkit:XamlApplication x:Class="SampleApp.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:TA="using:SampleApp"
|
||||
xmlns:Toolkit="using:Microsoft.Toolkit.Win32.UI.XamlHost"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:SampleApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Application x:Class="SampleApp.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:TA="using:SampleApp"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:SampleApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<!--
|
||||
If you want to prove this works, then add `RequestedTheme="Light"` to
|
||||
the properties on the XamlApplication
|
||||
-->
|
||||
<Toolkit:XamlApplication.Resources>
|
||||
<Application.Resources>
|
||||
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -67,5 +66,5 @@
|
||||
</ResourceDictionary>
|
||||
|
||||
|
||||
</Toolkit:XamlApplication.Resources>
|
||||
</Toolkit:XamlApplication>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
<PropertyGroup Label="NuGet Dependencies">
|
||||
<TerminalCppWinrt>true</TerminalCppWinrt>
|
||||
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
|
||||
@@ -128,6 +127,16 @@
|
||||
<Private>false</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</Reference>
|
||||
|
||||
<Reference Include="$(WindowsSDK_MetadataPathVersioned)\Windows.UI.Xaml.Hosting.HostingContract\*\*.winmd">
|
||||
<WinMDFile>true</WinMDFile>
|
||||
<CopyLocal>false</CopyLocal>
|
||||
<ReferenceGrouping>$(TargetPlatformMoniker)</ReferenceGrouping>
|
||||
<ReferenceGroupingDisplayName>$(TargetPlatformDisplayName)</ReferenceGroupingDisplayName>
|
||||
<ResolvedFrom>CppWinRTImplicitlyExpandTargetPlatform</ResolvedFrom>
|
||||
<IsSystemReference>True</IsSystemReference>
|
||||
</Reference>
|
||||
|
||||
</ItemGroup>
|
||||
<!-- ====================== Compiler & Linker Flags ===================== -->
|
||||
<ItemDefinitionGroup>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="NuGet Dependencies">
|
||||
<TerminalCppWinrt>true</TerminalCppWinrt>
|
||||
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
|
||||
@@ -45,7 +44,7 @@
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
<!-- Reference SampleAppLib here, so we can use it's App.winmd as
|
||||
<!-- Reference SampleAppLib here, so we can use its App.winmd as
|
||||
our App.winmd. This didn't work correctly in VS2017, you'd need to
|
||||
manually reference the lib -->
|
||||
<ProjectReference Include="$(OpenConsoleDir)scratch\ScratchIslandApp\SampleApp\SampleAppLib.vcxproj">
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<packages>
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.7.3" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
#include "winrt/Windows.UI.Xaml.Markup.h"
|
||||
#include "winrt/Windows.UI.ViewManagement.h"
|
||||
|
||||
#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
<PropertyGroup Label="NuGet Dependencies">
|
||||
<TerminalCppWinrt>true</TerminalCppWinrt>
|
||||
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
|
||||
<TerminalVCRTForwarders>true</TerminalVCRTForwarders>
|
||||
<TerminalThemeHelpers>true</TerminalThemeHelpers>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -90,7 +88,7 @@
|
||||
Particularly tricky is Microsoft.Terminal.Core.winmd. That winmd doesn't
|
||||
have its own DLL (it doesn't have any activatable classes, only structs and
|
||||
interfaces). However, it too is necessary for Terminal.Control to be able to
|
||||
marshall the Core types across the boundary.
|
||||
marshal the Core types across the boundary.
|
||||
-->
|
||||
<Reference Include="Microsoft.Terminal.Core">
|
||||
<HintPath>$(OpenConsoleCommonOutDir)TerminalCore\Microsoft.Terminal.Core.winmd</HintPath>
|
||||
@@ -197,26 +195,6 @@
|
||||
<!-- **END VC LIBS HACK** -->
|
||||
|
||||
|
||||
<!-- **BEGIN TERMINAL CONNECTION HACK** -->
|
||||
<!-- This is the same as the above VC libs hack, but for TerminalConnection.
|
||||
TerminalConnection depends on cpprest*.dll, and if we don't include it in
|
||||
the packaging output, we'll crash as soon as we try to load
|
||||
TerminalConnection.dll.
|
||||
|
||||
The Sample sln needs to do this manually - the real exe has a
|
||||
ProjectReference to TerminalConnection.vcxproj and can figure this out on
|
||||
its own. -->
|
||||
<ItemGroup>
|
||||
<_TerminalConnectionDlls Include="$(OpenConsoleCommonOutDir)\TerminalConnection\*.dll" />
|
||||
|
||||
<PackagingOutputs Include="@(_TerminalConnectionDlls)">
|
||||
<ProjectName>$(ProjectName)</ProjectName>
|
||||
<OutputGroup>BuiltProjectOutputGroup</OutputGroup>
|
||||
<TargetPath>%(Filename)%(Extension)</TargetPath>
|
||||
</PackagingOutputs>
|
||||
</ItemGroup>
|
||||
<!-- **END TERMINAL CONNECTION HACK** -->
|
||||
|
||||
<!-- Same thing again here, with WindowsTerminal.exe -->
|
||||
<ItemGroup>
|
||||
<_WindowsTerminalExe Include="$(OpenConsoleCommonOutDir)\WindowsTerminal\*.exe" />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.7.3" targetFramework="native" />
|
||||
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -25,19 +25,19 @@ constexpr til::inclusive_rect ScreenToBufferLine(const til::inclusive_rect& line
|
||||
{
|
||||
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
|
||||
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
|
||||
return { line.Left >> scale, line.Top, line.Right >> scale, line.Bottom };
|
||||
return { line.left >> scale, line.top, line.right >> scale, line.bottom };
|
||||
}
|
||||
|
||||
constexpr til::point ScreenToBufferLine(const til::point& line, const LineRendition lineRendition)
|
||||
{
|
||||
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
|
||||
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
|
||||
return { line.X >> scale, line.Y };
|
||||
return { line.x >> scale, line.y };
|
||||
}
|
||||
|
||||
constexpr til::inclusive_rect BufferToScreenLine(const til::inclusive_rect& line, const LineRendition lineRendition)
|
||||
{
|
||||
// Use shift left to quickly multiply the Left and Right by 2 for double width lines.
|
||||
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
|
||||
return { line.Left << scale, line.Top, (line.Right << scale) + scale, line.Bottom };
|
||||
return { line.left << scale, line.top, (line.right << scale) + scale, line.bottom };
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t f
|
||||
// - 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) :
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) noexcept :
|
||||
_mode(Mode::LooseTextOnly),
|
||||
_currentView(s_GenerateView(utf16Text)),
|
||||
_run(utf16Text),
|
||||
@@ -98,7 +98,7 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) :
|
||||
// Arguments:
|
||||
// - utf16Text - UTF-16 text range
|
||||
// - attribute - Color to apply over the entire range
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit) :
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Loose),
|
||||
_currentView(s_GenerateView(utf16Text, attribute)),
|
||||
_run(utf16Text),
|
||||
@@ -113,7 +113,7 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const
|
||||
// - 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 :
|
||||
OutputCellIterator::OutputCellIterator(const std::span<const WORD> legacyAttrs) noexcept :
|
||||
_mode(Mode::LegacyAttr),
|
||||
_currentView(s_GenerateViewLegacyAttr(til::at(legacyAttrs, 0))),
|
||||
_run(legacyAttrs),
|
||||
@@ -128,7 +128,7 @@ OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs)
|
||||
// - 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 :
|
||||
OutputCellIterator::OutputCellIterator(const std::span<const CHAR_INFO> charInfos) noexcept :
|
||||
_mode(Mode::CharInfo),
|
||||
_currentView(s_GenerateView(til::at(charInfos, 0))),
|
||||
_run(charInfos),
|
||||
@@ -143,7 +143,7 @@ OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfo
|
||||
// - 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) :
|
||||
OutputCellIterator::OutputCellIterator(const std::span<const OutputCell> cells) :
|
||||
_mode(Mode::Cell),
|
||||
_currentView(s_GenerateView(til::at(cells, 0))),
|
||||
_run(cells),
|
||||
@@ -181,15 +181,15 @@ OutputCellIterator::operator bool() const noexcept
|
||||
}
|
||||
case Mode::Cell:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const OutputCell>>(_run).size();
|
||||
return _pos < std::get<std::span<const OutputCell>>(_run).size();
|
||||
}
|
||||
case Mode::CharInfo:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const CHAR_INFO>>(_run).size();
|
||||
return _pos < std::get<std::span<const CHAR_INFO>>(_run).size();
|
||||
}
|
||||
case Mode::LegacyAttr:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const WORD>>(_run).size();
|
||||
return _pos < std::get<std::span<const WORD>>(_run).size();
|
||||
}
|
||||
default:
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
@@ -198,6 +198,11 @@ OutputCellIterator::operator bool() const noexcept
|
||||
CATCH_FAIL_FAST();
|
||||
}
|
||||
|
||||
size_t OutputCellIterator::Position() const noexcept
|
||||
{
|
||||
return _pos;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Advances the iterator one position over the underlying data source.
|
||||
// Return Value:
|
||||
@@ -263,7 +268,7 @@ OutputCellIterator& OutputCellIterator::operator++()
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(til::at(std::get<gsl::span<const OutputCell>>(_run), _pos));
|
||||
_currentView = s_GenerateView(til::at(std::get<std::span<const OutputCell>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -273,7 +278,7 @@ OutputCellIterator& OutputCellIterator::operator++()
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(til::at(std::get<gsl::span<const CHAR_INFO>>(_run), _pos));
|
||||
_currentView = s_GenerateView(til::at(std::get<std::span<const CHAR_INFO>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -283,7 +288,7 @@ OutputCellIterator& OutputCellIterator::operator++()
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<gsl::span<const WORD>>(_run), _pos));
|
||||
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<std::span<const WORD>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -357,7 +362,7 @@ bool OutputCellIterator::_TryMoveTrailing() noexcept
|
||||
// - 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)
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view) noexcept
|
||||
{
|
||||
return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current);
|
||||
}
|
||||
@@ -372,8 +377,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
|
||||
// - 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)
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept
|
||||
{
|
||||
return s_GenerateView(view, attr, TextAttributeBehavior::Stored);
|
||||
}
|
||||
@@ -389,9 +393,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
|
||||
// - 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)
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept
|
||||
{
|
||||
const auto glyph = til::utf16_next(view);
|
||||
const auto dbcsAttr = IsGlyphFullWidth(glyph) ? DbcsAttribute::Leading : DbcsAttribute::Single;
|
||||
|
||||
@@ -37,17 +37,18 @@ public:
|
||||
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, const size_t fillLimit = 0);
|
||||
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(const std::wstring_view utf16Text) noexcept;
|
||||
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const std::span<const WORD> legacyAttributes) noexcept;
|
||||
OutputCellIterator(const std::span<const CHAR_INFO> charInfos) noexcept;
|
||||
OutputCellIterator(const std::span<const OutputCell> cells);
|
||||
~OutputCellIterator() = default;
|
||||
|
||||
OutputCellIterator& operator=(const OutputCellIterator& it) = default;
|
||||
|
||||
operator bool() const noexcept;
|
||||
|
||||
size_t Position() const noexcept;
|
||||
til::CoordType GetCellDistance(OutputCellIterator other) const noexcept;
|
||||
til::CoordType GetInputDistance(OutputCellIterator other) const noexcept;
|
||||
friend til::CoordType operator-(OutputCellIterator one, OutputCellIterator two) = delete;
|
||||
@@ -86,13 +87,13 @@ private:
|
||||
};
|
||||
Mode _mode;
|
||||
|
||||
gsl::span<const WORD> _legacyAttrs;
|
||||
std::span<const WORD> _legacyAttrs;
|
||||
|
||||
std::variant<
|
||||
std::wstring_view,
|
||||
gsl::span<const WORD>,
|
||||
gsl::span<const CHAR_INFO>,
|
||||
gsl::span<const OutputCell>,
|
||||
std::span<const WORD>,
|
||||
std::span<const CHAR_INFO>,
|
||||
std::span<const OutputCell>,
|
||||
std::monostate>
|
||||
_run;
|
||||
|
||||
@@ -100,15 +101,9 @@ private:
|
||||
|
||||
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 std::wstring_view view) noexcept;
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept;
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept;
|
||||
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;
|
||||
|
||||
@@ -34,9 +34,9 @@ OutputCellRect::OutputCellRect(const til::CoordType rows, const til::CoordType c
|
||||
// - row - The Y position or row index in the buffer.
|
||||
// Return Value:
|
||||
// - Read/write span of OutputCells
|
||||
gsl::span<OutputCell> OutputCellRect::GetRow(const til::CoordType row)
|
||||
std::span<OutputCell> OutputCellRect::GetRow(const til::CoordType row)
|
||||
{
|
||||
return gsl::span<OutputCell>(_FindRowOffset(row), _cols);
|
||||
return std::span<OutputCell>(_FindRowOffset(row), _cols);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -47,7 +47,7 @@ gsl::span<OutputCell> OutputCellRect::GetRow(const til::CoordType row)
|
||||
// - Read-only iterator of OutputCells
|
||||
OutputCellIterator OutputCellRect::GetRowIter(const til::CoordType row) const
|
||||
{
|
||||
const gsl::span<const OutputCell> view(_FindRowOffset(row), _cols);
|
||||
const std::span<const OutputCell> view(_FindRowOffset(row), _cols);
|
||||
|
||||
return OutputCellIterator(view);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
OutputCellRect() noexcept;
|
||||
OutputCellRect(const til::CoordType rows, const til::CoordType cols);
|
||||
|
||||
gsl::span<OutputCell> GetRow(const til::CoordType row);
|
||||
std::span<OutputCell> GetRow(const til::CoordType row);
|
||||
OutputCellIterator GetRowIter(const til::CoordType row) const;
|
||||
|
||||
til::CoordType Height() const noexcept;
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
#include "precomp.h"
|
||||
#include "Row.hpp"
|
||||
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include "textBuffer.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
|
||||
// The STL is missing a std::iota_n analogue for std::iota, so I made my own.
|
||||
template<typename OutIt, typename Diff, typename T>
|
||||
@@ -85,19 +88,6 @@ ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, c
|
||||
}
|
||||
}
|
||||
|
||||
void swap(ROW& lhs, ROW& rhs) noexcept
|
||||
{
|
||||
std::swap(lhs._charsBuffer, rhs._charsBuffer);
|
||||
std::swap(lhs._charsHeap, rhs._charsHeap);
|
||||
std::swap(lhs._chars, rhs._chars);
|
||||
std::swap(lhs._charOffsets, rhs._charOffsets);
|
||||
std::swap(lhs._attr, rhs._attr);
|
||||
std::swap(lhs._columnCount, rhs._columnCount);
|
||||
std::swap(lhs._lineRendition, rhs._lineRendition);
|
||||
std::swap(lhs._wrapForced, rhs._wrapForced);
|
||||
std::swap(lhs._doubleBytePadded, rhs._doubleBytePadded);
|
||||
}
|
||||
|
||||
void ROW::SetWrapForced(const bool wrap) noexcept
|
||||
{
|
||||
_wrapForced = wrap;
|
||||
@@ -151,84 +141,46 @@ void ROW::_init() noexcept
|
||||
std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 });
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - resizes ROW to new width
|
||||
// Arguments:
|
||||
// - charsBuffer - a new backing buffer to use for _charsBuffer
|
||||
// - charOffsetsBuffer - a new backing buffer to use for _charOffsets
|
||||
// - rowWidth - the new width, in cells
|
||||
// - fillAttribute - the attribute to use for any newly added, trailing cells
|
||||
void ROW::Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute)
|
||||
{
|
||||
// A default-constructed ROW has no cols/chars to copy.
|
||||
// It can be detected by the lack of a _charsBuffer (among others).
|
||||
//
|
||||
// Otherwise, this block figures out how much we can copy into the new `rowWidth`.
|
||||
uint16_t colsToCopy = 0;
|
||||
uint16_t charsToCopy = 0;
|
||||
if (_charsBuffer)
|
||||
{
|
||||
colsToCopy = std::min(rowWidth, _columnCount);
|
||||
// Safety: colsToCopy is [0, _columnCount].
|
||||
charsToCopy = _uncheckedCharOffset(colsToCopy);
|
||||
// Safety: colsToCopy is [0, _columnCount] due to colsToCopy != 0.
|
||||
for (; colsToCopy != 0 && _uncheckedIsTrailer(colsToCopy); --colsToCopy)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// If we grow the row width, we have to append a bunch of whitespace.
|
||||
// `trailingWhitespace` stores that amount.
|
||||
// Safety: The preceding block left colsToCopy in the range [0, rowWidth].
|
||||
const uint16_t trailingWhitespace = rowWidth - colsToCopy;
|
||||
|
||||
// Allocate memory for the new `_chars` array.
|
||||
// Use the provided charsBuffer if possible, otherwise allocate a `_charsHeap`.
|
||||
std::unique_ptr<wchar_t[]> charsHeap;
|
||||
std::span chars{ charsBuffer, rowWidth };
|
||||
const std::span charOffsets{ charOffsetsBuffer, ::base::strict_cast<size_t>(rowWidth) + 1u };
|
||||
if (const uint16_t charsCapacity = charsToCopy + trailingWhitespace; charsCapacity > rowWidth)
|
||||
{
|
||||
charsHeap = std::make_unique_for_overwrite<wchar_t[]>(charsCapacity);
|
||||
chars = { charsHeap.get(), charsCapacity };
|
||||
}
|
||||
|
||||
// Copy chars and charOffsets over.
|
||||
{
|
||||
const auto it = std::copy_n(_chars.begin(), charsToCopy, chars.begin());
|
||||
std::fill_n(it, trailingWhitespace, L' ');
|
||||
}
|
||||
{
|
||||
const auto it = std::copy_n(_charOffsets.begin(), colsToCopy, charOffsets.begin());
|
||||
// The _charOffsets array is 1 wider than newWidth indicates.
|
||||
// This is because the extra column contains the past-the-end index into _chars.
|
||||
iota_n(it, trailingWhitespace + 1u, charsToCopy);
|
||||
}
|
||||
|
||||
_charsBuffer = charsBuffer;
|
||||
_charsHeap = std::move(charsHeap);
|
||||
_chars = chars;
|
||||
_charOffsets = charOffsets;
|
||||
_columnCount = rowWidth;
|
||||
|
||||
// .resize_trailing_extent() doesn't work if the vector is empty,
|
||||
// since there's no trailing item that could be extended.
|
||||
if (_attr.empty())
|
||||
{
|
||||
_attr = { rowWidth, fillAttribute };
|
||||
}
|
||||
else
|
||||
{
|
||||
_attr.resize_trailing_extent(rowWidth);
|
||||
}
|
||||
}
|
||||
|
||||
void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth)
|
||||
{
|
||||
_attr = attr;
|
||||
_attr.resize_trailing_extent(gsl::narrow<uint16_t>(newWidth));
|
||||
}
|
||||
|
||||
// Returns the previous possible cursor position, preceding the given column.
|
||||
// Returns 0 if column is less than or equal to 0.
|
||||
til::CoordType ROW::NavigateToPrevious(til::CoordType column) const noexcept
|
||||
{
|
||||
return _adjustBackward(_clampedColumn(column - 1));
|
||||
}
|
||||
|
||||
// Returns the next possible cursor position, following the given column.
|
||||
// Returns the row width if column is beyond the width of the row.
|
||||
til::CoordType ROW::NavigateToNext(til::CoordType column) const noexcept
|
||||
{
|
||||
return _adjustForward(_clampedColumn(column + 1));
|
||||
}
|
||||
|
||||
uint16_t ROW::_adjustBackward(uint16_t column) const noexcept
|
||||
{
|
||||
// Safety: This is a little bit more dangerous. The first column is supposed
|
||||
// to never be a trailer and so this loop should exit if column == 0.
|
||||
for (; _uncheckedIsTrailer(column); --column)
|
||||
{
|
||||
}
|
||||
return column;
|
||||
}
|
||||
|
||||
uint16_t ROW::_adjustForward(uint16_t column) const noexcept
|
||||
{
|
||||
// Safety: This is a little bit more dangerous. The last column is supposed
|
||||
// to never be a trailer and so this loop should exit if column == _columnCount.
|
||||
for (; _uncheckedIsTrailer(column); ++column)
|
||||
{
|
||||
}
|
||||
return column;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - clears char data in column in row
|
||||
// Arguments:
|
||||
@@ -311,16 +263,20 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType c
|
||||
}
|
||||
break;
|
||||
case DbcsAttribute::Trailing:
|
||||
// Handling the trailing half of wide chars ensures that we correctly restore
|
||||
// wide characters when a user backs up and restores the viewport via CHAR_INFOs.
|
||||
if (fillingFirstColumn)
|
||||
{
|
||||
// The wide char doesn't fit. Pad with whitespace.
|
||||
// Ignore the character. There's no correct alternative way to handle this situation.
|
||||
ClearCell(currentIndex);
|
||||
}
|
||||
else
|
||||
else if (it.Position() == 0)
|
||||
{
|
||||
// A common way to back up and restore the buffer is via `ReadConsoleOutputW` and
|
||||
// `WriteConsoleOutputW` respectively. But the area might bisect/intersect/clip wide characters and
|
||||
// only backup either their leading or trailing half. In general, in the rest of conhost, we're
|
||||
// throwing away the trailing half of all `CHAR_INFO`s (during text rendering, as well as during
|
||||
// `ReadConsoleOutputW`), so to make this code behave the same and prevent surprises, we need to
|
||||
// make sure to only look at the trailer if it's the first `CHAR_INFO` the user is trying to write.
|
||||
ReplaceCharacters(currentIndex - 1, 2, chars);
|
||||
}
|
||||
++it;
|
||||
@@ -371,90 +327,245 @@ void ROW::ReplaceAttributes(const til::CoordType beginIndex, const til::CoordTyp
|
||||
_attr.replace(_clampedColumnInclusive(beginIndex), _clampedColumnInclusive(endIndex), newAttr);
|
||||
}
|
||||
|
||||
void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars)
|
||||
[[msvc::forceinline]] ROW::WriteHelper::WriteHelper(ROW& row, til::CoordType columnBegin, til::CoordType columnLimit, const std::wstring_view& chars) noexcept :
|
||||
row{ row },
|
||||
chars{ chars }
|
||||
{
|
||||
const auto colBeg = _clampedUint16(columnBegin);
|
||||
const auto colEnd = _clampedUint16(columnBegin + width);
|
||||
colBeg = row._clampedColumnInclusive(columnBegin);
|
||||
colLimit = row._clampedColumnInclusive(columnLimit);
|
||||
chBegDirty = row._uncheckedCharOffset(colBeg);
|
||||
colBegDirty = row._adjustBackward(colBeg);
|
||||
leadingSpaces = colBeg - colBegDirty;
|
||||
chBeg = chBegDirty + leadingSpaces;
|
||||
colEnd = colBeg;
|
||||
colEndDirty = 0;
|
||||
charsConsumed = 0;
|
||||
}
|
||||
|
||||
if (colBeg >= colEnd || colEnd > _columnCount || chars.empty())
|
||||
[[msvc::forceinline]] bool ROW::WriteHelper::IsValid() const noexcept
|
||||
{
|
||||
return colBeg < colLimit && !chars.empty();
|
||||
}
|
||||
|
||||
void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars)
|
||||
try
|
||||
{
|
||||
WriteHelper h{ *this, columnBegin, _columnCount, chars };
|
||||
if (!h.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
h.ReplaceCharacters(width);
|
||||
h.Finish();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Due to this function writing _charOffsets first, then calling _resizeChars (which may throw) and only then finally
|
||||
// filling in _chars, we might end up in a situation were _charOffsets contains offsets outside of the _chars array.
|
||||
// --> Restore this row to a known "okay"-state.
|
||||
Reset(TextAttribute{});
|
||||
throw;
|
||||
}
|
||||
|
||||
// Safety:
|
||||
// * colBeg is now [0, _columnCount)
|
||||
// * colEnd is now (colBeg, _columnCount]
|
||||
[[msvc::forceinline]] void ROW::WriteHelper::ReplaceCharacters(til::CoordType width) noexcept
|
||||
{
|
||||
const auto colEndNew = gsl::narrow_cast<uint16_t>(colEnd + width);
|
||||
if (colEndNew > colLimit)
|
||||
{
|
||||
colEndDirty = colLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
til::at(row._charOffsets, colEnd++) = chBeg;
|
||||
for (; colEnd < colEndNew; ++colEnd)
|
||||
{
|
||||
til::at(row._charOffsets, colEnd) = gsl::narrow_cast<uint16_t>(chBeg | CharOffsetsTrailer);
|
||||
}
|
||||
|
||||
// Algorithm explanation
|
||||
colEndDirty = colEnd;
|
||||
charsConsumed = chars.size();
|
||||
}
|
||||
}
|
||||
|
||||
void ROW::ReplaceText(RowWriteState& state)
|
||||
try
|
||||
{
|
||||
WriteHelper h{ *this, state.columnBegin, state.columnLimit, state.text };
|
||||
if (!h.IsValid())
|
||||
{
|
||||
state.columnEnd = h.colBeg;
|
||||
state.columnBeginDirty = h.colBeg;
|
||||
state.columnEndDirty = h.colBeg;
|
||||
return;
|
||||
}
|
||||
h.ReplaceText();
|
||||
h.Finish();
|
||||
|
||||
state.text = state.text.substr(h.charsConsumed);
|
||||
// Here's why we set `state.columnEnd` to `colLimit` if there's remaining text:
|
||||
// Callers should be able to use `state.columnEnd` as the next cursor position, as well as the parameter for a
|
||||
// follow-up call to ReplaceAttributes(). But if we fail to insert a wide glyph into the last column of a row,
|
||||
// that last cell (which now contains padding whitespace) should get the same attributes as the rest of the
|
||||
// string so that the row looks consistent. This requires us to return `colLimit` instead of `colLimit - 1`.
|
||||
// Additionally, this has the benefit that callers can detect line wrapping by checking `columnEnd >= columnLimit`.
|
||||
state.columnEnd = state.text.empty() ? h.colEnd : h.colLimit;
|
||||
state.columnBeginDirty = h.colBegDirty;
|
||||
state.columnEndDirty = h.colEndDirty;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Reset(TextAttribute{});
|
||||
throw;
|
||||
}
|
||||
|
||||
[[msvc::forceinline]] void ROW::WriteHelper::ReplaceText() noexcept
|
||||
{
|
||||
size_t ch = chBeg;
|
||||
|
||||
for (const auto& s : til::utf16_iterator{ chars })
|
||||
{
|
||||
const auto wide = til::at(s, 0) < 0x80 ? false : IsGlyphFullWidth(s);
|
||||
const auto colEndNew = gsl::narrow_cast<uint16_t>(colEnd + 1u + wide);
|
||||
if (colEndNew > colLimit)
|
||||
{
|
||||
colEndDirty = colLimit;
|
||||
break;
|
||||
}
|
||||
|
||||
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(ch);
|
||||
if (wide)
|
||||
{
|
||||
til::at(row._charOffsets, colEnd++) = gsl::narrow_cast<uint16_t>(ch | CharOffsetsTrailer);
|
||||
}
|
||||
|
||||
colEndDirty = colEnd;
|
||||
ch += s.size();
|
||||
}
|
||||
|
||||
charsConsumed = ch - chBeg;
|
||||
}
|
||||
|
||||
til::CoordType ROW::CopyRangeFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit)
|
||||
try
|
||||
{
|
||||
const auto otherColBeg = other._clampedColumnInclusive(otherBegin);
|
||||
const auto otherColLimit = other._clampedColumnInclusive(otherLimit);
|
||||
std::span<uint16_t> charOffsets;
|
||||
std::wstring_view chars;
|
||||
|
||||
if (otherColBeg < otherColLimit)
|
||||
{
|
||||
charOffsets = other._charOffsets.subspan(otherColBeg, static_cast<size_t>(otherColLimit) - otherColBeg + 1);
|
||||
const auto charsOffset = charOffsets.front() & CharOffsetsMask;
|
||||
// We _are_ using span. But C++ decided that string_view and span aren't convertible.
|
||||
// _chars is a std::span for performance and because it refers to raw, shared memory.
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
chars = { other._chars.data() + charsOffset, other._chars.size() - charsOffset };
|
||||
}
|
||||
|
||||
WriteHelper h{ *this, columnBegin, columnLimit, chars };
|
||||
if (!h.IsValid())
|
||||
{
|
||||
return h.colBeg;
|
||||
}
|
||||
// Any valid charOffsets array is at least 2 elements long (the 1st element is the start offset and the 2nd
|
||||
// element is the length of the first glyph) and begins/ends with a non-trailer offset. We don't really
|
||||
// need to test for the end offset, since `WriteHelper::WriteWithOffsets` already takes care of that.
|
||||
if (charOffsets.size() < 2 || WI_IsFlagSet(charOffsets.front(), CharOffsetsTrailer))
|
||||
{
|
||||
assert(false);
|
||||
otherBegin = other.size();
|
||||
return h.colBeg;
|
||||
}
|
||||
h.CopyRangeFrom(charOffsets);
|
||||
h.Finish();
|
||||
|
||||
otherBegin += h.colEnd - h.colBeg;
|
||||
return h.colEndDirty;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Reset(TextAttribute{});
|
||||
throw;
|
||||
}
|
||||
|
||||
[[msvc::forceinline]] void ROW::WriteHelper::CopyRangeFrom(const std::span<const uint16_t>& charOffsets) noexcept
|
||||
{
|
||||
// Since our `charOffsets` input is already in columns (just like the `ROW::_charOffsets`),
|
||||
// we can directly look up the end char-offset, but...
|
||||
const auto colEndDirtyInput = std::min(gsl::narrow_cast<uint16_t>(colLimit - colBeg), gsl::narrow<uint16_t>(charOffsets.size() - 1));
|
||||
|
||||
// ...since the colLimit might intersect with a wide glyph in `charOffset`, we need to adjust our input-colEnd.
|
||||
auto colEndInput = colEndDirtyInput;
|
||||
for (; WI_IsFlagSet(til::at(charOffsets, colEndInput), CharOffsetsTrailer); --colEndInput)
|
||||
{
|
||||
}
|
||||
|
||||
const auto baseOffset = til::at(charOffsets, 0);
|
||||
const auto endOffset = til::at(charOffsets, colEndInput);
|
||||
const auto inToOutOffset = gsl::narrow_cast<uint16_t>(chBeg - baseOffset);
|
||||
|
||||
// Now with the `colEndInput` figured out, we can easily copy the `charOffsets` into the `_charOffsets`.
|
||||
// It's possible to use SIMD for this loop for extra perf gains. Something like this for SSE2 (~8x faster):
|
||||
// const auto in = _mm_loadu_si128(...);
|
||||
// const auto off = _mm_and_epi32(in, _mm_set1_epi16(CharOffsetsMask));
|
||||
// const auto trailer = _mm_and_epi32(in, _mm_set1_epi16(CharOffsetsTrailer));
|
||||
// const auto out = _mm_or_epi32(_mm_add_epi16(off, _mm_set1_epi16(inToOutOffset)), trailer);
|
||||
// _mm_store_si128(..., out);
|
||||
for (uint16_t i = 0; i < colEndInput; ++i, ++colEnd)
|
||||
{
|
||||
const auto ch = til::at(charOffsets, i);
|
||||
const auto off = ch & CharOffsetsMask;
|
||||
const auto trailer = ch & CharOffsetsTrailer;
|
||||
til::at(row._charOffsets, colEnd) = gsl::narrow_cast<uint16_t>((off + inToOutOffset) | trailer);
|
||||
}
|
||||
|
||||
colEndDirty = gsl::narrow_cast<uint16_t>(colBeg + colEndDirtyInput);
|
||||
charsConsumed = endOffset - baseOffset;
|
||||
}
|
||||
|
||||
[[msvc::forceinline]] void ROW::WriteHelper::Finish()
|
||||
{
|
||||
colEndDirty = row._adjustForward(colEndDirty);
|
||||
|
||||
const uint16_t trailingSpaces = colEndDirty - colEnd;
|
||||
const auto chEndDirtyOld = row._uncheckedCharOffset(colEndDirty);
|
||||
const auto chEndDirty = chBegDirty + charsConsumed + leadingSpaces + trailingSpaces;
|
||||
|
||||
if (chEndDirty != chEndDirtyOld)
|
||||
{
|
||||
row._resizeChars(colEndDirty, chBegDirty, chEndDirty, chEndDirtyOld);
|
||||
}
|
||||
|
||||
{
|
||||
// std::copy_n compiles to memmove. We can do better. It also gets rid of an extra branch,
|
||||
// because std::copy_n avoids calling memmove if the count is 0. It's never 0 for us.
|
||||
const auto itBeg = row._chars.begin() + chBeg;
|
||||
memcpy(&*itBeg, chars.data(), charsConsumed * sizeof(wchar_t));
|
||||
|
||||
if (leadingSpaces)
|
||||
{
|
||||
fill_n_small(row._chars.begin() + chBegDirty, leadingSpaces, L' ');
|
||||
iota_n(row._charOffsets.begin() + colBegDirty, leadingSpaces, chBegDirty);
|
||||
}
|
||||
if (trailingSpaces)
|
||||
{
|
||||
fill_n_small(itBeg + charsConsumed, trailingSpaces, L' ');
|
||||
iota_n(row._charOffsets.begin() + colEnd, trailingSpaces, gsl::narrow_cast<uint16_t>(chBeg + charsConsumed));
|
||||
}
|
||||
}
|
||||
|
||||
// This updates `_doubleBytePadded` whenever we write the last column in the row. `_doubleBytePadded` tells our text
|
||||
// reflow algorithm whether it should ignore the last column. This is important when writing wide characters into
|
||||
// the terminal: If the last wide character in a row only fits partially, we should render whitespace, but
|
||||
// during text reflow pretend as if no whitespace exists. After all, the user didn't write any whitespace there.
|
||||
//
|
||||
// Task:
|
||||
// Replace the characters in cells [colBeg, colEnd) with a single `width`-wide glyph consisting of `chars`.
|
||||
//
|
||||
// Problem:
|
||||
// Imagine that we have the following ROW contents:
|
||||
// "xxyyzz"
|
||||
// xx, yy, zz are 2 cell wide glyphs. We want to insert a 2 cell wide glyph ww at colBeg 1:
|
||||
// ^^
|
||||
// ww
|
||||
// An incorrect result would be:
|
||||
// "xwwyzz"
|
||||
// The half cut off x and y glyph wouldn't make much sense, so we need to fill them with whitespace:
|
||||
// " ww zz"
|
||||
//
|
||||
// Solution:
|
||||
// Given the range we want to replace [colBeg, colEnd), we "extend" it to encompass leading (preceding)
|
||||
// and trailing wide glyphs we partially overwrite resulting in the range [colExtBeg, colExtEnd), where
|
||||
// colExtBeg <= colBeg and colExtEnd >= colEnd. In other words, the to be replaced range has been "extended".
|
||||
// The amount of leading whitespace we need to insert is thus colBeg - colExtBeg
|
||||
// and the amount of trailing whitespace colExtEnd - colEnd.
|
||||
|
||||
// Extend range downwards (leading whitespace)
|
||||
uint16_t colExtBeg = colBeg;
|
||||
// Safety: colExtBeg is [0, _columnCount], because colBeg is.
|
||||
const uint16_t chExtBeg = _uncheckedCharOffset(colExtBeg);
|
||||
// Safety: colExtBeg remains [0, _columnCount] due to colExtBeg != 0.
|
||||
for (; colExtBeg != 0 && _uncheckedIsTrailer(colExtBeg); --colExtBeg)
|
||||
// The way this is written, it'll set `_doubleBytePadded` to `true` no matter whether a wide character didn't fit,
|
||||
// or if the last 2 columns contain a wide character and a narrow character got written into the left half of it.
|
||||
// In both cases `trailingSpaces` is 1 and fills the last column and `_doubleBytePadded` will be `true`.
|
||||
if (colEndDirty == row._columnCount)
|
||||
{
|
||||
}
|
||||
|
||||
// Extend range upwards (trailing whitespace)
|
||||
uint16_t colExtEnd = colEnd;
|
||||
// Safety: colExtEnd cannot be incremented past _columnCount, because the last
|
||||
// _charOffset at index _columnCount will never get the CharOffsetsTrailer flag.
|
||||
for (; _uncheckedIsTrailer(colExtEnd); ++colExtEnd)
|
||||
{
|
||||
}
|
||||
// Safety: After the previous loop colExtEnd is [0, _columnCount].
|
||||
const uint16_t chExtEnd = _uncheckedCharOffset(colExtEnd);
|
||||
|
||||
const uint16_t leadingSpaces = colBeg - colExtBeg;
|
||||
const uint16_t trailingSpaces = colExtEnd - colEnd;
|
||||
const size_t chExtEndNew = chars.size() + leadingSpaces + trailingSpaces + chExtBeg;
|
||||
|
||||
if (chExtEndNew != chExtEnd)
|
||||
{
|
||||
_resizeChars(colExtEnd, chExtBeg, chExtEnd, chExtEndNew);
|
||||
}
|
||||
|
||||
// Add leading/trailing whitespace and copy chars
|
||||
{
|
||||
auto it = _chars.begin() + chExtBeg;
|
||||
it = fill_n_small(it, leadingSpaces, L' ');
|
||||
it = copy_n_small(chars.begin(), chars.size(), it);
|
||||
it = fill_n_small(it, trailingSpaces, L' ');
|
||||
}
|
||||
// Update char offsets with leading/trailing whitespace and the chars columns.
|
||||
{
|
||||
auto chPos = chExtBeg;
|
||||
auto it = _charOffsets.begin() + colExtBeg;
|
||||
|
||||
it = iota_n_mut(it, leadingSpaces, chPos);
|
||||
|
||||
*it++ = chPos;
|
||||
it = fill_small(it, _charOffsets.begin() + colEnd, gsl::narrow_cast<uint16_t>(chPos | CharOffsetsTrailer));
|
||||
chPos = gsl::narrow_cast<uint16_t>(chPos + chars.size());
|
||||
|
||||
it = iota_n_mut(it, trailingSpaces, chPos);
|
||||
row.SetDoubleBytePadded(colEnd < row._columnCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,15 +573,15 @@ void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, co
|
||||
// as it reallocates the backing buffer and shifts the char offsets.
|
||||
// The parameters are difficult to explain, but their names are identical to
|
||||
// local variables in ReplaceCharacters() which I've attempted to document there.
|
||||
void ROW::_resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew)
|
||||
void ROW::_resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDirty, uint16_t chEndDirtyOld)
|
||||
{
|
||||
const auto diff = chExtEndNew - chExtEnd;
|
||||
const auto diff = chEndDirty - chEndDirtyOld;
|
||||
const auto currentLength = _charSize();
|
||||
const auto newLength = currentLength + diff;
|
||||
|
||||
if (newLength <= _chars.size())
|
||||
{
|
||||
std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, _chars.begin() + chExtEndNew);
|
||||
std::copy_n(_chars.begin() + chEndDirtyOld, currentLength - chEndDirtyOld, _chars.begin() + chEndDirty);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -480,14 +591,14 @@ void ROW::_resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd,
|
||||
auto charsHeap = std::make_unique_for_overwrite<wchar_t[]>(newCapacity);
|
||||
const std::span chars{ charsHeap.get(), newCapacity };
|
||||
|
||||
std::copy_n(_chars.begin(), chExtBeg, chars.begin());
|
||||
std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, chars.begin() + chExtEndNew);
|
||||
std::copy_n(_chars.begin(), chBegDirty, chars.begin());
|
||||
std::copy_n(_chars.begin() + chEndDirtyOld, currentLength - chEndDirtyOld, chars.begin() + chEndDirty);
|
||||
|
||||
_charsHeap = std::move(charsHeap);
|
||||
_chars = chars;
|
||||
}
|
||||
|
||||
auto it = _charOffsets.begin() + colExtEnd;
|
||||
auto it = _charOffsets.begin() + colEndDirty;
|
||||
const auto end = _charOffsets.end();
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
@@ -495,6 +606,11 @@ void ROW::_resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd,
|
||||
}
|
||||
}
|
||||
|
||||
til::small_rle<TextAttribute, uint16_t, 1>& ROW::Attributes() noexcept
|
||||
{
|
||||
return _attr;
|
||||
}
|
||||
|
||||
const til::small_rle<TextAttribute, uint16_t, 1>& ROW::Attributes() const noexcept
|
||||
{
|
||||
return _attr;
|
||||
@@ -523,6 +639,12 @@ uint16_t ROW::size() const noexcept
|
||||
return _columnCount;
|
||||
}
|
||||
|
||||
til::CoordType ROW::LineRenditionColumns() const noexcept
|
||||
{
|
||||
const auto scale = _lineRendition != LineRendition::SingleWidth ? 1 : 0;
|
||||
return _columnCount >> scale;
|
||||
}
|
||||
|
||||
til::CoordType ROW::MeasureLeft() const noexcept
|
||||
{
|
||||
const auto text = GetText();
|
||||
@@ -677,11 +799,13 @@ uint16_t ROW::_charSize() const noexcept
|
||||
// Safety: col must be [0, _columnCount].
|
||||
uint16_t ROW::_uncheckedCharOffset(size_t col) const noexcept
|
||||
{
|
||||
assert(col < _charOffsets.size());
|
||||
return til::at(_charOffsets, col) & CharOffsetsMask;
|
||||
}
|
||||
|
||||
// Safety: col must be [0, _columnCount].
|
||||
bool ROW::_uncheckedIsTrailer(size_t col) const noexcept
|
||||
{
|
||||
assert(col < _charOffsets.size());
|
||||
return WI_IsFlagSet(til::at(_charOffsets, col), CharOffsetsTrailer);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ Revision History:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include <til/rle.h>
|
||||
|
||||
#include "LineRendition.hpp"
|
||||
@@ -37,6 +35,28 @@ enum class DelimiterClass
|
||||
RegularChar
|
||||
};
|
||||
|
||||
struct RowWriteState
|
||||
{
|
||||
// The text you want to write into the given ROW. When ReplaceText() returns,
|
||||
// this is updated to remove all text from the beginning that was successfully written.
|
||||
std::wstring_view text; // IN/OUT
|
||||
// The column at which to start writing.
|
||||
til::CoordType columnBegin = 0; // IN
|
||||
// The first column which should not be written to anymore.
|
||||
til::CoordType columnLimit = 0; // IN
|
||||
|
||||
// The column 1 past the last glyph that was successfully written into the row. If you need to call
|
||||
// ReplaceAttributes() to colorize the written range, etc., this is the columnEnd parameter you want.
|
||||
// If you want to continue writing where you left off, this is also the next columnBegin parameter.
|
||||
til::CoordType columnEnd = 0; // OUT
|
||||
// The first column that got modified by this write operation. In case that the first glyph we write overwrites
|
||||
// the trailing half of a wide glyph, leadingSpaces will be 1 and this value will be 1 less than colBeg.
|
||||
til::CoordType columnBeginDirty = 0; // OUT
|
||||
// This is 1 past the last column that was modified and will be 1 past columnEnd if we overwrote
|
||||
// the leading half of a wide glyph and had to fill the trailing half with whitespace.
|
||||
til::CoordType columnEndDirty = 0; // OUT
|
||||
};
|
||||
|
||||
class ROW final
|
||||
{
|
||||
public:
|
||||
@@ -46,11 +66,9 @@ public:
|
||||
ROW(const ROW& other) = delete;
|
||||
ROW& operator=(const ROW& other) = delete;
|
||||
|
||||
explicit ROW(ROW&& other) = default;
|
||||
ROW(ROW&& other) = default;
|
||||
ROW& operator=(ROW&& other) = default;
|
||||
|
||||
friend void swap(ROW& lhs, ROW& rhs) noexcept;
|
||||
|
||||
void SetWrapForced(const bool wrap) noexcept;
|
||||
bool WasWrapForced() const noexcept;
|
||||
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept;
|
||||
@@ -59,19 +77,25 @@ public:
|
||||
LineRendition GetLineRendition() const noexcept;
|
||||
|
||||
void Reset(const TextAttribute& attr);
|
||||
void Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
|
||||
void TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth);
|
||||
|
||||
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
|
||||
til::CoordType NavigateToNext(til::CoordType column) const noexcept;
|
||||
|
||||
void ClearCell(til::CoordType column);
|
||||
OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional<bool> wrap = std::nullopt, std::optional<til::CoordType> limitRight = std::nullopt);
|
||||
bool SetAttrToEnd(til::CoordType columnBegin, TextAttribute attr);
|
||||
void ReplaceAttributes(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr);
|
||||
void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars);
|
||||
void ReplaceText(RowWriteState& state);
|
||||
til::CoordType CopyRangeFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit);
|
||||
|
||||
til::small_rle<TextAttribute, uint16_t, 1>& Attributes() noexcept;
|
||||
const til::small_rle<TextAttribute, uint16_t, 1>& Attributes() const noexcept;
|
||||
TextAttribute GetAttrByColumn(til::CoordType column) const;
|
||||
std::vector<uint16_t> GetHyperlinks() const;
|
||||
uint16_t size() const noexcept;
|
||||
til::CoordType LineRenditionColumns() const noexcept;
|
||||
til::CoordType MeasureLeft() const noexcept;
|
||||
til::CoordType MeasureRight() const noexcept;
|
||||
bool ContainsText() const noexcept;
|
||||
@@ -89,6 +113,50 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
// WriteHelper exists because other forms of abstracting this functionality away (like templates with lambdas)
|
||||
// where only very poorly optimized by MSVC as it failed to inline the templates.
|
||||
struct WriteHelper
|
||||
{
|
||||
explicit WriteHelper(ROW& row, til::CoordType columnBegin, til::CoordType columnLimit, const std::wstring_view& chars) noexcept;
|
||||
bool IsValid() const noexcept;
|
||||
void ReplaceCharacters(til::CoordType width) noexcept;
|
||||
void ReplaceText() noexcept;
|
||||
void CopyRangeFrom(const std::span<const uint16_t>& charOffsets) noexcept;
|
||||
void Finish();
|
||||
|
||||
// Parent pointer.
|
||||
ROW& row;
|
||||
// The text given by the caller.
|
||||
const std::wstring_view& chars;
|
||||
|
||||
// This is the same as the columnBegin parameter for ReplaceText(), etc.,
|
||||
// but clamped to a valid range via _clampedColumnInclusive.
|
||||
uint16_t colBeg;
|
||||
// This is the same as the columnLimit parameter for ReplaceText(), etc.,
|
||||
// but clamped to a valid range via _clampedColumnInclusive.
|
||||
uint16_t colLimit;
|
||||
|
||||
// The column 1 past the last glyph that was successfully written into the row. If you need to call
|
||||
// ReplaceAttributes() to colorize the written range, etc., this is the columnEnd parameter you want.
|
||||
// If you want to continue writing where you left off, this is also the next columnBegin parameter.
|
||||
uint16_t colEnd;
|
||||
// The first column that got modified by this write operation. In case that the first glyph we write overwrites
|
||||
// the trailing half of a wide glyph, leadingSpaces will be 1 and this value will be 1 less than colBeg.
|
||||
uint16_t colBegDirty;
|
||||
// Similar to dirtyBeg, this is 1 past the last column that was modified and will be 1 past colEnd if
|
||||
// we overwrote the leading half of a wide glyph and had to fill the trailing half with whitespace.
|
||||
uint16_t colEndDirty;
|
||||
// The offset in ROW::chars at which we start writing the contents of WriteHelper::chars.
|
||||
uint16_t chBeg;
|
||||
// The offset at which we start writing leadingSpaces-many whitespaces.
|
||||
uint16_t chBegDirty;
|
||||
// The same as `colBeg - colBegDirty`. This is the amount of whitespace
|
||||
// we write at chBegDirty, before the actual WriteHelper::chars content.
|
||||
uint16_t leadingSpaces;
|
||||
// The amount of characters copied from WriteHelper::chars.
|
||||
size_t charsConsumed;
|
||||
};
|
||||
|
||||
// To simplify the detection of wide glyphs, we don't just store the simple character offset as described
|
||||
// for _charOffsets. Instead we use the most significant bit to indicate whether any column is the
|
||||
// trailing half of a wide glyph. This simplifies many implementation details via _uncheckedIsTrailer.
|
||||
@@ -102,13 +170,16 @@ private:
|
||||
template<typename T>
|
||||
constexpr uint16_t _clampedColumnInclusive(T v) const noexcept;
|
||||
|
||||
uint16_t _adjustBackward(uint16_t column) const noexcept;
|
||||
uint16_t _adjustForward(uint16_t column) const noexcept;
|
||||
|
||||
wchar_t _uncheckedChar(size_t off) const noexcept;
|
||||
uint16_t _charSize() const noexcept;
|
||||
uint16_t _uncheckedCharOffset(size_t col) const noexcept;
|
||||
bool _uncheckedIsTrailer(size_t col) const noexcept;
|
||||
|
||||
void _init() noexcept;
|
||||
void _resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew);
|
||||
void _resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDirty, uint16_t chEndDirtyOld);
|
||||
|
||||
// These fields are a bit "wasteful", but it makes all this a bit more robust against
|
||||
// programming errors during initial development (which is when this comment was written).
|
||||
|
||||
@@ -64,7 +64,7 @@ bool TextColor::CanBeBrightened() const noexcept
|
||||
|
||||
bool TextColor::IsLegacy() const noexcept
|
||||
{
|
||||
return IsIndex16() || (IsIndex256() && _index < 16);
|
||||
return (IsIndex16() || IsIndex256()) && _index < 16;
|
||||
}
|
||||
|
||||
bool TextColor::IsIndex16() const noexcept
|
||||
@@ -82,6 +82,11 @@ bool TextColor::IsDefault() const noexcept
|
||||
return _meta == ColorType::IsDefault;
|
||||
}
|
||||
|
||||
bool TextColor::IsDefaultOrLegacy() const noexcept
|
||||
{
|
||||
return _meta != ColorType::IsRgb && _index < 16;
|
||||
}
|
||||
|
||||
bool TextColor::IsRgb() const noexcept
|
||||
{
|
||||
return _meta == ColorType::IsRgb;
|
||||
|
||||
@@ -37,12 +37,14 @@ Revision History:
|
||||
#include "WexTestClass.h"
|
||||
#endif
|
||||
|
||||
// The enum values being in this particular order allows the compiler to do some useful optimizations,
|
||||
// like simplifying `IsIndex16() || IsIndex256()` into a simple range check without branching.
|
||||
enum class ColorType : BYTE
|
||||
{
|
||||
IsIndex256 = 0x0,
|
||||
IsIndex16 = 0x1,
|
||||
IsDefault = 0x2,
|
||||
IsRgb = 0x3
|
||||
IsDefault,
|
||||
IsIndex16,
|
||||
IsIndex256,
|
||||
IsRgb
|
||||
};
|
||||
|
||||
enum class ColorAlias : size_t
|
||||
@@ -121,6 +123,7 @@ public:
|
||||
bool IsIndex16() const noexcept;
|
||||
bool IsIndex256() const noexcept;
|
||||
bool IsDefault() const noexcept;
|
||||
bool IsDefaultOrLegacy() const noexcept;
|
||||
bool IsRgb() const noexcept;
|
||||
|
||||
void SetColor(const COLORREF rgbColor) noexcept;
|
||||
|
||||
@@ -101,6 +101,13 @@ void Cursor::SetIsOn(const bool fIsOn) noexcept
|
||||
void Cursor::SetBlinkingAllowed(const bool fBlinkingAllowed) noexcept
|
||||
{
|
||||
_fBlinkingAllowed = fBlinkingAllowed;
|
||||
// GH#2642 - From what we've gathered from other terminals, when blinking is
|
||||
// disabled, the cursor should remain On always, and have the visibility
|
||||
// controlled by the IsVisible property. So when you do a printf "\e[?12l"
|
||||
// to disable blinking, the cursor stays stuck On. At this point, only the
|
||||
// cursor visibility property controls whether the user can see it or not.
|
||||
// (Yes, the cursor can be On and NOT Visible)
|
||||
_fIsOn = true;
|
||||
_RedrawCursorAlways();
|
||||
}
|
||||
|
||||
@@ -199,7 +206,7 @@ void Cursor::SetPosition(const til::point cPosition) noexcept
|
||||
void Cursor::SetXPosition(const til::CoordType NewX) noexcept
|
||||
{
|
||||
_RedrawCursor();
|
||||
_cPosition.X = NewX;
|
||||
_cPosition.x = NewX;
|
||||
_RedrawCursor();
|
||||
ResetDelayEOLWrap();
|
||||
}
|
||||
@@ -207,7 +214,7 @@ void Cursor::SetXPosition(const til::CoordType NewX) noexcept
|
||||
void Cursor::SetYPosition(const til::CoordType NewY) noexcept
|
||||
{
|
||||
_RedrawCursor();
|
||||
_cPosition.Y = NewY;
|
||||
_cPosition.y = NewY;
|
||||
_RedrawCursor();
|
||||
ResetDelayEOLWrap();
|
||||
}
|
||||
@@ -215,7 +222,7 @@ void Cursor::SetYPosition(const til::CoordType NewY) noexcept
|
||||
void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept
|
||||
{
|
||||
_RedrawCursor();
|
||||
_cPosition.X += DeltaX;
|
||||
_cPosition.x += DeltaX;
|
||||
_RedrawCursor();
|
||||
ResetDelayEOLWrap();
|
||||
}
|
||||
@@ -223,7 +230,7 @@ void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept
|
||||
void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept
|
||||
{
|
||||
_RedrawCursor();
|
||||
_cPosition.Y += DeltaY;
|
||||
_cPosition.y += DeltaY;
|
||||
_RedrawCursor();
|
||||
ResetDelayEOLWrap();
|
||||
}
|
||||
@@ -231,7 +238,7 @@ void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept
|
||||
void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept
|
||||
{
|
||||
_RedrawCursor();
|
||||
_cPosition.X -= DeltaX;
|
||||
_cPosition.x -= DeltaX;
|
||||
_RedrawCursor();
|
||||
ResetDelayEOLWrap();
|
||||
}
|
||||
@@ -239,7 +246,7 @@ void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept
|
||||
void Cursor::DecrementYPosition(const til::CoordType DeltaY) noexcept
|
||||
{
|
||||
_RedrawCursor();
|
||||
_cPosition.Y -= DeltaY;
|
||||
_cPosition.y -= DeltaY;
|
||||
_RedrawCursor();
|
||||
ResetDelayEOLWrap();
|
||||
}
|
||||
@@ -279,9 +286,9 @@ void Cursor::CopyProperties(const Cursor& OtherCursor) noexcept
|
||||
_cursorType = OtherCursor._cursorType;
|
||||
}
|
||||
|
||||
void Cursor::DelayEOLWrap(const til::point coordDelayedAt) noexcept
|
||||
void Cursor::DelayEOLWrap() noexcept
|
||||
{
|
||||
_coordDelayedAt = coordDelayedAt;
|
||||
_coordDelayedAt = _cPosition;
|
||||
_fDelayedEolWrap = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
|
||||
void CopyProperties(const Cursor& OtherCursor) noexcept;
|
||||
|
||||
void DelayEOLWrap(const til::point coordDelayedAt) noexcept;
|
||||
void DelayEOLWrap() noexcept;
|
||||
void ResetDelayEOLWrap() noexcept;
|
||||
til::point GetDelayedAtPosition() const noexcept;
|
||||
bool IsDelayedEOLWrap() const noexcept;
|
||||
|
||||
@@ -1,41 +1,7 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- precomp.h
|
||||
|
||||
Abstract:
|
||||
- Contains external headers to include in the precompile phase of console build process.
|
||||
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
|
||||
--*/
|
||||
|
||||
// stdafx.h : include file for standard system include files,
|
||||
// or project specific include files that are used frequently, but
|
||||
// are changed infrequently
|
||||
//
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#pragma once
|
||||
|
||||
// clang-format off
|
||||
#include <LibraryIncludes.h>
|
||||
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
#pragma warning(push)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
#define NOMCX
|
||||
#define NOHELP
|
||||
#define NOCOMM
|
||||
#endif
|
||||
|
||||
// Windows Header Files:
|
||||
#include <windows.h>
|
||||
#include <intsafe.h>
|
||||
|
||||
// private dependencies
|
||||
#include "../inc/unicode.hpp"
|
||||
#pragma warning(pop)
|
||||
|
||||
// clang-format on
|
||||
#include <unicode.hpp>
|
||||
|
||||
@@ -18,19 +18,19 @@ using namespace Microsoft::Console::Types;
|
||||
// - Once you've found something, you can perform actions like .Select() or .Color()
|
||||
// Arguments:
|
||||
// - textBuffer - The screen text buffer to search through (the "haystack")
|
||||
// - uiaData - The IUiaData type reference, it is for providing selection methods
|
||||
// - renderData - The IRenderData type reference, it is for providing selection methods
|
||||
// - str - The search term you want to find (the "needle")
|
||||
// - direction - The direction to search (upward or downward)
|
||||
// - sensitivity - Whether or not you care about case
|
||||
Search::Search(IUiaData& uiaData,
|
||||
Search::Search(Microsoft::Console::Render::IRenderData& renderData,
|
||||
const std::wstring_view str,
|
||||
const Direction direction,
|
||||
const Sensitivity sensitivity) :
|
||||
_direction(direction),
|
||||
_sensitivity(sensitivity),
|
||||
_needle(s_CreateNeedleFromString(str)),
|
||||
_uiaData(uiaData),
|
||||
_coordAnchor(s_GetInitialAnchor(uiaData, direction))
|
||||
_renderData(renderData),
|
||||
_coordAnchor(s_GetInitialAnchor(renderData, direction))
|
||||
{
|
||||
_coordNext = _coordAnchor;
|
||||
}
|
||||
@@ -41,12 +41,12 @@ Search::Search(IUiaData& uiaData,
|
||||
// - Once you've found something, you can perform actions like .Select() or .Color()
|
||||
// Arguments:
|
||||
// - textBuffer - The screen text buffer to search through (the "haystack")
|
||||
// - uiaData - The IUiaData type reference, it is for providing selection methods
|
||||
// - renderData - The IRenderData type reference, it is for providing selection methods
|
||||
// - str - The search term you want to find (the "needle")
|
||||
// - direction - The direction to search (upward or downward)
|
||||
// - sensitivity - Whether or not you care about case
|
||||
// - anchor - starting search location in screenInfo
|
||||
Search::Search(IUiaData& uiaData,
|
||||
Search::Search(Microsoft::Console::Render::IRenderData& renderData,
|
||||
const std::wstring_view str,
|
||||
const Direction direction,
|
||||
const Sensitivity sensitivity,
|
||||
@@ -55,7 +55,7 @@ Search::Search(IUiaData& uiaData,
|
||||
_sensitivity(sensitivity),
|
||||
_needle(s_CreateNeedleFromString(str)),
|
||||
_coordAnchor(anchor),
|
||||
_uiaData(uiaData)
|
||||
_renderData(renderData)
|
||||
{
|
||||
_coordNext = _coordAnchor;
|
||||
}
|
||||
@@ -99,10 +99,10 @@ void Search::Select() const
|
||||
{
|
||||
// Convert buffer selection offsets into the equivalent screen coordinates
|
||||
// required by SelectNewRegion, taking line renditions into account.
|
||||
const auto& textBuffer = _uiaData.GetTextBuffer();
|
||||
const auto& textBuffer = _renderData.GetTextBuffer();
|
||||
const auto selStart = textBuffer.BufferToScreenPosition(_coordSelStart);
|
||||
const auto selEnd = textBuffer.BufferToScreenPosition(_coordSelEnd);
|
||||
_uiaData.SelectNewRegion(selStart, selEnd);
|
||||
_renderData.SelectNewRegion(selStart, selEnd);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -114,7 +114,7 @@ void Search::Color(const TextAttribute attr) const
|
||||
// Only select if we've found something.
|
||||
if (_coordSelEnd >= _coordSelStart)
|
||||
{
|
||||
_uiaData.ColorSelection(_coordSelStart, _coordSelEnd, attr);
|
||||
_renderData.ColorSelection(_coordSelStart, _coordSelEnd, attr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,19 +135,19 @@ std::pair<til::point, til::point> Search::GetFoundLocation() const noexcept
|
||||
// - If the screen buffer given already has a selection in it, it will be used to determine the anchor.
|
||||
// - Otherwise, we will choose one of the ends of the screen buffer depending on direction.
|
||||
// Arguments:
|
||||
// - uiaData - The reference to the IUiaData interface type object
|
||||
// - renderData - The reference to the IRenderData interface type object
|
||||
// - direction - The intended direction of the search
|
||||
// Return Value:
|
||||
// - Coordinate to start the search from.
|
||||
til::point Search::s_GetInitialAnchor(const IUiaData& uiaData, const Direction direction)
|
||||
til::point Search::s_GetInitialAnchor(const Microsoft::Console::Render::IRenderData& renderData, const Direction direction)
|
||||
{
|
||||
const auto& textBuffer = uiaData.GetTextBuffer();
|
||||
const auto textBufferEndPosition = uiaData.GetTextBufferEndPosition();
|
||||
if (uiaData.IsSelectionActive())
|
||||
const auto& textBuffer = renderData.GetTextBuffer();
|
||||
const auto textBufferEndPosition = renderData.GetTextBufferEndPosition();
|
||||
if (renderData.IsSelectionActive())
|
||||
{
|
||||
// Convert the screen position of the selection anchor into an equivalent
|
||||
// buffer position to start searching, taking line rendition into account.
|
||||
auto anchor = textBuffer.ScreenToBufferPosition(uiaData.GetSelectionAnchor());
|
||||
auto anchor = textBuffer.ScreenToBufferPosition(renderData.GetSelectionAnchor());
|
||||
|
||||
if (direction == Direction::Forward)
|
||||
{
|
||||
@@ -158,8 +158,8 @@ til::point Search::s_GetInitialAnchor(const IUiaData& uiaData, const Direction d
|
||||
textBuffer.GetSize().DecrementInBoundsCircular(anchor);
|
||||
// If the selection starts at (0, 0), we need to make sure
|
||||
// it does not exceed the text buffer end position
|
||||
anchor.X = std::min(textBufferEndPosition.X, anchor.X);
|
||||
anchor.Y = std::min(textBufferEndPosition.Y, anchor.Y);
|
||||
anchor.x = std::min(textBufferEndPosition.x, anchor.x);
|
||||
anchor.y = std::min(textBufferEndPosition.y, anchor.y);
|
||||
}
|
||||
return anchor;
|
||||
}
|
||||
@@ -196,7 +196,7 @@ bool Search::_FindNeedleInHaystackAt(const til::point pos, til::point& start, ti
|
||||
for (const auto& needleChars : _needle)
|
||||
{
|
||||
// Haystack is the buffer. Needle is the string we were given.
|
||||
const auto hayIter = _uiaData.GetTextBuffer().GetTextDataAt(bufferPos);
|
||||
const auto hayIter = _renderData.GetTextBuffer().GetTextDataAt(bufferPos);
|
||||
const auto hayChars = *hayIter;
|
||||
|
||||
// If we didn't match at any point of the needle, return false.
|
||||
@@ -269,7 +269,7 @@ wchar_t Search::_ApplySensitivity(const wchar_t wch) const noexcept
|
||||
// - coord - Updated by function to increment one position (will wrap X and Y direction)
|
||||
void Search::_IncrementCoord(til::point& coord) const noexcept
|
||||
{
|
||||
_uiaData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord);
|
||||
_renderData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -278,7 +278,7 @@ void Search::_IncrementCoord(til::point& coord) const noexcept
|
||||
// - coord - Updated by function to decrement one position (will wrap X and Y direction)
|
||||
void Search::_DecrementCoord(til::point& coord) const noexcept
|
||||
{
|
||||
_uiaData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord);
|
||||
_renderData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -305,10 +305,10 @@ void Search::_UpdateNextPosition()
|
||||
// We put the next position to:
|
||||
// Forward: (0, 0)
|
||||
// Backward: the position of the end of the text buffer
|
||||
const auto bufferEndPosition = _uiaData.GetTextBufferEndPosition();
|
||||
const auto bufferEndPosition = _renderData.GetTextBufferEndPosition();
|
||||
|
||||
if (_coordNext.Y > bufferEndPosition.Y ||
|
||||
(_coordNext.Y == bufferEndPosition.Y && _coordNext.X > bufferEndPosition.X))
|
||||
if (_coordNext.y > bufferEndPosition.y ||
|
||||
(_coordNext.y == bufferEndPosition.y && _coordNext.x > bufferEndPosition.x))
|
||||
{
|
||||
if (_direction == Direction::Forward)
|
||||
{
|
||||
|
||||
@@ -17,10 +17,9 @@ Revision History:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <WinConTypes.h>
|
||||
#include "TextAttribute.hpp"
|
||||
#include "textBuffer.hpp"
|
||||
#include "../types/IUiaData.h"
|
||||
#include "../renderer/inc/IRenderData.hpp"
|
||||
|
||||
// This used to be in find.h.
|
||||
#define SEARCH_STRING_LENGTH (80)
|
||||
@@ -40,12 +39,12 @@ public:
|
||||
CaseSensitive
|
||||
};
|
||||
|
||||
Search(Microsoft::Console::Types::IUiaData& uiaData,
|
||||
Search(Microsoft::Console::Render::IRenderData& renderData,
|
||||
const std::wstring_view str,
|
||||
const Direction dir,
|
||||
const Sensitivity sensitivity);
|
||||
|
||||
Search(Microsoft::Console::Types::IUiaData& uiaData,
|
||||
Search(Microsoft::Console::Render::IRenderData& renderData,
|
||||
const std::wstring_view str,
|
||||
const Direction dir,
|
||||
const Sensitivity sensitivity,
|
||||
@@ -66,7 +65,7 @@ private:
|
||||
void _IncrementCoord(til::point& coord) const noexcept;
|
||||
void _DecrementCoord(til::point& coord) const noexcept;
|
||||
|
||||
static til::point s_GetInitialAnchor(const Microsoft::Console::Types::IUiaData& uiaData, const Direction dir);
|
||||
static til::point s_GetInitialAnchor(const Microsoft::Console::Render::IRenderData& renderData, const Direction dir);
|
||||
|
||||
static std::vector<std::wstring> s_CreateNeedleFromString(const std::wstring_view wstr);
|
||||
|
||||
@@ -79,7 +78,7 @@ private:
|
||||
const std::vector<std::wstring> _needle;
|
||||
const Direction _direction;
|
||||
const Sensitivity _sensitivity;
|
||||
Microsoft::Console::Types::IUiaData& _uiaData;
|
||||
Microsoft::Console::Render::IRenderData& _renderData;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class SearchTests;
|
||||
|
||||
@@ -13,75 +13,6 @@
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct BufferAllocator
|
||||
{
|
||||
BufferAllocator(til::size sz)
|
||||
{
|
||||
const auto w = gsl::narrow<uint16_t>(sz.width);
|
||||
const auto h = gsl::narrow<uint16_t>(sz.height);
|
||||
|
||||
const auto charsBytes = w * sizeof(wchar_t);
|
||||
// The ROW::_indices array stores 1 more item than the buffer is wide.
|
||||
// That extra column stores the past-the-end _chars pointer.
|
||||
const auto indicesBytes = w * sizeof(uint16_t) + sizeof(uint16_t);
|
||||
const auto rowStride = charsBytes + indicesBytes;
|
||||
// 65535*65535 cells would result in a charsAreaSize of 8GiB.
|
||||
// --> Use uint64_t so that we can safely do our calculations even on x86.
|
||||
const auto allocSize = gsl::narrow<size_t>(::base::strict_cast<uint64_t>(rowStride) * ::base::strict_cast<uint64_t>(h));
|
||||
|
||||
_buffer = wil::unique_virtualalloc_ptr<std::byte>{ static_cast<std::byte*>(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) };
|
||||
THROW_IF_NULL_ALLOC(_buffer);
|
||||
|
||||
_data = std::span{ _buffer.get(), allocSize }.begin();
|
||||
_rowStride = rowStride;
|
||||
_indicesOffset = charsBytes;
|
||||
_width = w;
|
||||
_height = h;
|
||||
}
|
||||
|
||||
BufferAllocator& operator++() noexcept
|
||||
{
|
||||
_data += _rowStride;
|
||||
return *this;
|
||||
}
|
||||
|
||||
wchar_t* chars() const noexcept
|
||||
{
|
||||
return til::bit_cast<wchar_t*>(&*_data);
|
||||
}
|
||||
|
||||
uint16_t* indices() const noexcept
|
||||
{
|
||||
return til::bit_cast<uint16_t*>(&*(_data + _indicesOffset));
|
||||
}
|
||||
|
||||
uint16_t width() const noexcept
|
||||
{
|
||||
return _width;
|
||||
}
|
||||
|
||||
uint16_t height() const noexcept
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
wil::unique_virtualalloc_ptr<std::byte>&& take() noexcept
|
||||
{
|
||||
return std::move(_buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
wil::unique_virtualalloc_ptr<std::byte> _buffer;
|
||||
std::span<std::byte>::iterator _data;
|
||||
size_t _rowStride;
|
||||
size_t _indicesOffset;
|
||||
uint16_t _width;
|
||||
uint16_t _height;
|
||||
};
|
||||
}
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
@@ -98,7 +29,7 @@ using PointTree = interval_tree::IntervalTree<til::point, size_t>;
|
||||
// Return Value:
|
||||
// - constructed object
|
||||
// Note: may throw exception
|
||||
TextBuffer::TextBuffer(const til::size screenBufferSize,
|
||||
TextBuffer::TextBuffer(til::size screenBufferSize,
|
||||
const TextAttribute defaultAttributes,
|
||||
const UINT cursorSize,
|
||||
const bool isActiveBuffer,
|
||||
@@ -108,15 +39,10 @@ TextBuffer::TextBuffer(const til::size screenBufferSize,
|
||||
_cursor{ cursorSize, *this },
|
||||
_isActiveBuffer{ isActiveBuffer }
|
||||
{
|
||||
BufferAllocator allocator{ screenBufferSize };
|
||||
|
||||
_storage.reserve(allocator.height());
|
||||
for (til::CoordType i = 0; i < screenBufferSize.Y; ++i, ++allocator)
|
||||
{
|
||||
_storage.emplace_back(allocator.chars(), allocator.indices(), allocator.width(), _currentAttributes);
|
||||
}
|
||||
|
||||
_charBuffer = allocator.take();
|
||||
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
|
||||
screenBufferSize.width = std::max(screenBufferSize.width, 1);
|
||||
screenBufferSize.height = std::max(screenBufferSize.height, 1);
|
||||
_charBuffer = _allocateBuffer(screenBufferSize, _currentAttributes, _storage);
|
||||
_UpdateSize();
|
||||
}
|
||||
|
||||
@@ -215,10 +141,10 @@ TextBufferTextIterator TextBuffer::GetTextLineDataAt(const til::point at) const
|
||||
TextBufferCellIterator TextBuffer::GetCellLineDataAt(const til::point at) const
|
||||
{
|
||||
til::inclusive_rect limit;
|
||||
limit.Top = at.Y;
|
||||
limit.Bottom = at.Y;
|
||||
limit.Left = 0;
|
||||
limit.Right = GetSize().RightInclusive();
|
||||
limit.top = at.y;
|
||||
limit.bottom = at.y;
|
||||
limit.left = 0;
|
||||
limit.right = GetSize().RightInclusive();
|
||||
|
||||
return TextBufferCellIterator(*this, at, Viewport::FromInclusive(limit));
|
||||
}
|
||||
@@ -262,11 +188,11 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
|
||||
{
|
||||
// To figure out if the sequence is valid, we have to look at the character that comes before the current one
|
||||
const auto coordPrevPosition = _GetPreviousFromCursor();
|
||||
auto& prevRow = GetRowByOffset(coordPrevPosition.Y);
|
||||
auto& prevRow = GetRowByOffset(coordPrevPosition.y);
|
||||
DbcsAttribute prevDbcsAttr = DbcsAttribute::Single;
|
||||
try
|
||||
{
|
||||
prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.X);
|
||||
prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.x);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -319,7 +245,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
|
||||
// Erase previous character into an N type.
|
||||
try
|
||||
{
|
||||
prevRow.ClearCell(coordPrevPosition.X);
|
||||
prevRow.ClearCell(coordPrevPosition.x);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -346,23 +272,19 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
|
||||
// - 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.
|
||||
|
||||
auto 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 == DbcsAttribute::Leading)
|
||||
{
|
||||
const auto cursorPosition = GetCursor().GetPosition();
|
||||
const auto lineWidth = GetLineWidth(cursorPosition.Y);
|
||||
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)
|
||||
if (cursorPosition.x == lineWidth - 1)
|
||||
{
|
||||
// set that we're wrapping for double byte reasons
|
||||
auto& row = GetRowByOffset(cursorPosition.Y);
|
||||
auto& row = GetRowByOffset(cursorPosition.y);
|
||||
row.SetDoubleBytePadded(true);
|
||||
|
||||
// then move the cursor forward and onto the next row
|
||||
@@ -372,6 +294,32 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
void TextBuffer::ConsumeGrapheme(std::wstring_view& chars) noexcept
|
||||
{
|
||||
// This function is supposed to mirror the behavior of ROW::Write, when it reads characters off of `chars`.
|
||||
// (I know that a UTF-16 code point is not a grapheme, but that's what we're working towards.)
|
||||
chars = til::utf16_pop(chars);
|
||||
}
|
||||
|
||||
// This function is intended for writing regular "lines" of text and only the `state.text` and`state.columnBegin`
|
||||
// fields are being used, whereas `state.columnLimit` is automatically overwritten by the line width of the given row.
|
||||
// This allows this function to automatically set the wrap-forced field of the row, which is also the return value.
|
||||
// The return value indicates to the caller whether the cursor should be moved to the next line.
|
||||
void TextBuffer::WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribute& attributes, RowWriteState& state)
|
||||
{
|
||||
auto& r = GetRowByOffset(row);
|
||||
|
||||
r.ReplaceText(state);
|
||||
r.ReplaceAttributes(state.columnBegin, state.columnEnd, attributes);
|
||||
|
||||
if (state.columnEnd >= state.columnLimit)
|
||||
{
|
||||
r.SetWrapForced(wrapAtEOL);
|
||||
}
|
||||
|
||||
TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, state.columnEndDirty, row + 1 }));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer. Writes at the cursor.
|
||||
// Arguments:
|
||||
@@ -417,8 +365,8 @@ OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt,
|
||||
it = WriteLine(it, lineTarget, wrap);
|
||||
|
||||
// Move to the next line down.
|
||||
lineTarget.X = 0;
|
||||
++lineTarget.Y;
|
||||
lineTarget.x = 0;
|
||||
++lineTarget.y;
|
||||
}
|
||||
|
||||
return it;
|
||||
@@ -445,8 +393,8 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
|
||||
}
|
||||
|
||||
// Get the row and write the cells
|
||||
auto& row = GetRowByOffset(target.Y);
|
||||
const auto newIt = row.WriteCells(givenIt, target.X, wrap, limitRight);
|
||||
auto& 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);
|
||||
@@ -475,8 +423,8 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars,
|
||||
if (fSuccess)
|
||||
{
|
||||
// Get the current cursor position
|
||||
const auto iRow = GetCursor().GetPosition().Y; // row stored as logical position, not array position
|
||||
const auto iCol = GetCursor().GetPosition().X; // column logical and array positions are equal.
|
||||
const auto iRow = GetCursor().GetPosition().y; // row stored as logical position, not array position
|
||||
const auto iCol = GetCursor().GetPosition().x; // column logical and array positions are equal.
|
||||
|
||||
// Get the row associated with the given logical position
|
||||
auto& Row = GetRowByOffset(iRow);
|
||||
@@ -550,7 +498,7 @@ void TextBuffer::_SetWrapOnCurrentRow() noexcept
|
||||
void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet) noexcept
|
||||
{
|
||||
// The vertical position of the cursor represents the current row we're manipulating.
|
||||
const auto uiCurrentRowOffset = GetCursor().GetPosition().Y;
|
||||
const auto uiCurrentRowOffset = GetCursor().GetPosition().y;
|
||||
|
||||
// Set the wrap status as appropriate
|
||||
GetRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet);
|
||||
@@ -569,14 +517,14 @@ bool TextBuffer::IncrementCursor()
|
||||
// Cursor position is stored as logical array indices (starts at 0) for the window
|
||||
// Buffer Size is specified as the "length" of the array. It would say 80 for valid values of 0-79.
|
||||
// So subtract 1 from buffer size in each direction to find the index of the final column in the buffer
|
||||
const auto iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().Y) - 1;
|
||||
const auto iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().y) - 1;
|
||||
|
||||
// Move the cursor one position to the right
|
||||
GetCursor().IncrementXPosition(1);
|
||||
|
||||
auto fSuccess = true;
|
||||
// If we've passed the final valid column...
|
||||
if (GetCursor().GetPosition().X > iFinalColumnIndex)
|
||||
if (GetCursor().GetPosition().x > iFinalColumnIndex)
|
||||
{
|
||||
// Then mark that we've been forced to wrap
|
||||
_SetWrapOnCurrentRow();
|
||||
@@ -603,7 +551,7 @@ bool TextBuffer::NewlineCursor()
|
||||
GetCursor().IncrementYPosition(1);
|
||||
|
||||
// If we've passed the final valid row...
|
||||
if (GetCursor().GetPosition().Y > iFinalRowIndex)
|
||||
if (GetCursor().GetPosition().y > iFinalRowIndex)
|
||||
{
|
||||
// Stay on the final logical/offset row of the buffer.
|
||||
GetCursor().SetYPosition(iFinalRowIndex);
|
||||
@@ -677,28 +625,28 @@ til::point TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::C
|
||||
|
||||
til::point coordEndOfText;
|
||||
// Search the given viewport by starting at the bottom.
|
||||
coordEndOfText.Y = viewport.BottomInclusive();
|
||||
coordEndOfText.y = viewport.BottomInclusive();
|
||||
|
||||
const auto& currRow = GetRowByOffset(coordEndOfText.Y);
|
||||
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 = currRow.MeasureRight() - 1;
|
||||
coordEndOfText.x = 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();
|
||||
auto fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop); // this row is empty, and we're not at the top
|
||||
auto fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop); // this row is empty, and we're not at the top
|
||||
while (fDoBackUp)
|
||||
{
|
||||
coordEndOfText.Y--;
|
||||
const auto& backupRow = GetRowByOffset(coordEndOfText.Y);
|
||||
coordEndOfText.y--;
|
||||
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 = backupRow.MeasureRight() - 1;
|
||||
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
|
||||
coordEndOfText.x = backupRow.MeasureRight() - 1;
|
||||
fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop);
|
||||
}
|
||||
|
||||
// don't allow negative results
|
||||
coordEndOfText.Y = std::max(coordEndOfText.Y, 0);
|
||||
coordEndOfText.X = std::max(coordEndOfText.X, 0);
|
||||
coordEndOfText.y = std::max(coordEndOfText.y, 0);
|
||||
coordEndOfText.x = std::max(coordEndOfText.x, 0);
|
||||
|
||||
return coordEndOfText;
|
||||
}
|
||||
@@ -715,20 +663,20 @@ til::point TextBuffer::_GetPreviousFromCursor() const noexcept
|
||||
auto coordPosition = GetCursor().GetPosition();
|
||||
|
||||
// If we're not at the left edge, simply move the cursor to the left by one
|
||||
if (coordPosition.X > 0)
|
||||
if (coordPosition.x > 0)
|
||||
{
|
||||
coordPosition.X--;
|
||||
coordPosition.x--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, only if we're not on the top row (e.g. we don't move anywhere in the top left corner. there is no previous)
|
||||
if (coordPosition.Y > 0)
|
||||
if (coordPosition.y > 0)
|
||||
{
|
||||
// move the cursor up one line
|
||||
coordPosition.Y--;
|
||||
coordPosition.y--;
|
||||
|
||||
// and to the right edge
|
||||
coordPosition.X = GetLineWidth(coordPosition.Y) - 1;
|
||||
coordPosition.x = GetLineWidth(coordPosition.y) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,6 +693,37 @@ const Viewport TextBuffer::GetSize() const noexcept
|
||||
return _size;
|
||||
}
|
||||
|
||||
wil::unique_virtualalloc_ptr<std::byte> TextBuffer::_allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector<ROW>& rows)
|
||||
{
|
||||
const auto w = gsl::narrow<uint16_t>(sz.width);
|
||||
const auto h = gsl::narrow<uint16_t>(sz.height);
|
||||
|
||||
const auto charsBytes = w * sizeof(wchar_t);
|
||||
// The ROW::_indices array stores 1 more item than the buffer is wide.
|
||||
// That extra column stores the past-the-end _chars pointer.
|
||||
const auto indicesBytes = w * sizeof(uint16_t) + sizeof(uint16_t);
|
||||
const auto rowStride = charsBytes + indicesBytes;
|
||||
// 65535*65535 cells would result in a charsAreaSize of 8GiB.
|
||||
// --> Use uint64_t so that we can safely do our calculations even on x86.
|
||||
const auto allocSize = gsl::narrow<size_t>(::base::strict_cast<uint64_t>(rowStride) * ::base::strict_cast<uint64_t>(h));
|
||||
|
||||
auto buffer = wil::unique_virtualalloc_ptr<std::byte>{ static_cast<std::byte*>(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) };
|
||||
THROW_IF_NULL_ALLOC(buffer);
|
||||
|
||||
auto data = std::span{ buffer.get(), allocSize }.begin();
|
||||
|
||||
rows.resize(h);
|
||||
for (auto& row : rows)
|
||||
{
|
||||
const auto chars = til::bit_cast<wchar_t*>(&*data);
|
||||
const auto indices = til::bit_cast<uint16_t*>(&*(data + charsBytes));
|
||||
row = { chars, indices, w, attributes };
|
||||
data += rowStride;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void TextBuffer::_UpdateSize()
|
||||
{
|
||||
_size = Viewport::FromDimensions({ _storage.at(0).size(), gsl::narrow<til::CoordType>(_storage.size()) });
|
||||
@@ -875,7 +854,7 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no
|
||||
void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
|
||||
{
|
||||
const auto cursorPosition = GetCursor().GetPosition();
|
||||
const auto rowIndex = cursorPosition.Y;
|
||||
const auto rowIndex = cursorPosition.y;
|
||||
auto& row = GetRowByOffset(rowIndex);
|
||||
if (row.GetLineRendition() != lineRendition)
|
||||
{
|
||||
@@ -926,22 +905,22 @@ til::CoordType TextBuffer::GetLineWidth(const til::CoordType row) const noexcept
|
||||
|
||||
til::point TextBuffer::ClampPositionWithinLine(const til::point position) const noexcept
|
||||
{
|
||||
const auto rightmostColumn = GetLineWidth(position.Y) - 1;
|
||||
return { std::min(position.X, rightmostColumn), position.Y };
|
||||
const auto rightmostColumn = GetLineWidth(position.y) - 1;
|
||||
return { std::min(position.x, rightmostColumn), position.y };
|
||||
}
|
||||
|
||||
til::point TextBuffer::ScreenToBufferPosition(const til::point position) const noexcept
|
||||
{
|
||||
// Use shift right to quickly divide the X pos by 2 for double width lines.
|
||||
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
return { position.X >> scale, position.Y };
|
||||
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
|
||||
return { position.x >> scale, position.y };
|
||||
}
|
||||
|
||||
til::point TextBuffer::BufferToScreenPosition(const til::point position) const noexcept
|
||||
{
|
||||
// Use shift left to quickly multiply the X pos by 2 for double width lines.
|
||||
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
return { position.X << scale, position.Y };
|
||||
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
|
||||
return { position.x << scale, position.y };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -963,41 +942,63 @@ void TextBuffer::Reset()
|
||||
// - newSize - new size of screen.
|
||||
// Return Value:
|
||||
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
|
||||
[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(const til::size newSize) noexcept
|
||||
[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(til::size newSize) noexcept
|
||||
{
|
||||
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
|
||||
newSize.width = std::max(newSize.width, 1);
|
||||
newSize.height = std::max(newSize.height, 1);
|
||||
|
||||
try
|
||||
{
|
||||
BufferAllocator allocator{ newSize };
|
||||
|
||||
const auto currentSize = GetSize().Dimensions();
|
||||
const auto attributes = GetCurrentAttributes();
|
||||
|
||||
til::CoordType TopRow = 0; // new top row of the screen buffer
|
||||
if (newSize.Y <= GetCursor().GetPosition().Y)
|
||||
if (newSize.height <= GetCursor().GetPosition().y)
|
||||
{
|
||||
TopRow = GetCursor().GetPosition().Y - newSize.Y + 1;
|
||||
TopRow = GetCursor().GetPosition().y - newSize.height + 1;
|
||||
}
|
||||
const auto TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y;
|
||||
const auto TopRowIndex = gsl::narrow_cast<size_t>(_firstRow + TopRow) % _storage.size();
|
||||
|
||||
std::vector<ROW> newStorage;
|
||||
auto newBuffer = _allocateBuffer(newSize, _currentAttributes, newStorage);
|
||||
|
||||
// This basically imitates a std::rotate_copy(first, mid, last), but uses ROW::CopyRangeFrom() to do the copying.
|
||||
{
|
||||
const auto first = _storage.begin();
|
||||
const auto last = _storage.end();
|
||||
const auto mid = first + TopRowIndex;
|
||||
auto dest = newStorage.begin();
|
||||
|
||||
std::span<ROW> sourceRanges[]{
|
||||
{ mid, last },
|
||||
{ first, mid },
|
||||
};
|
||||
|
||||
// Ensure we don't copy more from `_storage` than fit into `newStorage`.
|
||||
if (sourceRanges[0].size() > newStorage.size())
|
||||
{
|
||||
sourceRanges[0] = sourceRanges[0].subspan(0, newStorage.size());
|
||||
}
|
||||
if (const auto remaining = newStorage.size() - sourceRanges[0].size(); sourceRanges[1].size() > remaining)
|
||||
{
|
||||
sourceRanges[1] = sourceRanges[1].subspan(0, remaining);
|
||||
}
|
||||
|
||||
for (const auto& sourceRange : sourceRanges)
|
||||
{
|
||||
for (const auto& oldRow : sourceRange)
|
||||
{
|
||||
til::CoordType begin = 0;
|
||||
dest->CopyRangeFrom(0, til::CoordTypeMax, oldRow, begin, til::CoordTypeMax);
|
||||
dest->TransferAttributes(oldRow.Attributes(), newSize.width);
|
||||
++dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_charBuffer = std::move(newBuffer);
|
||||
_storage = std::move(newStorage);
|
||||
|
||||
// rotate rows until the top row is at index 0
|
||||
std::rotate(_storage.begin(), _storage.begin() + TopRowIndex, _storage.end());
|
||||
_SetFirstRowIndex(0);
|
||||
|
||||
// realloc in the Y direction
|
||||
// remove rows if we're shrinking
|
||||
_storage.resize(allocator.height());
|
||||
|
||||
// realloc in the X direction
|
||||
for (auto& it : _storage)
|
||||
{
|
||||
it.Resize(allocator.chars(), allocator.indices(), allocator.width(), attributes);
|
||||
++allocator;
|
||||
}
|
||||
|
||||
// Update the cached size value
|
||||
_UpdateSize();
|
||||
|
||||
_charBuffer = allocator.take();
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@@ -1088,7 +1089,7 @@ ROW& TextBuffer::_GetFirstRow() noexcept
|
||||
// - the delimiter class for the given char
|
||||
DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept
|
||||
{
|
||||
return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters);
|
||||
return GetRowByOffset(pos.y).DelimiterClassAt(pos.x, wordDelimiters);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1205,7 +1206,7 @@ til::point TextBuffer::_GetWordStartForSelection(const til::point target, const
|
||||
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
|
||||
|
||||
// expand left until we hit the left boundary or a different delimiter class
|
||||
while (result.X > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
|
||||
while (result.x > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
|
||||
{
|
||||
bufferSize.DecrementInBounds(result);
|
||||
}
|
||||
@@ -1323,7 +1324,7 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
// can't expand right
|
||||
if (target.X == bufferSize.RightInclusive())
|
||||
if (target.x == bufferSize.RightInclusive())
|
||||
{
|
||||
return target;
|
||||
}
|
||||
@@ -1332,7 +1333,7 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
|
||||
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
|
||||
|
||||
// expand right until we hit the right boundary or a different delimiter class
|
||||
while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
|
||||
while (result.x < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
|
||||
{
|
||||
bufferSize.IncrementInBounds(result);
|
||||
}
|
||||
@@ -1606,25 +1607,25 @@ const std::vector<til::inclusive_rect> TextBuffer::GetTextRects(til::point start
|
||||
std::make_tuple(start, end) :
|
||||
std::make_tuple(end, start);
|
||||
|
||||
const auto textRectSize = 1 + lowerCoord.Y - higherCoord.Y;
|
||||
const auto textRectSize = 1 + lowerCoord.y - higherCoord.y;
|
||||
textRects.reserve(textRectSize);
|
||||
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
|
||||
for (auto row = higherCoord.y; row <= lowerCoord.y; row++)
|
||||
{
|
||||
til::inclusive_rect textRow;
|
||||
|
||||
textRow.Top = row;
|
||||
textRow.Bottom = row;
|
||||
textRow.top = row;
|
||||
textRow.bottom = row;
|
||||
|
||||
if (blockSelection || higherCoord.Y == lowerCoord.Y)
|
||||
if (blockSelection || higherCoord.y == lowerCoord.y)
|
||||
{
|
||||
// set the left and right margin to the left-/right-most respectively
|
||||
textRow.Left = std::min(higherCoord.X, lowerCoord.X);
|
||||
textRow.Right = std::max(higherCoord.X, lowerCoord.X);
|
||||
textRow.left = std::min(higherCoord.x, lowerCoord.x);
|
||||
textRow.right = std::max(higherCoord.x, lowerCoord.x);
|
||||
}
|
||||
else
|
||||
{
|
||||
textRow.Left = (row == higherCoord.Y) ? higherCoord.X : bufferSize.Left();
|
||||
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
|
||||
textRow.left = (row == higherCoord.y) ? higherCoord.x : bufferSize.Left();
|
||||
textRow.right = (row == lowerCoord.y) ? lowerCoord.x : bufferSize.RightInclusive();
|
||||
}
|
||||
|
||||
// If we were passed screen coordinates, convert the given range into
|
||||
@@ -1665,8 +1666,8 @@ std::vector<til::point_span> TextBuffer::GetTextSpans(til::point start, til::poi
|
||||
|
||||
for (auto rect : rects)
|
||||
{
|
||||
const til::point first = { rect.Left, rect.Top };
|
||||
const til::point second = { rect.Right, rect.Bottom };
|
||||
const til::point first = { rect.left, rect.top };
|
||||
const til::point second = { rect.right, rect.bottom };
|
||||
textSpans.emplace_back(first, second);
|
||||
}
|
||||
}
|
||||
@@ -1687,16 +1688,16 @@ std::vector<til::point_span> TextBuffer::GetTextSpans(til::point start, til::poi
|
||||
// equivalent buffer offsets, taking line rendition into account.
|
||||
if (!bufferCoordinates)
|
||||
{
|
||||
higherCoord = ScreenToBufferLine(higherCoord, GetLineRendition(higherCoord.Y));
|
||||
lowerCoord = ScreenToBufferLine(lowerCoord, GetLineRendition(lowerCoord.Y));
|
||||
higherCoord = ScreenToBufferLine(higherCoord, GetLineRendition(higherCoord.y));
|
||||
lowerCoord = ScreenToBufferLine(lowerCoord, GetLineRendition(lowerCoord.y));
|
||||
}
|
||||
|
||||
til::inclusive_rect asRect = { higherCoord.X, higherCoord.Y, lowerCoord.X, lowerCoord.Y };
|
||||
til::inclusive_rect asRect = { higherCoord.x, higherCoord.y, lowerCoord.x, lowerCoord.y };
|
||||
_ExpandTextRow(asRect);
|
||||
higherCoord.X = asRect.Left;
|
||||
higherCoord.Y = asRect.Top;
|
||||
lowerCoord.X = asRect.Right;
|
||||
lowerCoord.Y = asRect.Bottom;
|
||||
higherCoord.x = asRect.left;
|
||||
higherCoord.y = asRect.top;
|
||||
lowerCoord.x = asRect.right;
|
||||
lowerCoord.y = asRect.bottom;
|
||||
|
||||
textSpans.emplace_back(higherCoord, lowerCoord);
|
||||
}
|
||||
@@ -1716,10 +1717,10 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
// expand left side of rect
|
||||
til::point targetPoint{ textRow.Left, textRow.Top };
|
||||
til::point targetPoint{ textRow.left, textRow.top };
|
||||
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Trailing)
|
||||
{
|
||||
if (targetPoint.X == bufferSize.Left())
|
||||
if (targetPoint.x == bufferSize.Left())
|
||||
{
|
||||
bufferSize.IncrementInBounds(targetPoint);
|
||||
}
|
||||
@@ -1727,14 +1728,14 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
|
||||
{
|
||||
bufferSize.DecrementInBounds(targetPoint);
|
||||
}
|
||||
textRow.Left = targetPoint.X;
|
||||
textRow.left = targetPoint.x;
|
||||
}
|
||||
|
||||
// expand right side of rect
|
||||
targetPoint = { textRow.Right, textRow.Bottom };
|
||||
targetPoint = { textRow.right, textRow.bottom };
|
||||
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Leading)
|
||||
{
|
||||
if (targetPoint.X == bufferSize.RightInclusive())
|
||||
if (targetPoint.x == bufferSize.RightInclusive())
|
||||
{
|
||||
bufferSize.DecrementInBounds(targetPoint);
|
||||
}
|
||||
@@ -1742,7 +1743,7 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
|
||||
{
|
||||
bufferSize.IncrementInBounds(targetPoint);
|
||||
}
|
||||
textRow.Right = targetPoint.X;
|
||||
textRow.right = targetPoint.x;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1777,7 +1778,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
|
||||
// for each row in the selection
|
||||
for (size_t i = 0; i < rows; i++)
|
||||
{
|
||||
const auto iRow = selectionRects.at(i).Top;
|
||||
const auto iRow = selectionRects.at(i).top;
|
||||
|
||||
const auto highlight = Viewport::FromInclusive(selectionRects.at(i));
|
||||
|
||||
@@ -2329,7 +2330,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
const auto cOldCursorPos = oldCursor.GetPosition();
|
||||
const auto cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
|
||||
|
||||
const auto cOldRowsTotal = cOldLastChar.Y + 1;
|
||||
const auto cOldRowsTotal = cOldLastChar.y + 1;
|
||||
|
||||
til::point cNewCursorPos;
|
||||
auto fFoundCursorPos = false;
|
||||
@@ -2348,9 +2349,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// If we're starting a new row, try and preserve the line rendition
|
||||
// from the row in the original buffer.
|
||||
const auto newBufferPos = newCursor.GetPosition();
|
||||
if (newBufferPos.X == 0)
|
||||
if (newBufferPos.x == 0)
|
||||
{
|
||||
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y);
|
||||
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.y);
|
||||
newRow.SetLineRendition(row.GetLineRendition());
|
||||
}
|
||||
|
||||
@@ -2385,7 +2386,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
const auto copyRight = iRight;
|
||||
for (; iOldCol < copyRight; iOldCol++)
|
||||
{
|
||||
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
|
||||
if (iOldCol == cOldCursorPos.x && iOldRow == cOldCursorPos.y)
|
||||
{
|
||||
cNewCursorPos = newCursor.GetPosition();
|
||||
fFoundCursorPos = true;
|
||||
@@ -2427,9 +2428,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// line in the new buffer, then we didn't wrap. That's fine. just
|
||||
// copy attributes from the old row till the end of the new row, and
|
||||
// move on.
|
||||
const auto newRowY = newCursor.GetPosition().Y;
|
||||
const auto newRowY = newCursor.GetPosition().y;
|
||||
auto& newRow = newBuffer.GetRowByOffset(newRowY);
|
||||
auto newAttrColumn = newCursor.GetPosition().X;
|
||||
auto newAttrColumn = newCursor.GetPosition().x;
|
||||
const auto newWidth = newBuffer.GetLineWidth(newRowY);
|
||||
// Stop when we get to the end of the buffer width, or the new position
|
||||
// for inserting an attr would be past the right of the new buffer.
|
||||
@@ -2458,7 +2459,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
{
|
||||
if (iOldRow >= positionInfo.value().get().mutableViewportTop)
|
||||
{
|
||||
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().Y;
|
||||
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().y;
|
||||
foundOldMutable = true;
|
||||
}
|
||||
}
|
||||
@@ -2467,7 +2468,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
{
|
||||
if (iOldRow >= positionInfo.value().get().visibleViewportTop)
|
||||
{
|
||||
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().Y;
|
||||
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().y;
|
||||
foundOldVisible = true;
|
||||
}
|
||||
}
|
||||
@@ -2482,7 +2483,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// only because we ran out of space.
|
||||
if (iRight < cOldColsTotal && !row.WasWrapForced())
|
||||
{
|
||||
if (!fFoundCursorPos && (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y))
|
||||
if (!fFoundCursorPos && (iRight == cOldCursorPos.x && iOldRow == cOldCursorPos.y))
|
||||
{
|
||||
cNewCursorPos = newCursor.GetPosition();
|
||||
fFoundCursorPos = true;
|
||||
@@ -2522,9 +2523,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// | |
|
||||
// ^ and the cursor is now here.
|
||||
const auto coordNewCursor = newCursor.GetPosition();
|
||||
if (coordNewCursor.X == 0 && coordNewCursor.Y > 0)
|
||||
if (coordNewCursor.x == 0 && coordNewCursor.y > 0)
|
||||
{
|
||||
if (newBuffer.GetRowByOffset(coordNewCursor.Y - 1).WasWrapForced())
|
||||
if (newBuffer.GetRowByOffset(coordNewCursor.y - 1).WasWrapForced())
|
||||
{
|
||||
hr = newBuffer.NewlineCursor() ? hr : E_OUTOFMEMORY;
|
||||
}
|
||||
@@ -2538,7 +2539,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// printable character. This is to fix the `color 2f` scenario, where you
|
||||
// change the buffer colors then resize and everything below the last
|
||||
// printable char gets reset. See GH #12567
|
||||
auto newRowY = newCursor.GetPosition().Y + 1;
|
||||
auto newRowY = newCursor.GetPosition().y + 1;
|
||||
const auto newHeight = newBuffer.GetSize().Height();
|
||||
const auto oldHeight = oldBuffer.GetSize().Height();
|
||||
for (;
|
||||
@@ -2578,13 +2579,13 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// Advance the cursor to the same offset as before
|
||||
// get the number of newlines and spaces between the old end of text and the old cursor,
|
||||
// then advance that many newlines and chars
|
||||
auto iNewlines = cOldCursorPos.Y - cOldLastChar.Y;
|
||||
const auto iIncrements = cOldCursorPos.X - cOldLastChar.X;
|
||||
auto iNewlines = cOldCursorPos.y - cOldLastChar.y;
|
||||
const auto iIncrements = cOldCursorPos.x - cOldLastChar.x;
|
||||
const auto cNewLastChar = newBuffer.GetLastNonSpaceCharacter();
|
||||
|
||||
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
|
||||
// because the cursor is already on the next line
|
||||
if (newBuffer.GetRowByOffset(cNewLastChar.Y).WasWrapForced())
|
||||
if (newBuffer.GetRowByOffset(cNewLastChar.y).WasWrapForced())
|
||||
{
|
||||
iNewlines = std::max(iNewlines - 1, 0);
|
||||
}
|
||||
@@ -2592,7 +2593,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
{
|
||||
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
|
||||
// old buffer will be one more than in this buffer, so new need one LESS.
|
||||
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).WasWrapForced())
|
||||
if (oldBuffer.GetRowByOffset(cOldLastChar.y).WasWrapForced())
|
||||
{
|
||||
iNewlines = std::max(iNewlines - 1, 0);
|
||||
}
|
||||
@@ -2636,9 +2637,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// - Adds or updates a hyperlink in our hyperlink table
|
||||
// Arguments:
|
||||
// - The hyperlink URI, the hyperlink id (could be new or old)
|
||||
void TextBuffer::AddHyperlinkToMap(std::wstring_view uri, uint16_t id)
|
||||
void TextBuffer::AddHyperlinkToMap(LinkData data, uint16_t id)
|
||||
{
|
||||
_hyperlinkMap[id] = uri;
|
||||
_hyperlinkMap[id] = data;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -2647,7 +2648,7 @@ void TextBuffer::AddHyperlinkToMap(std::wstring_view uri, uint16_t id)
|
||||
// - The hyperlink ID
|
||||
// Return Value:
|
||||
// - The URI
|
||||
std::wstring TextBuffer::GetHyperlinkUriFromId(uint16_t id) const
|
||||
const LinkData& TextBuffer::GetHyperlinkUriFromId(uint16_t id) const
|
||||
{
|
||||
return _hyperlinkMap.at(id);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,20 @@ namespace Microsoft::Console::Render
|
||||
class Renderer;
|
||||
}
|
||||
|
||||
enum class LinkType
|
||||
{
|
||||
None = 0,
|
||||
Url,
|
||||
SendInput,
|
||||
};
|
||||
|
||||
struct LinkData
|
||||
{
|
||||
std::wstring payload;
|
||||
LinkType type{ LinkType::None };
|
||||
bool IsUrl() const { return type == LinkType::Url; };
|
||||
};
|
||||
|
||||
class TextBuffer final
|
||||
{
|
||||
public:
|
||||
@@ -89,6 +103,9 @@ public:
|
||||
TextBufferTextIterator GetTextDataAt(const til::point at, const Microsoft::Console::Types::Viewport limit) const;
|
||||
|
||||
// Text insertion functions
|
||||
static void ConsumeGrapheme(std::wstring_view& chars) noexcept;
|
||||
void WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribute& attributes, RowWriteState& state);
|
||||
|
||||
OutputCellIterator Write(const OutputCellIterator givenIt);
|
||||
|
||||
OutputCellIterator Write(const OutputCellIterator givenIt,
|
||||
@@ -164,8 +181,8 @@ public:
|
||||
const std::vector<til::inclusive_rect> GetTextRects(til::point start, til::point end, bool blockSelection, bool bufferCoordinates) const;
|
||||
std::vector<til::point_span> GetTextSpans(til::point start, til::point end, bool blockSelection, bool bufferCoordinates) const;
|
||||
|
||||
void AddHyperlinkToMap(std::wstring_view uri, uint16_t id);
|
||||
std::wstring GetHyperlinkUriFromId(uint16_t id) const;
|
||||
void AddHyperlinkToMap(LinkData uri, uint16_t id);
|
||||
const LinkData& GetHyperlinkUriFromId(uint16_t id) const;
|
||||
uint16_t GetHyperlinkId(std::wstring_view uri, std::wstring_view id);
|
||||
void RemoveHyperlinkFromMap(uint16_t id) noexcept;
|
||||
std::wstring GetCustomIdFromId(uint16_t id) const;
|
||||
@@ -216,6 +233,8 @@ public:
|
||||
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const;
|
||||
|
||||
private:
|
||||
static wil::unique_virtualalloc_ptr<std::byte> _allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector<ROW>& rows);
|
||||
|
||||
void _UpdateSize();
|
||||
void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept;
|
||||
til::point _GetPreviousFromCursor() const noexcept;
|
||||
@@ -237,7 +256,7 @@ private:
|
||||
|
||||
Microsoft::Console::Render::Renderer& _renderer;
|
||||
|
||||
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
|
||||
std::unordered_map<uint16_t, LinkData> _hyperlinkMap;
|
||||
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
|
||||
uint16_t _currentHyperlinkId = 1;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, til::po
|
||||
// Throw if the coordinate is not limited to the inside of the given buffer.
|
||||
THROW_HR_IF(E_INVALIDARG, !limits.IsInBounds(pos));
|
||||
|
||||
_attrIter += pos.X;
|
||||
_attrIter += pos.x;
|
||||
|
||||
_GenerateView();
|
||||
}
|
||||
@@ -115,15 +115,15 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
|
||||
// _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.
|
||||
// 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;
|
||||
const auto oldX = _pos.x;
|
||||
const auto oldY = _pos.y;
|
||||
|
||||
// Under MSVC writing the individual members of a til::point generates worse assembly
|
||||
// compared to having them be local variables. This causes a performance impact.
|
||||
@@ -166,15 +166,15 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
|
||||
|
||||
_view.UpdateText(_pRow->GlyphAt(newX));
|
||||
_view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX));
|
||||
_pos.X = newX;
|
||||
_pos.x = newX;
|
||||
}
|
||||
else
|
||||
{
|
||||
// cold path (_GenerateView is slow)
|
||||
_pRow = s_GetRow(_buffer, { newX, newY });
|
||||
_attrIter = _pRow->AttrBegin() + newX;
|
||||
_pos.X = newX;
|
||||
_pos.Y = newY;
|
||||
_pos.x = newX;
|
||||
_pos.y = newY;
|
||||
_GenerateView();
|
||||
}
|
||||
|
||||
@@ -289,16 +289,16 @@ ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it)
|
||||
// - newPos - The new coordinate position.
|
||||
void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
|
||||
{
|
||||
if (newPos.Y != _pos.Y)
|
||||
if (newPos.y != _pos.y)
|
||||
{
|
||||
_pRow = s_GetRow(_buffer, newPos);
|
||||
_attrIter = _pRow->AttrBegin();
|
||||
_pos.X = 0;
|
||||
_pos.x = 0;
|
||||
}
|
||||
|
||||
if (newPos.X != _pos.X)
|
||||
if (newPos.x != _pos.x)
|
||||
{
|
||||
const auto diff = gsl::narrow_cast<ptrdiff_t>(newPos.X) - gsl::narrow_cast<ptrdiff_t>(_pos.X);
|
||||
const auto diff = gsl::narrow_cast<ptrdiff_t>(newPos.x) - gsl::narrow_cast<ptrdiff_t>(_pos.x);
|
||||
_attrIter += diff;
|
||||
}
|
||||
|
||||
@@ -317,15 +317,15 @@ void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
|
||||
// - Pointer to the underlying CharRow structure
|
||||
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept
|
||||
{
|
||||
return &buffer.GetRowByOffset(pos.Y);
|
||||
return &buffer.GetRowByOffset(pos.y);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Updates the internal view. Call after updating row, attribute, or positions.
|
||||
void TextBufferCellIterator::_GenerateView() noexcept
|
||||
{
|
||||
_view = OutputCellView(_pRow->GlyphAt(_pos.X),
|
||||
_pRow->DbcsAttrAt(_pos.X),
|
||||
_view = OutputCellView(_pRow->GlyphAt(_pos.x),
|
||||
_pRow->DbcsAttrAt(_pos.x),
|
||||
*_attrIter,
|
||||
TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
||||
@@ -830,7 +830,7 @@ class ReflowTests
|
||||
for (size_t bufferIndex{ 1 }; bufferIndex < testCase.buffers.size(); ++bufferIndex)
|
||||
{
|
||||
const auto& testBuffer{ til::at(testCase.buffers, bufferIndex) };
|
||||
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.X, testBuffer.size.Y));
|
||||
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.width, testBuffer.size.height));
|
||||
|
||||
auto newBuffer{ _textBufferByReflowingTextBuffer(*textBuffer, testBuffer.size) };
|
||||
|
||||
|
||||
@@ -153,6 +153,8 @@
|
||||
<ItemGroup>
|
||||
<FrameworkSdkReference Remove="@(FrameworkSdkReference)" Condition="'%(FrameworkSdkReference.SimpleName)'=='Microsoft.VCLibs'" />
|
||||
<FrameworkSdkPackage Remove="@(FrameworkSdkPackage)" Condition="'%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00' or '%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.Debug'" />
|
||||
<FrameworkSdkReference Remove="@(FrameworkSdkReference)" Condition="'%(FrameworkSdkReference.SimpleName)'=='Microsoft.VCLibs.Desktop'" />
|
||||
<FrameworkSdkPackage Remove="@(FrameworkSdkPackage)" Condition="'%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.UWPDesktop' or '%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.Debug.UWPDesktop'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
@@ -161,6 +163,8 @@
|
||||
<ItemGroup>
|
||||
<FrameworkSdkReference Remove="@(FrameworkSdkReference)" Condition="'%(FrameworkSdkReference.SimpleName)'=='Microsoft.VCLibs'" />
|
||||
<FrameworkSdkPackage Remove="@(FrameworkSdkPackage)" Condition="'%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00' or '%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.Debug'" />
|
||||
<FrameworkSdkReference Remove="@(FrameworkSdkReference)" Condition="'%(FrameworkSdkReference.SimpleName)'=='Microsoft.VCLibs.Desktop'" />
|
||||
<FrameworkSdkPackage Remove="@(FrameworkSdkPackage)" Condition="'%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.UWPDesktop' or '%(FrameworkSdkPackage.Name)'=='Microsoft.VCLibs.140.00.Debug.UWPDesktop'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
|
||||
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
|
||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
|
||||
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
||||
IgnorableNamespaces="uap mp rescap uap3">
|
||||
IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization">
|
||||
|
||||
<Identity
|
||||
Name="WindowsTerminalDev"
|
||||
@@ -23,6 +25,14 @@
|
||||
<DisplayName>ms-resource:AppStoreNameDev</DisplayName>
|
||||
<PublisherDisplayName>A Lone Developer</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
<!-- Older versions of Windows 10 respect this -->
|
||||
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
|
||||
<!-- Newer versions of Windows 10 plus all versions of Windows 11 respect this -->
|
||||
<virtualization:RegistryWriteVirtualization>
|
||||
<virtualization:ExcludedKeys>
|
||||
<virtualization:ExcludedKey>HKEY_CURRENT_USER\Console\%%Startup</virtualization:ExcludedKey>
|
||||
</virtualization:ExcludedKeys>
|
||||
</virtualization:RegistryWriteVirtualization>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
@@ -136,5 +146,6 @@
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="unvirtualizedResources" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
|
||||
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
|
||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap mp rescap uap3">
|
||||
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
|
||||
IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization">
|
||||
|
||||
<Identity
|
||||
Name="Microsoft.WindowsTerminalPreview"
|
||||
@@ -24,6 +26,14 @@
|
||||
<DisplayName>ms-resource:AppStoreNamePre</DisplayName>
|
||||
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
<!-- Older versions of Windows 10 respect this -->
|
||||
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
|
||||
<!-- Newer versions of Windows 10 plus all versions of Windows 11 respect this -->
|
||||
<virtualization:RegistryWriteVirtualization>
|
||||
<virtualization:ExcludedKeys>
|
||||
<virtualization:ExcludedKey>HKEY_CURRENT_USER\Console\%%Startup</virtualization:ExcludedKey>
|
||||
</virtualization:ExcludedKeys>
|
||||
</virtualization:RegistryWriteVirtualization>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
@@ -225,6 +235,7 @@
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="unvirtualizedResources" />
|
||||
</Capabilities>
|
||||
|
||||
<Extensions>
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
|
||||
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
|
||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap mp rescap uap3">
|
||||
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
|
||||
IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization">
|
||||
|
||||
<Identity
|
||||
Name="Microsoft.WindowsTerminal"
|
||||
@@ -24,6 +26,14 @@
|
||||
<DisplayName>ms-resource:AppStoreName</DisplayName>
|
||||
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
<!-- Older versions of Windows 10 respect this -->
|
||||
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
|
||||
<!-- Newer versions of Windows 10 plus all versions of Windows 11 respect this -->
|
||||
<virtualization:RegistryWriteVirtualization>
|
||||
<virtualization:ExcludedKeys>
|
||||
<virtualization:ExcludedKey>HKEY_CURRENT_USER\Console\%%Startup</virtualization:ExcludedKey>
|
||||
</virtualization:ExcludedKeys>
|
||||
</virtualization:RegistryWriteVirtualization>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
@@ -225,6 +235,7 @@
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="unvirtualizedResources" />
|
||||
</Capabilities>
|
||||
|
||||
<Extensions>
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
#include <wil/stl.h>
|
||||
#include <wil/resource.h>
|
||||
#include <wil/win32_helpers.h>
|
||||
#include <gsl/gsl_util>
|
||||
#include <gsl/pointers>
|
||||
#include <shellapi.h>
|
||||
#include <appmodel.h>
|
||||
|
||||
// BODGY
|
||||
//
|
||||
@@ -25,18 +28,68 @@
|
||||
// process can successfully elevate.
|
||||
|
||||
#pragma warning(suppress : 26461) // we can't change the signature of wWinMain
|
||||
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR pCmdLine, int)
|
||||
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
|
||||
{
|
||||
// All of the args passed to us (something like `new-tab -p {guid}`) are in
|
||||
// pCmdLine
|
||||
// This will invoke an elevated terminal in two possible ways. See GH#14501
|
||||
// In both scenarios, it passes the entire cmdline as-is to the new process.
|
||||
//
|
||||
// #1 discover and invoke the app using the GetCurrentApplicationUserModelId
|
||||
// api using shell:AppsFolder\package!appid
|
||||
// cmd: shell:AppsFolder\WindowsTerminalDev_8wekyb3d8bbwe!App
|
||||
// params: new-tab -p {guid}
|
||||
//
|
||||
// #2 find and execute WindowsTerminal.exe
|
||||
// cmd: {same path as this binary}\WindowsTerminal.exe
|
||||
// params: new-tab -p {guid}
|
||||
|
||||
// Get the path to WindowsTerminal.exe, which should live next to us.
|
||||
std::filesystem::path module{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
|
||||
// Swap elevate-shim.exe for WindowsTerminal.exe
|
||||
module.replace_filename(L"WindowsTerminal.exe");
|
||||
// see if we're a store app we can invoke with shell:AppsFolder
|
||||
std::wstring appUserModelId;
|
||||
const auto result = wil::AdaptFixedSizeToAllocatedResult<std::wstring, APPLICATION_USER_MODEL_ID_MAX_LENGTH>(
|
||||
appUserModelId, [&](PWSTR value, size_t valueLength, gsl::not_null<size_t*> valueLengthNeededWithNull) noexcept -> HRESULT {
|
||||
UINT32 length = gsl::narrow_cast<UINT32>(valueLength);
|
||||
const LONG rc = GetCurrentApplicationUserModelId(&length, value);
|
||||
switch (rc)
|
||||
{
|
||||
case ERROR_SUCCESS:
|
||||
*valueLengthNeededWithNull = length;
|
||||
return S_OK;
|
||||
|
||||
case ERROR_INSUFFICIENT_BUFFER:
|
||||
*valueLengthNeededWithNull = length;
|
||||
return S_FALSE; // trigger allocation loop
|
||||
|
||||
case APPMODEL_ERROR_NO_APPLICATION:
|
||||
return E_FAIL; // we are not running as a store app
|
||||
|
||||
default:
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
});
|
||||
LOG_IF_FAILED(result);
|
||||
|
||||
std::wstring cmd = {};
|
||||
if (result == S_OK && appUserModelId.length() > 0)
|
||||
{
|
||||
// scenario #1
|
||||
cmd = L"shell:AppsFolder\\" + appUserModelId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// scenario #2
|
||||
// Get the path to WindowsTerminal.exe, which should live next to us.
|
||||
std::filesystem::path module{
|
||||
wil::GetModuleFileNameW<std::wstring>(nullptr)
|
||||
};
|
||||
// Swap elevate-shim.exe for WindowsTerminal.exe
|
||||
module.replace_filename(L"WindowsTerminal.exe");
|
||||
cmd = module;
|
||||
}
|
||||
|
||||
// Go!
|
||||
|
||||
// The cmdline argument passed to WinMain is stripping the first argument.
|
||||
// Using GetCommandLine() instead for lParameters
|
||||
|
||||
// disable warnings from SHELLEXECUTEINFOW struct. We can't fix that.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26476) // Macro uses naked union over variant.
|
||||
@@ -46,8 +99,10 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR pCmdLine, int)
|
||||
seInfo.cbSize = sizeof(seInfo);
|
||||
seInfo.fMask = SEE_MASK_DEFAULT;
|
||||
seInfo.lpVerb = L"runas"; // This asks the shell to elevate the process
|
||||
seInfo.lpFile = module.c_str(); // This is `...\WindowsTerminal.exe`
|
||||
seInfo.lpParameters = pCmdLine; // This is `new-tab -p {guid}`
|
||||
seInfo.lpFile = cmd.c_str(); // This is `shell:AppsFolder\...` or `...\WindowsTerminal.exe`
|
||||
seInfo.lpParameters = GetCommandLine(); // This is `new-tab -p {guid}`
|
||||
seInfo.nShow = SW_SHOWNORMAL;
|
||||
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(til::color(0xFF, 0xFF, 0xFF, 255), til::color{ scheme->CursorColor() });
|
||||
|
||||
std::array<COLORREF, COLOR_TABLE_SIZE> expectedCampbellTable;
|
||||
const auto campbellSpan = gsl::make_span(expectedCampbellTable);
|
||||
const auto campbellSpan = std::span{ expectedCampbellTable };
|
||||
Utils::InitializeColorTable(campbellSpan);
|
||||
|
||||
for (size_t i = 0; i < expectedCampbellTable.size(); i++)
|
||||
@@ -217,6 +217,30 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
"name": "Different reference",
|
||||
"colorScheme": "One Half Dark"
|
||||
},
|
||||
{
|
||||
"name": "rename neither",
|
||||
"colorScheme":
|
||||
{
|
||||
"dark": "One Half Dark",
|
||||
"light": "One Half Light"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "rename only light",
|
||||
"colorScheme":
|
||||
{
|
||||
"dark": "One Half Dark",
|
||||
"light": "Campbell"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "rename only dark",
|
||||
"colorScheme":
|
||||
{
|
||||
"dark": "Campbell",
|
||||
"light": "One Half Light"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -289,6 +313,28 @@ namespace SettingsModelLocalTests
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#DCDFE4",
|
||||
"yellow": "#E5C07B"
|
||||
},
|
||||
{
|
||||
"name": "One Half Light",
|
||||
"foreground": "#383A42",
|
||||
"background": "#FAFAFA",
|
||||
"cursorColor": "#4F525D",
|
||||
"black": "#383A42",
|
||||
"red": "#E45649",
|
||||
"green": "#50A14F",
|
||||
"yellow": "#C18301",
|
||||
"blue": "#0184BC",
|
||||
"purple": "#A626A4",
|
||||
"cyan": "#0997B3",
|
||||
"white": "#FAFAFA",
|
||||
"brightBlack": "#4F525D",
|
||||
"brightRed": "#DF6C75",
|
||||
"brightGreen": "#98C379",
|
||||
"brightYellow": "#E4C07A",
|
||||
"brightBlue": "#61AFEF",
|
||||
"brightPurple": "#C577DD",
|
||||
"brightCyan": "#56B5C1",
|
||||
"brightWhite": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
})json" };
|
||||
@@ -296,31 +342,63 @@ namespace SettingsModelLocalTests
|
||||
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString) };
|
||||
|
||||
const auto newName{ L"Campbell (renamed)" };
|
||||
|
||||
settings->UpdateColorSchemeReferences(L"Campbell", newName);
|
||||
|
||||
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasDarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasLightColorSchemeName());
|
||||
|
||||
const auto& profiles{ settings->AllProfiles() };
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(0) };
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(1) };
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(2) };
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_FALSE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_IS_FALSE(prof.DefaultAppearance().HasDarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
|
||||
VERIFY_IS_FALSE(prof.DefaultAppearance().HasLightColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(3) };
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(4) };
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"One Half Light", prof.DefaultAppearance().LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(5) };
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(6) };
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"One Half Light", prof.DefaultAppearance().LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,7 +744,8 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size());
|
||||
for (const auto& profile : settings->AllProfiles())
|
||||
{
|
||||
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().LightColorSchemeName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -959,6 +960,10 @@ namespace SettingsModelLocalTests
|
||||
},
|
||||
{
|
||||
"name": "profile3",
|
||||
"closeOnExit": "automatic"
|
||||
},
|
||||
{
|
||||
"name": "profile4",
|
||||
"closeOnExit": null
|
||||
}
|
||||
]
|
||||
@@ -968,9 +973,10 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(0).CloseOnExit());
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Always, settings->AllProfiles().GetAt(1).CloseOnExit());
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings->AllProfiles().GetAt(2).CloseOnExit());
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Automatic, settings->AllProfiles().GetAt(3).CloseOnExit());
|
||||
|
||||
// Unknown modes parse as "Graceful"
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(3).CloseOnExit());
|
||||
// Unknown modes parse as "Automatic"
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Automatic, settings->AllProfiles().GetAt(4).CloseOnExit());
|
||||
}
|
||||
|
||||
void DeserializationTests::TestCloseOnExitCompatibilityShim()
|
||||
@@ -1524,7 +1530,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size());
|
||||
// Because the "parent" command didn't have a name, it couldn't be
|
||||
// placed into the list of commands. It and it's children are just
|
||||
// placed into the list of commands. It and its children are just
|
||||
// ignored.
|
||||
VERIFY_ARE_EQUAL(0u, settings->ActionMap().NameMap().Size());
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class JsonTestClass
|
||||
public:
|
||||
static Json::Value VerifyParseSucceeded(const std::string_view& content)
|
||||
{
|
||||
static const std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
static const std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder{}.newCharReader() };
|
||||
|
||||
Json::Value root;
|
||||
std::string errs;
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
|
||||
static std::string toString(const Json::Value& json)
|
||||
{
|
||||
static const std::unique_ptr<Json::StreamWriter> writer{ Json::StreamWriterBuilder::StreamWriterBuilder().newStreamWriter() };
|
||||
static const std::unique_ptr<Json::StreamWriter> writer{ Json::StreamWriterBuilder{}.newStreamWriter() };
|
||||
|
||||
std::stringstream s;
|
||||
writer->write(json, &s);
|
||||
|
||||
107
src/cascadia/LocalTests_SettingsModel/NewTabMenuTests.cpp
Normal file
107
src/cascadia/LocalTests_SettingsModel/NewTabMenuTests.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalSettingsModel/NewTabMenuEntry.h"
|
||||
#include "../TerminalSettingsModel/FolderEntry.h"
|
||||
#include "../TerminalSettingsModel/CascadiaSettings.h"
|
||||
#include "../types/inc/colorTable.hpp"
|
||||
#include "JsonTestClass.h"
|
||||
|
||||
#include <defaults.h>
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
||||
namespace SettingsModelLocalTests
|
||||
{
|
||||
// TODO:microsoft/terminal#3838:
|
||||
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
|
||||
// an updated TAEF that will let us install framework packages when the test
|
||||
// package is deployed. Until then, these tests won't deploy in CI.
|
||||
|
||||
class NewTabMenuTests : public JsonTestClass
|
||||
{
|
||||
// Use a custom AppxManifest to ensure that we can activate winrt types
|
||||
// from our test. This property will tell taef to manually use this as
|
||||
// the AppxManifest for this test class.
|
||||
// This does not yet work for anything XAML-y. See TabTests.cpp for more
|
||||
// details on that.
|
||||
BEGIN_TEST_CLASS(NewTabMenuTests)
|
||||
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(DefaultsToRemainingProfiles);
|
||||
TEST_METHOD(ParseEmptyFolder);
|
||||
};
|
||||
|
||||
void NewTabMenuTests::DefaultsToRemainingProfiles()
|
||||
{
|
||||
Log::Comment(L"If the user doesn't customize the menu, put one entry for each profile");
|
||||
|
||||
static constexpr std::string_view settingsString{ R"json({
|
||||
})json" };
|
||||
|
||||
try
|
||||
{
|
||||
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
|
||||
|
||||
const auto& entries = settings->GlobalSettings().NewTabMenu();
|
||||
VERIFY_ARE_EQUAL(1u, entries.Size());
|
||||
VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType::RemainingProfiles, entries.GetAt(0).Type());
|
||||
}
|
||||
catch (const SettingsException& ex)
|
||||
{
|
||||
auto loadError = ex.Error();
|
||||
loadError;
|
||||
throw ex;
|
||||
}
|
||||
catch (const SettingsTypedDeserializationException& e)
|
||||
{
|
||||
auto deserializationErrorMessage = til::u8u16(e.what());
|
||||
Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void NewTabMenuTests::ParseEmptyFolder()
|
||||
{
|
||||
Log::Comment(L"GH #14557 - An empty folder entry shouldn't crash");
|
||||
|
||||
static constexpr std::string_view settingsString{ R"json({
|
||||
"newTabMenu": [
|
||||
{ "type": "folder" }
|
||||
]
|
||||
})json" };
|
||||
|
||||
try
|
||||
{
|
||||
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
|
||||
|
||||
const auto& entries = settings->GlobalSettings().NewTabMenu();
|
||||
VERIFY_ARE_EQUAL(1u, entries.Size());
|
||||
}
|
||||
catch (const SettingsException& ex)
|
||||
{
|
||||
auto loadError = ex.Error();
|
||||
loadError;
|
||||
throw ex;
|
||||
}
|
||||
catch (const SettingsTypedDeserializationException& e)
|
||||
{
|
||||
auto deserializationErrorMessage = til::u8u16(e.what());
|
||||
Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,9 @@ namespace SettingsModelLocalTests
|
||||
TEST_METHOD(LayerProfileProperties);
|
||||
TEST_METHOD(LayerProfileIcon);
|
||||
TEST_METHOD(LayerProfilesOnArray);
|
||||
TEST_METHOD(ProfileWithEnvVars);
|
||||
TEST_METHOD(ProfileWithEnvVarsSameNameDifferentCases);
|
||||
|
||||
TEST_METHOD(DuplicateProfileTest);
|
||||
TEST_METHOD(TestGenGuidsForProfiles);
|
||||
TEST_METHOD(TestCorrectOldDefaultShellPaths);
|
||||
@@ -349,6 +352,50 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_NOT_EQUAL(settings->AllProfiles().GetAt(0).Guid(), settings->AllProfiles().GetAt(1).Guid());
|
||||
}
|
||||
|
||||
void ProfileTests::ProfileWithEnvVars()
|
||||
{
|
||||
const std::string profileString{ R"({
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"environment": {
|
||||
"VAR_1": "value1",
|
||||
"VAR_2": "value2",
|
||||
"VAR_3": "%VAR_3%;value3"
|
||||
}
|
||||
})" };
|
||||
const auto profile = implementation::Profile::FromJson(VerifyParseSucceeded(profileString));
|
||||
std::vector<IEnvironmentVariableMap> envVarMaps{};
|
||||
envVarMaps.emplace_back(profile->EnvironmentVariables());
|
||||
for (auto& envMap : envVarMaps)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(static_cast<uint32_t>(3), envMap.Size());
|
||||
VERIFY_ARE_EQUAL(L"value1", envMap.Lookup(L"VAR_1"));
|
||||
VERIFY_ARE_EQUAL(L"value2", envMap.Lookup(L"VAR_2"));
|
||||
VERIFY_ARE_EQUAL(L"%VAR_3%;value3", envMap.Lookup(L"VAR_3"));
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileTests::ProfileWithEnvVarsSameNameDifferentCases()
|
||||
{
|
||||
const std::string userSettings{ R"({
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"environment": {
|
||||
"FOO": "VALUE",
|
||||
"Foo": "Value"
|
||||
}
|
||||
}
|
||||
]
|
||||
})" };
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(userSettings);
|
||||
const auto warnings = settings->Warnings();
|
||||
VERIFY_ARE_EQUAL(static_cast<uint32_t>(2), warnings.Size());
|
||||
uint32_t index;
|
||||
VERIFY_IS_TRUE(warnings.IndexOf(SettingsLoadWarnings::InvalidProfileEnvironmentVariables, index));
|
||||
}
|
||||
|
||||
void ProfileTests::TestCorrectOldDefaultShellPaths()
|
||||
{
|
||||
static constexpr std::string_view inboxProfiles{ R"({
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace SettingsModelLocalTests
|
||||
|
||||
"font": {
|
||||
"face": "Cascadia Mono",
|
||||
"size": 12,
|
||||
"size": 12.0,
|
||||
"weight": "normal"
|
||||
},
|
||||
"padding": "8, 8, 8, 8",
|
||||
@@ -165,7 +165,13 @@ namespace SettingsModelLocalTests
|
||||
"historySize": 9001,
|
||||
|
||||
"closeOnExit": "graceful",
|
||||
"experimental.retroTerminalEffect": false
|
||||
"experimental.retroTerminalEffect": false,
|
||||
"environment":
|
||||
{
|
||||
"KEY_1": "VALUE_1",
|
||||
"KEY_2": "%KEY_1%",
|
||||
"KEY_3": "%PATH%"
|
||||
}
|
||||
})" };
|
||||
|
||||
static constexpr std::string_view smallProfileString{ R"(
|
||||
@@ -250,8 +256,8 @@ namespace SettingsModelLocalTests
|
||||
|
||||
// complex command with key chords
|
||||
static constexpr std::string_view actionsString4A{ R"([
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" },
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+c" },
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
|
||||
])" };
|
||||
// GH#13323 - these can be fragile. In the past, the order these get
|
||||
// re-serialized as has been not entirely stable. We don't really care
|
||||
@@ -260,7 +266,7 @@ namespace SettingsModelLocalTests
|
||||
// itself. Feel free to change as needed.
|
||||
static constexpr std::string_view actionsString4B{ R"([
|
||||
{ "command": { "action": "findMatch", "direction": "prev" }, "keys": "ctrl+shift+r" },
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
|
||||
])" };
|
||||
|
||||
// command with name and icon and multiple key chords
|
||||
@@ -284,8 +290,8 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
"name": "Change font size...",
|
||||
"commands": [
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 } },
|
||||
{ "command": { "action": "adjustFontSize", "delta": -1 } },
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1.0 } },
|
||||
{ "command": { "action": "adjustFontSize", "delta": -1.0 } },
|
||||
{ "command": "resetFontSize" },
|
||||
]
|
||||
}
|
||||
@@ -406,6 +412,12 @@ namespace SettingsModelLocalTests
|
||||
"$schema" : "https://aka.ms/terminal-profiles-schema",
|
||||
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
|
||||
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
|
||||
"newTabMenu":
|
||||
[
|
||||
{
|
||||
"type": "remainingProfiles"
|
||||
}
|
||||
],
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"font": {
|
||||
@@ -478,7 +490,7 @@ namespace SettingsModelLocalTests
|
||||
"name": "Profile with legacy font settings",
|
||||
|
||||
"fontFace": "Cascadia Mono",
|
||||
"fontSize": 12,
|
||||
"fontSize": 12.0,
|
||||
"fontWeight": "normal"
|
||||
})" };
|
||||
|
||||
@@ -488,7 +500,7 @@ namespace SettingsModelLocalTests
|
||||
|
||||
"font": {
|
||||
"face": "Cascadia Mono",
|
||||
"size": 12,
|
||||
"size": 12.0,
|
||||
"weight": "normal"
|
||||
}
|
||||
})" };
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dependencies, like MUX, can be aggregated correctly, and resources properly
|
||||
combined into a resources.pri file.
|
||||
|
||||
TestHostApp will manually copy the output of this project into it's own
|
||||
TestHostApp will manually copy the output of this project into its own
|
||||
OutDir, so we can run the tests from there. -->
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -20,6 +20,7 @@
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
|
||||
<!-- TerminalCppWinrt is not set intentionally -->
|
||||
<TerminalMUX>true</TerminalMUX>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
@@ -39,6 +40,7 @@
|
||||
<ClCompile Include="KeyBindingsTests.cpp" />
|
||||
<ClCompile Include="CommandTests.cpp" />
|
||||
<ClCompile Include="DeserializationTests.cpp" />
|
||||
<ClCompile Include="NewTabMenuTests.cpp" />
|
||||
<ClCompile Include="SerializationTests.cpp" />
|
||||
<ClCompile Include="TerminalSettingsTests.cpp" />
|
||||
<ClCompile Include="ThemeTests.cpp" />
|
||||
@@ -72,8 +74,6 @@
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<!-- Required for JsonUtils.h. -->
|
||||
<AdditionalOptions>%(AdditionalOptions) /Zc:twoPhase-</AdditionalOptions>
|
||||
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
|
||||
<DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
@@ -98,15 +98,4 @@
|
||||
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
|
||||
<Import Project="$(OpenConsoleDir)src\common.build.tests.props" />
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- From Microsoft.UI.Xaml.targets -->
|
||||
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
|
||||
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- We actually can just straight up reference MUX here, it's fine -->
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.$(TerminalMUXVersion)\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -779,21 +779,22 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(6u, settings->ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings->GlobalSettings().ColorSchemes().Size());
|
||||
|
||||
auto createTerminalSettings = [&](const auto& profile, const auto& schemes) {
|
||||
auto createTerminalSettings = [&](const auto& profile, const auto& schemes, const auto& Theme) {
|
||||
auto terminalSettings{ winrt::make_self<implementation::TerminalSettings>() };
|
||||
terminalSettings->_ApplyProfileSettings(profile);
|
||||
terminalSettings->_ApplyAppearanceSettings(profile.DefaultAppearance(), schemes);
|
||||
terminalSettings->_ApplyAppearanceSettings(profile.DefaultAppearance(), schemes, Theme);
|
||||
return terminalSettings;
|
||||
};
|
||||
|
||||
const auto activeProfiles = settings->ActiveProfiles();
|
||||
const auto colorSchemes = settings->GlobalSettings().ColorSchemes();
|
||||
const auto terminalSettings0 = createTerminalSettings(activeProfiles.GetAt(0), colorSchemes);
|
||||
const auto terminalSettings1 = createTerminalSettings(activeProfiles.GetAt(1), colorSchemes);
|
||||
const auto terminalSettings2 = createTerminalSettings(activeProfiles.GetAt(2), colorSchemes);
|
||||
const auto terminalSettings3 = createTerminalSettings(activeProfiles.GetAt(3), colorSchemes);
|
||||
const auto terminalSettings4 = createTerminalSettings(activeProfiles.GetAt(4), colorSchemes);
|
||||
const auto terminalSettings5 = createTerminalSettings(activeProfiles.GetAt(5), colorSchemes);
|
||||
const auto currentTheme = settings->GlobalSettings().CurrentTheme();
|
||||
const auto terminalSettings0 = createTerminalSettings(activeProfiles.GetAt(0), colorSchemes, currentTheme);
|
||||
const auto terminalSettings1 = createTerminalSettings(activeProfiles.GetAt(1), colorSchemes, currentTheme);
|
||||
const auto terminalSettings2 = createTerminalSettings(activeProfiles.GetAt(2), colorSchemes, currentTheme);
|
||||
const auto terminalSettings3 = createTerminalSettings(activeProfiles.GetAt(3), colorSchemes, currentTheme);
|
||||
const auto terminalSettings4 = createTerminalSettings(activeProfiles.GetAt(4), colorSchemes, currentTheme);
|
||||
const auto terminalSettings5 = createTerminalSettings(activeProfiles.GetAt(5), colorSchemes, currentTheme);
|
||||
|
||||
VERIFY_ARE_EQUAL(til::color(0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme
|
||||
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default
|
||||
|
||||
@@ -64,7 +64,8 @@ namespace SettingsModelLocalTests
|
||||
},
|
||||
"window":
|
||||
{
|
||||
"applicationTheme": "light"
|
||||
"applicationTheme": "light",
|
||||
"useMica": true
|
||||
}
|
||||
})" };
|
||||
|
||||
@@ -80,6 +81,7 @@ namespace SettingsModelLocalTests
|
||||
|
||||
VERIFY_IS_NOT_NULL(theme->Window());
|
||||
VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Light, theme->Window().RequestedTheme());
|
||||
VERIFY_ARE_EQUAL(true, theme->Window().UseMica());
|
||||
}
|
||||
|
||||
void ThemeTests::ParseEmptyTheme()
|
||||
@@ -161,7 +163,8 @@ namespace SettingsModelLocalTests
|
||||
},
|
||||
"window":
|
||||
{
|
||||
"applicationTheme": "light"
|
||||
"applicationTheme": "light",
|
||||
"useMica": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -172,14 +175,16 @@ namespace SettingsModelLocalTests
|
||||
},
|
||||
"window":
|
||||
{
|
||||
"applicationTheme": "light"
|
||||
"applicationTheme": "light",
|
||||
"useMica": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "backgroundOmittedEntirely",
|
||||
"window":
|
||||
{
|
||||
"applicationTheme": "light"
|
||||
"applicationTheme": "light",
|
||||
"useMica": true
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -234,7 +239,8 @@ namespace SettingsModelLocalTests
|
||||
"tabRow": {},
|
||||
"window":
|
||||
{
|
||||
"applicationTheme": "light"
|
||||
"applicationTheme": "light",
|
||||
"useMica": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -18,7 +18,7 @@ Author(s):
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#define BLOCK_TIL
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#include "LibraryIncludes.h"
|
||||
#include <LibraryIncludes.h>
|
||||
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
|
||||
// SDK definition of this function, so the only fix is to undef it.
|
||||
// from WinBase.h
|
||||
@@ -28,8 +28,9 @@ Author(s):
|
||||
#endif
|
||||
|
||||
#include <wil/cppwinrt.h>
|
||||
#include <unknwn.h>
|
||||
#include <Unknwn.h>
|
||||
#include <hstring.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <WexTestClass.h>
|
||||
#include <json.h>
|
||||
@@ -44,6 +45,7 @@ Author(s):
|
||||
#include <Windows.Graphics.Imaging.Interop.h>
|
||||
#include <winrt/windows.ui.core.h>
|
||||
#include <winrt/Windows.ui.input.h>
|
||||
#include <winrt/Windows.UI.ViewManagement.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Windows.ui.xaml.media.h>
|
||||
|
||||
@@ -98,6 +98,26 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
}
|
||||
void _logCommands(winrt::Windows::Foundation::Collections::IVector<Command> commands, const int indentation = 1)
|
||||
{
|
||||
if (indentation == 1)
|
||||
{
|
||||
Log::Comment((commands.Size() == 0) ? L"Commands:\n <none>" : L"Commands:");
|
||||
}
|
||||
for (const auto& cmd : commands)
|
||||
{
|
||||
Log::Comment(fmt::format(L"{0:>{1}}* {2}",
|
||||
L"",
|
||||
indentation,
|
||||
cmd.Name())
|
||||
.c_str());
|
||||
|
||||
if (cmd.HasNestedCommands())
|
||||
{
|
||||
_logCommandNames(cmd.NestedCommands(), indentation + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void SettingsTests::TestIterateCommands()
|
||||
@@ -164,14 +184,15 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile0");
|
||||
auto command = expandedCommands.GetAt(0);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile0", command.Name());
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -189,7 +210,8 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile1");
|
||||
auto command = expandedCommands.GetAt(1);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile1", command.Name());
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -207,7 +229,8 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile2");
|
||||
auto command = expandedCommands.GetAt(2);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile2", command.Name());
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -287,14 +310,16 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile0");
|
||||
auto command = expandedCommands.GetAt(0);
|
||||
VERIFY_ARE_EQUAL(L"Split pane, profile: profile0", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -312,7 +337,9 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile1");
|
||||
auto command = expandedCommands.GetAt(1);
|
||||
VERIFY_ARE_EQUAL(L"Split pane, profile: profile1", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -330,7 +357,9 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile2");
|
||||
auto command = expandedCommands.GetAt(2);
|
||||
VERIFY_ARE_EQUAL(L"Split pane, profile: profile2", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -412,14 +441,16 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile0");
|
||||
auto command = expandedCommands.GetAt(0);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile0", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -437,7 +468,9 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile1\"");
|
||||
auto command = expandedCommands.GetAt(1);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile1\"", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -455,7 +488,9 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile2");
|
||||
auto command = expandedCommands.GetAt(2);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile2", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -527,14 +562,15 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"Connect to ssh...");
|
||||
auto rootCommand = expandedCommands.GetAt(0);
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
VERIFY_ARE_EQUAL(L"Connect to ssh...", rootCommand.Name());
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
|
||||
@@ -621,14 +657,16 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
|
||||
|
||||
auto grandparentCommand = expandedCommands.Lookup(L"grandparent");
|
||||
auto grandparentCommand = expandedCommands.GetAt(0);
|
||||
VERIFY_IS_NOT_NULL(grandparentCommand);
|
||||
VERIFY_ARE_EQUAL(L"grandparent", grandparentCommand.Name());
|
||||
|
||||
auto grandparentActionAndArgs = grandparentCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(grandparentActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, grandparentActionAndArgs.Action());
|
||||
@@ -744,17 +782,22 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
for (auto name : std::vector<std::wstring>({ L"profile0", L"profile1", L"profile2" }))
|
||||
const std::vector<std::wstring> profileNames{ L"profile0", L"profile1", L"profile2" };
|
||||
for (auto i = 0u; i < profileNames.size(); i++)
|
||||
{
|
||||
winrt::hstring commandName{ name + L"..." };
|
||||
auto command = expandedCommands.Lookup(commandName);
|
||||
const auto& name{ profileNames[i] };
|
||||
winrt::hstring commandName{ profileNames[i] + L"..." };
|
||||
|
||||
auto command = expandedCommands.GetAt(i);
|
||||
VERIFY_ARE_EQUAL(commandName, command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -880,14 +923,16 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"New Tab With Profile...");
|
||||
auto rootCommand = expandedCommands.GetAt(0);
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
VERIFY_ARE_EQUAL(L"New Tab With Profile...", rootCommand.Name());
|
||||
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
|
||||
@@ -982,13 +1027,16 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"New Pane...");
|
||||
auto rootCommand = expandedCommands.GetAt(0);
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
VERIFY_ARE_EQUAL(L"New Pane...", rootCommand.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
@@ -1205,8 +1253,8 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"${scheme.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
@@ -1215,7 +1263,9 @@ namespace TerminalAppLocalTests
|
||||
// just easy tests to write.
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Campbell");
|
||||
auto command = expandedCommands.GetAt(0);
|
||||
VERIFY_ARE_EQUAL(L"iterable command Campbell", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -1233,7 +1283,9 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Campbell PowerShell");
|
||||
auto command = expandedCommands.GetAt(1);
|
||||
VERIFY_ARE_EQUAL(L"iterable command Campbell PowerShell", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
@@ -1251,7 +1303,9 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Vintage");
|
||||
auto command = expandedCommands.GetAt(2);
|
||||
VERIFY_ARE_EQUAL(L"iterable command Vintage", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalApp/TerminalPage.h"
|
||||
#include "../TerminalApp/TerminalWindow.h"
|
||||
#include "../TerminalApp/MinMaxCloseControl.h"
|
||||
#include "../TerminalApp/TabRowControl.h"
|
||||
#include "../TerminalApp/ShortcutActionDispatch.h"
|
||||
#include "../TerminalApp/TerminalTab.h"
|
||||
#include "../TerminalApp/CommandPalette.h"
|
||||
#include "../TerminalApp/ContentManager.h"
|
||||
#include "CppWinrtTailored.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
@@ -110,6 +112,8 @@ namespace TerminalAppLocalTests
|
||||
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
|
||||
CascadiaSettings initialSettings);
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> _commonSetup();
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::WindowProperties> _windowProperties;
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::ContentManager> _contentManager;
|
||||
};
|
||||
|
||||
template<typename TFunction>
|
||||
@@ -194,8 +198,14 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>();
|
||||
_windowProperties = winrt::make_self<winrt::TerminalApp::implementation::WindowProperties>();
|
||||
winrt::TerminalApp::WindowProperties props = *_windowProperties;
|
||||
|
||||
_contentManager = winrt::make_self<winrt::TerminalApp::implementation::ContentManager>();
|
||||
winrt::TerminalApp::ContentManager contentManager = *_contentManager;
|
||||
|
||||
auto result = RunOnUIThread([&page, props, contentManager]() {
|
||||
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>(props, contentManager);
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
@@ -239,9 +249,13 @@ namespace TerminalAppLocalTests
|
||||
// it's weird.
|
||||
winrt::TerminalApp::TerminalPage projectedPage{ nullptr };
|
||||
|
||||
_windowProperties = winrt::make_self<winrt::TerminalApp::implementation::WindowProperties>();
|
||||
winrt::TerminalApp::WindowProperties props = *_windowProperties;
|
||||
_contentManager = winrt::make_self<winrt::TerminalApp::implementation::ContentManager>();
|
||||
winrt::TerminalApp::ContentManager contentManager = *_contentManager;
|
||||
Log::Comment(NoThrowString().Format(L"Construct the TerminalPage"));
|
||||
auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() {
|
||||
projectedPage = winrt::TerminalApp::TerminalPage();
|
||||
auto result = RunOnUIThread([&projectedPage, &page, initialSettings, props, contentManager]() {
|
||||
projectedPage = winrt::TerminalApp::TerminalPage(props, contentManager);
|
||||
page.copy_from(winrt::get_self<winrt::TerminalApp::implementation::TerminalPage>(projectedPage));
|
||||
page->_settings = initialSettings;
|
||||
});
|
||||
@@ -1088,7 +1102,7 @@ namespace TerminalAppLocalTests
|
||||
// If you don't do this, the palette will just stay open, and the
|
||||
// next time we call _HandleNextTab, we'll continue traversing the
|
||||
// MRU list, instead of just hoping one entry.
|
||||
page->CommandPalette().Visibility(Visibility::Collapsed);
|
||||
page->LoadCommandPalette().Visibility(Visibility::Collapsed);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
@@ -1109,7 +1123,7 @@ namespace TerminalAppLocalTests
|
||||
// If you don't do this, the palette will just stay open, and the
|
||||
// next time we call _HandleNextTab, we'll continue traversing the
|
||||
// MRU list, instead of just hoping one entry.
|
||||
page->CommandPalette().Visibility(Visibility::Collapsed);
|
||||
page->LoadCommandPalette().Visibility(Visibility::Collapsed);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
@@ -1225,7 +1239,7 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title());
|
||||
});
|
||||
|
||||
const auto palette = winrt::get_self<winrt::TerminalApp::implementation::CommandPalette>(page->CommandPalette());
|
||||
const auto palette = winrt::get_self<winrt::TerminalApp::implementation::CommandPalette>(page->LoadCommandPalette());
|
||||
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::implementation::CommandPaletteMode::TabSwitchMode, palette->_currentMode, L"Verify we are in the tab switcher mode");
|
||||
// At this point, the contents of the command palette's _mruTabs list is
|
||||
@@ -1242,14 +1256,16 @@ namespace TerminalAppLocalTests
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
page->RenameWindowRequested([&page](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) {
|
||||
page->RenameWindowRequested([&page, this](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) {
|
||||
// In the real terminal, this would bounce up to the monarch and
|
||||
// come back down. Instead, immediately call back and set the name.
|
||||
page->WindowName(args.ProposedName());
|
||||
//
|
||||
// This replicates how TerminalWindow works
|
||||
_windowProperties->WindowName(args.ProposedName());
|
||||
});
|
||||
|
||||
auto windowNameChanged = false;
|
||||
page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable {
|
||||
_windowProperties->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable {
|
||||
if (args.PropertyName() == L"WindowNameForDisplay")
|
||||
{
|
||||
windowNameChanged = true;
|
||||
@@ -1260,7 +1276,7 @@ namespace TerminalAppLocalTests
|
||||
page->_RequestWindowRename(winrt::hstring{ L"Foo" });
|
||||
});
|
||||
TestOnUIThread([&]() {
|
||||
VERIFY_ARE_EQUAL(L"Foo", page->_WindowName);
|
||||
VERIFY_ARE_EQUAL(L"Foo", page->WindowProperties().WindowName());
|
||||
VERIFY_IS_TRUE(windowNameChanged,
|
||||
L"The window name should have changed, and we should have raised a notification that WindowNameForDisplay changed");
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user