Compare commits
59 commits
main
...
release-1.
Author | SHA1 | Date | |
---|---|---|---|
06c7a3abcb | |||
1122838862 | |||
e745d6281e | |||
52bf57e67c | |||
f3d70f2068 | |||
f86d02e66e | |||
39a5b270e8 | |||
8282d3a1c3 | |||
8318bf9a6a | |||
aec7392e6b | |||
dd744a5e8f | |||
e17e4f5c3d | |||
6e3be8f42a | |||
61292d8cf6 | |||
b25dc74a1d | |||
2366427a14 | |||
3fad41c0bb | |||
9ac7ce2082 | |||
5abb574973 | |||
e49d180e13 | |||
f4de1e47b3 | |||
c8007ce9e2 | |||
797abba352 | |||
cc0bc484ce | |||
0a76283911 | |||
1d090d6036 | |||
47b4ddcc1e | |||
d052617e87 | |||
c384ef657e | |||
ceb3c05c1b | |||
c18c240627 | |||
32e075956d | |||
62814f80a7 | |||
cbf5d6c5e4 | |||
5cc663c830 | |||
68c54bf65f | |||
cc215ce59a | |||
3dadd8e2be | |||
2bc4f64eac | |||
d44bb4e60e | |||
f6a821f026 | |||
a18bca8689 | |||
1821c00722 | |||
6c46b172a1 | |||
a02e8aef3c | |||
3831d47bc4 | |||
d87b49067a | |||
988dad37e5 | |||
5235d2d43b | |||
ccf87cd98c | |||
4c1815bd4b | |||
a5541ec285 | |||
2093dc4a4d | |||
4d8ccbfe54 | |||
2f6530ccf2 | |||
47e6a1a7d0 | |||
36879c1e4d | |||
0cd97a920c | |||
7a83ee2710 |
|
@ -1,7 +1,6 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
|
|
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -3,8 +3,6 @@
|
|||
###############################################################################
|
||||
* -text
|
||||
|
||||
*.inc linguist-language=cpp
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
|
|
19
.github/ISSUE_TEMPLATE/Bug_Report.yml
vendored
19
.github/ISSUE_TEMPLATE/Bug_Report.yml
vendored
|
@ -10,21 +10,14 @@ body:
|
|||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Windows Terminal version
|
||||
placeholder: "1.7.3651.0"
|
||||
label: Windows Terminal version (or Windows build number)
|
||||
placeholder: "10.0.19042.0, 1.7.3651.0"
|
||||
description: |
|
||||
You can find the version in the about dialog, or by running `wt -v` at the commandline.
|
||||
If you are reporting an issue in Windows Terminal, you can find the version in the about dialog.
|
||||
|
||||
If you are reporting an issue with the Windows Console, please run `ver` or `[Environment]::OSVersion`.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Windows build number
|
||||
placeholder: "10.0.19042.0"
|
||||
description: |
|
||||
Please run `ver` or `[Environment]::OSVersion`.
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
|
14
.github/actions/spelling/allow/allow.txt
vendored
14
.github/actions/spelling/allow/allow.txt
vendored
|
@ -1,14 +1,12 @@
|
|||
admins
|
||||
apc
|
||||
Apc
|
||||
bsd
|
||||
calt
|
||||
ccmp
|
||||
changelog
|
||||
cybersecurity
|
||||
Apc
|
||||
clickable
|
||||
clig
|
||||
copyable
|
||||
cybersecurity
|
||||
dalet
|
||||
dcs
|
||||
Dcs
|
||||
|
@ -32,22 +30,20 @@ hyperlink
|
|||
hyperlinking
|
||||
hyperlinks
|
||||
img
|
||||
inlined
|
||||
It'd
|
||||
kje
|
||||
liga
|
||||
lje
|
||||
Llast
|
||||
Lmid
|
||||
locl
|
||||
lorem
|
||||
Llast
|
||||
Lmid
|
||||
Lorigin
|
||||
maxed
|
||||
mkmk
|
||||
mnt
|
||||
mru
|
||||
nje
|
||||
noreply
|
||||
nje
|
||||
ogonek
|
||||
ok'd
|
||||
overlined
|
||||
|
|
12
.github/actions/spelling/allow/apis.txt
vendored
12
.github/actions/spelling/allow/apis.txt
vendored
|
@ -1,7 +1,5 @@
|
|||
ACCEPTFILES
|
||||
ACCESSDENIED
|
||||
acl
|
||||
aclapi
|
||||
alignas
|
||||
alignof
|
||||
APPLYTOSUBMENUS
|
||||
|
@ -11,7 +9,6 @@ BUILDBRANCH
|
|||
BUILDMSG
|
||||
BUILDNUMBER
|
||||
BYPOSITION
|
||||
BYCOMMAND
|
||||
charconv
|
||||
CLASSNOTAVAILABLE
|
||||
cmdletbinding
|
||||
|
@ -19,11 +16,9 @@ COLORPROPERTY
|
|||
colspan
|
||||
COMDLG
|
||||
comparand
|
||||
commandlinetoargv
|
||||
cstdint
|
||||
CXICON
|
||||
CYICON
|
||||
Dacl
|
||||
dataobject
|
||||
dcomp
|
||||
DERR
|
||||
|
@ -90,7 +85,6 @@ LSHIFT
|
|||
MENUCOMMAND
|
||||
MENUDATA
|
||||
MENUINFO
|
||||
MENUITEMINFOW
|
||||
memicmp
|
||||
mptt
|
||||
mov
|
||||
|
@ -120,19 +114,15 @@ OSVERSIONINFOEXW
|
|||
otms
|
||||
OUTLINETEXTMETRICW
|
||||
overridable
|
||||
PACL
|
||||
PAGESCROLL
|
||||
PEXPLICIT
|
||||
PICKFOLDERS
|
||||
pmr
|
||||
ptstr
|
||||
rcx
|
||||
REGCLS
|
||||
RETURNCMD
|
||||
rfind
|
||||
roundf
|
||||
RSHIFT
|
||||
SACL
|
||||
schandle
|
||||
semver
|
||||
serializer
|
||||
|
@ -169,8 +159,6 @@ toupper
|
|||
TTask
|
||||
TVal
|
||||
UChar
|
||||
UFIELD
|
||||
ULARGE
|
||||
UPDATEINIFILE
|
||||
userenv
|
||||
wcsstr
|
||||
|
|
1
.github/actions/spelling/allow/names.txt
vendored
1
.github/actions/spelling/allow/names.txt
vendored
|
@ -9,7 +9,6 @@ Diviness
|
|||
dsafa
|
||||
duhowett
|
||||
ekg
|
||||
eryksun
|
||||
ethanschoonover
|
||||
Firefox
|
||||
Gatta
|
||||
|
|
2
.github/actions/spelling/excludes.txt
vendored
2
.github/actions/spelling/excludes.txt
vendored
|
@ -61,14 +61,12 @@ SUMS$
|
|||
^src/host/runft\.bat$
|
||||
^src/host/runut\.bat$
|
||||
^src/interactivity/onecore/BgfxEngine\.
|
||||
^src/renderer/atlas/
|
||||
^src/renderer/wddmcon/WddmConRenderer\.
|
||||
^src/terminal/adapter/ut_adapter/run\.bat$
|
||||
^src/terminal/parser/delfuzzpayload\.bat$
|
||||
^src/terminal/parser/ft_fuzzer/run\.bat$
|
||||
^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$
|
||||
^src/terminal/parser/ft_fuzzwrapper/run\.bat$
|
||||
^src/terminal/parser/ut_parser/Base64Test.cpp$
|
||||
^src/terminal/parser/ut_parser/run\.bat$
|
||||
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
|
||||
^src/tools/lnkd/lnkd\.bat$
|
||||
|
|
4
.github/actions/spelling/expect/expect.txt
vendored
4
.github/actions/spelling/expect/expect.txt
vendored
|
@ -508,7 +508,6 @@ dealloc
|
|||
Debian
|
||||
debolden
|
||||
debugtype
|
||||
DECAC
|
||||
DECALN
|
||||
DECANM
|
||||
DECAUPSS
|
||||
|
@ -939,7 +938,6 @@ GTP
|
|||
guc
|
||||
gui
|
||||
guidatom
|
||||
guiddef
|
||||
GValue
|
||||
GWL
|
||||
GWLP
|
||||
|
@ -1537,7 +1535,6 @@ NOCOLOR
|
|||
NOCOMM
|
||||
NOCONTEXTHELP
|
||||
NOCOPYBITS
|
||||
nodefaultlib
|
||||
nodiscard
|
||||
NODUP
|
||||
noexcept
|
||||
|
@ -2235,7 +2232,6 @@ STARTWPARMSW
|
|||
Statusline
|
||||
stdafx
|
||||
STDAPI
|
||||
stdc
|
||||
stdcall
|
||||
stdcpp
|
||||
stderr
|
||||
|
|
|
@ -4,7 +4,7 @@ https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf
|
|||
https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]*
|
||||
https://www.w3.org/[-a-zA-Z0-9?&=\/_#]*
|
||||
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
|
||||
https://(?:[a-z-]+\.|)github(?:usercontent|)\.com/[-a-zA-Z0-9?%&=_\/.+]*
|
||||
https://(?:[a-z-]+\.|)github(?:usercontent|)\.com/[-a-zA-Z0-9?%&=_\/.]*
|
||||
https://www.xfree86.org/[-a-zA-Z0-9?&=\/_#]*
|
||||
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
|
||||
(?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|</UniqueIdentifier)
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"Microsoft.Net.Component.4.5.TargetingPack",
|
||||
"Microsoft.VisualStudio.Component.DiagnosticTools",
|
||||
"Microsoft.VisualStudio.Component.Debugger.JustInTime",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.22000",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.19041",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.Support",
|
||||
"Microsoft.VisualStudio.Component.VC.CoreIde",
|
||||
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
|
||||
|
|
|
@ -99,29 +99,15 @@ If you don't have any additional info/context to add but would like to indicate
|
|||
|
||||
## Contributing fixes / features
|
||||
|
||||
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!
|
||||
|
||||
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.
|
||||
* ["Tasks"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Issue-Task%22+) are usually new pieces of functionality that aren't yet implemented for the Terminal/Console. These are usually smaller features, which we believe
|
||||
- could be a single, atomic PR
|
||||
- Don't require much design consideration, or we've already written the spec for the larger feature they belong to.
|
||||
* ["Features"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Issue-Feature%22+) are larger pieces of new functionality. These are usually things we believe would require larger discussion of how they should be implemented, or they'll require some complicated new settings. They might just be features that are composed of many individual tasks. Often times, with features, we like to have a spec written before development work is started, to make sure we're all on the same page (see below).
|
||||
|
||||
Bugs and tasks are obviously the easiest to get started with, but don't feel afraid of features either! We've had some community members contribute some amazing "feature"-level work to the Terminal (albeit, with lots of discussion 😄).
|
||||
|
||||
|
||||
Often, we like to assign issues that generally belong to somebody's area of expertise to the team member that owns that area. This doesn't mean the community can't jump in -- they should reach out and have a chat with the assignee to see if it'd okay to take. If an issue's been assigned more than a month ago, there's a good chance it's fair game to try yourself.
|
||||
For those able & willing to help fix issues and/or implement features ...
|
||||
|
||||
### To Spec or not to Spec
|
||||
|
||||
Some issues/features may be quick and simple to describe and understand. For such scenarios, once a team member has agreed with your approach, skip ahead to the section headed "Fork, Branch, and Create your PR", below.
|
||||
|
||||
Small issues that do not require a spec will be labelled `Issue-Bug` or `Issue-Task`.
|
||||
Small issues that do not require a spec will be labelled Issue-Bug or Issue-Task.
|
||||
|
||||
However, some issues/features will require careful thought & formal design before implementation. For these scenarios, we'll request that a spec is written and the associated issue will be labeled `Issue-Feature`. More often than not, we'll add such features to the ["Specification Tracker" project](https://github.com/microsoft/terminal/projects/1).
|
||||
However, some issues/features will require careful thought & formal design before implementation. For these scenarios, we'll request that a spec is written and the associated issue will be labeled Issue-Feature.
|
||||
|
||||
Specs help collaborators discuss different approaches to solve a problem, describe how the feature will behave, how the feature will impact the user, what happens if something goes wrong, etc. Driving towards agreement in a spec, before any code is written, often results in simpler code, and less wasted effort in the long run.
|
||||
|
||||
|
@ -139,7 +125,7 @@ Team members will be happy to help review specs and guide them to completion.
|
|||
|
||||
### Help Wanted
|
||||
|
||||
Once the team has approved an issue/spec, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/terminal/labels/Help%20Wanted).
|
||||
Once the team have approved an issue/spec, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/terminal/labels/Help%20Wanted).
|
||||
|
||||
---
|
||||
|
||||
|
|
67
NOTICE.md
67
NOTICE.md
|
@ -117,6 +117,7 @@ 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.
|
||||
|
||||
```
|
||||
|
||||
## dynamic_bitset
|
||||
|
@ -147,6 +148,7 @@ 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.
|
||||
|
||||
```
|
||||
|
||||
## \{fmt\}
|
||||
|
@ -213,6 +215,7 @@ 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.
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
@ -246,71 +249,7 @@ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
|
||||
## PCG Random Number Generation
|
||||
|
||||
**Source**: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp)
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
## ConEmu
|
||||
**Source**: [https://github.com/Maximus5/ConEmu](https://github.com/Maximus5/ConEmu)
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2009-2017, Maximus5 <ConEmu.Maximus5@gmail.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* 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.
|
||||
```
|
||||
|
||||
# Microsoft Open Source
|
||||
|
|
|
@ -73,7 +73,6 @@ EndProject
|
|||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.unittest", "src\host\ut_lib\host.unittest.vcxproj", "{06EC74CB-9A12-429C-B551-8562EC954747}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{18D09A24-8240-42D6-8CB6-236EEE820263} = {18D09A24-8240-42D6-8CB6-236EEE820263}
|
||||
{71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF} = {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Unit", "src\host\ut_host\Host.UnitTests.vcxproj", "{531C23E7-4B76-4C08-8AAD-04164CB628C9}"
|
||||
|
@ -400,8 +399,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTerminal.UIA.Tests",
|
|||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "api-ms-win-core-synch-l1-2-0", "src\api-ms-win-core-synch-l1-2-0\api-ms-win-core-synch-l1-2-0.vcxproj", "{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererAtlas", "src\renderer\atlas\atlas.vcxproj", "{8222900C-8B6C-452A-91AC-BE95DB04B95F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
|
@ -3341,46 +3338,6 @@ Global
|
|||
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x64.Build.0 = Release|x64
|
||||
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x86.ActiveCfg = Release|Win32
|
||||
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x86.Build.0 = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.ActiveCfg = AuditMode|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.Build.0 = Debug|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.Build.0 = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|DotNet_x64Test.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|DotNet_x86Test.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.Build.0 = Fuzzing|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.Build.0 = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.ActiveCfg = Release|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.Build.0 = Release|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -3480,7 +3437,6 @@ Global
|
|||
{C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{F19DACD5-0C6E-40DC-B6E4-767A3200542C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F} = {05500DEF-2294-41E3-AF9A-24E580B82836}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
|
|
@ -214,7 +214,7 @@ resources useful and interesting:
|
|||
* Windows Terminal Launch: [Build 2019
|
||||
Session](https://www.youtube.com/watch?v=KMudkRcwjCw)
|
||||
* Run As Radio: [Show 645 - Windows Terminal with Richard
|
||||
Turner](https://www.runasradio.com/Shows/Show/645)
|
||||
Turner](http://www.runasradio.com/Shows/Show/645)
|
||||
* Azure Devops Podcast: [Episode 54 - Kayla Cinnamon and Rich Turner on DevOps
|
||||
on the Windows
|
||||
Terminal](http://azuredevopspodcast.clear-measure.com/kayla-cinnamon-and-rich-turner-on-devops-on-the-windows-terminal-team-episode-54)
|
||||
|
@ -289,7 +289,6 @@ If you would like to ask a question that you feel doesn't warrant an issue
|
|||
* You must [enable Developer Mode in the Windows Settings
|
||||
app](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development)
|
||||
to locally install and run Windows Terminal
|
||||
* You must have [PowerShell 7 or later](https://github.com/PowerShell/PowerShell/releases/latest) installed
|
||||
* You must have the [Windows 10 1903
|
||||
SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk)
|
||||
installed
|
||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
clean: true
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Code Formatting Check'
|
||||
displayName: 'Code Formattting Check'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: '.\build\scripts\Invoke-FormattingCheck.ps1'
|
||||
|
|
|
@ -63,14 +63,8 @@
|
|||
Outputs="$(OpenConsoleCommonOutDir)\inc\TilFeatureStaging.h"
|
||||
DependsOnTargets="_GenerateBranchAndBrandingCache">
|
||||
<MakeDir Directories="$(OpenConsoleCommonOutDir)\inc" />
|
||||
<!-- This commandline is escaped like:
|
||||
|
||||
powershell -Command "&'$(SolutionDir)\tools\Generate-FeatureStagingHeader.ps1' -Path '%(FeatureFlagFile.FullPath)'' -Branding $(_WTBrandingName)"
|
||||
|
||||
which was the only way I could find to get it to obey spaces in the SolutionDir
|
||||
-->
|
||||
<Exec
|
||||
Command="powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy ByPass -Command "&'$(SolutionDir)\tools\Generate-FeatureStagingHeader.ps1' -Path '%(FeatureFlagFile.FullPath)' -Branding $(_WTBrandingName)""
|
||||
Command="powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy ByPass -Command "$(SolutionDir)\tools\Generate-FeatureStagingHeader.ps1" -Path "%(FeatureFlagFile.FullPath)" -Branding $(_WTBrandingName)"
|
||||
ConsoleToMsBuild="true"
|
||||
StandardOutputImportance="low">
|
||||
<Output TaskParameter="ConsoleOutput" ItemName="_FeatureFlagFileLines" />
|
||||
|
|
|
@ -22,7 +22,7 @@ Param(
|
|||
[Parameter(HelpMessage="Path to makeappx.exe")]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x86\MakeAppx.exe"
|
||||
$MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\MakeAppx.exe"
|
||||
)
|
||||
|
||||
If ($null -Eq (Get-Item $MakeAppxPath -EA:SilentlyContinue)) {
|
||||
|
|
|
@ -8,7 +8,7 @@ Param(
|
|||
[Parameter(HelpMessage="Path to Windows Kit")]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$WindowsKitPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0"
|
||||
$WindowsKitPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2021</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>13</VersionMinor>
|
||||
<VersionMinor>10</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
Introducing exceptions to an existing non-exception-based codebase can be perilous. The console was originally written
|
||||
in C at a time when C++ was relatively unused in the Windows operating system. As part of our project to modernize the
|
||||
Windows console, we converted to use C++, but still had an aversion to using exception-based error handling in
|
||||
our code for fear that it might introduce unexpected failures. However, the STL and other libraries like it are so useful that
|
||||
our code for fear that it introduce unexpected failures. However, the STL and other libraries like it are so useful that
|
||||
sometimes it's significantly simpler to use them. Given that, we have a set of rules that we follow when considering
|
||||
exception use.
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ I think there might be a bit of a misunderstanding here - there are two differen
|
|||
* shell applications, like `cmd.exe`, `powershell`, `zsh`, etc. These are text-only applications that emit streams of characters. They don't care at all about how they're eventually rendered to the user. These are also sometimes referred to as "commandline client" applications.
|
||||
* terminal applications, like the Windows Terminal, gnome-terminal, xterm, iterm2, hyper. These are graphical applications that can be used to render the output of commandline clients.
|
||||
|
||||
On Windows, if you just run `cmd.exe` directly, the OS will create an instance of `conhost.exe` as the _terminal_ for `cmd.exe`. The same thing happens for `powershell.exe`, the system will create a new conhost window for any client that's not already connected to a terminal of some sort. This has lead to an enormous amount of confusion for people thinking that a conhost window is actually a "`cmd` window". `cmd` can't have a window, it's just a commandline application. Its window is always some other terminal.
|
||||
On Windows, if you just run `cmd.exe` directly, the OS will create an instance of `conhost.exe` as the _terminal_ for `cmd.exe`. The same thing happens for `powershell.exe`, the system will creates a new conhost window for any client that's not already connected to a terminal of some sort. This has lead to an enormous amount of confusion for people thinking that a conhost window is actually a "`cmd` window". `cmd` can't have a window, it's just a commandline application. Its window is always some other terminal.
|
||||
|
||||
Any terminal can run any commandline client application. So you can use the Windows Terminal to run whatever shell you want. I use mine for both `cmd` and `powershell`, and also WSL:
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ Import-Module .\tools\OpenConsole.psm1
|
|||
Set-MsBuildDevEnvironment
|
||||
Get-Format
|
||||
```
|
||||
After, go to Tools > Options > Text Editor > C++ > Formatting and check "Use custom clang-format.exe file" in Visual Studio and choose the clang-format.exe in the repository at /packages/clang-format.win-x86.10.0.0/tools/clang-format.exe by clicking "browse" right under the check box.
|
||||
After, go to Tools > Options > Text Editor > C++ > Formatting and checking "Use custom clang-format.exe file" in Visual Studio and choose the clang-format.exe in the repository at /packages/clang-format.win-x86.10.0.0/tools/clang-format.exe by clicking "browse" right under the check box.
|
||||
|
||||
### Building in PowerShell
|
||||
|
||||
|
|
|
@ -380,7 +380,7 @@ Here's the AppxManifest we're using:
|
|||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22000.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug" MinVersion="14.0.27023.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug.UWPDesktop" MinVersion="14.0.27027.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
</Dependencies>
|
||||
|
@ -517,7 +517,7 @@ This is because of a few key lines we already put in the appxmanifest:
|
|||
|
||||
```xml
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22000.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug" MinVersion="14.0.27023.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug.UWPDesktop" MinVersion="14.0.27027.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
</Dependencies>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,619 +0,0 @@
|
|||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-11-20
|
||||
last updated: 2021-08-17
|
||||
issue id: #1032
|
||||
---
|
||||
|
||||
# Elevation Quality of Life Improvements
|
||||
|
||||
## Abstract
|
||||
|
||||
For a long time, we've been researching adding support to the Windows Terminal
|
||||
for running both unelevated and elevated (admin) tabs side-by-side, in the same
|
||||
window. However, after much research, we've determined that there isn't a safe
|
||||
way to do this without opening the Terminal up as a potential
|
||||
escalation-of-privilege vector.
|
||||
|
||||
Instead, we'll be adding a number of features to the Terminal to improve the
|
||||
user experience of working in elevated scenarios. These improvements include:
|
||||
|
||||
* A visible indicator that the Terminal window is elevated ([#1939])
|
||||
* Configuring the Terminal to always run elevated ([#632])
|
||||
* Configuring a specific profile to always open elevated ([#632])
|
||||
* Allowing new tabs, panes to be opened elevated directly from an unelevated
|
||||
window
|
||||
* Dynamic profile appearance that changes depending on if the Terminal is
|
||||
elevated or not. ([#1939], [#8311])
|
||||
|
||||
## Background
|
||||
|
||||
_This section was originally authored in the [Process Model 2.0 Spec]. Please
|
||||
refer to it there for its original context._
|
||||
|
||||
Let's presume that you're a user who wants to be able to open an elevated tab
|
||||
within an otherwise unelevated Terminal window. We call this scenario "mixed
|
||||
elevation" - the tabs within the Terminal can be running either unelevated _or_
|
||||
elevated client applications.
|
||||
|
||||
It wouldn't be terribly difficult for the unelevated Terminal to request the
|
||||
permission of the user to spawn an elevated client application. The user would
|
||||
see a UAC prompt, they'd accept, and then they'd be able to have an elevated
|
||||
shell alongside their unelevated tabs.
|
||||
|
||||
However, this creates an escalation of privilege vector. Now, there's an
|
||||
unelevated window which is connected directly to an elevated process. At this
|
||||
point, **any other unelevated application could send input to the Terminal's
|
||||
`HWND`**. This would make it possible for another unelevated process to "drive"
|
||||
the Terminal window, and send commands to the elevated client application.
|
||||
|
||||
It was initially theorized that the window/content model architecture would also
|
||||
help enable "mixed elevation". With mixed elevation, tabs could run at different
|
||||
integrity levels within the same terminal window. However, after investigation
|
||||
and research, it has become apparent that this scenario is not possible to do
|
||||
safely after all. There are numerous technical difficulties involved, and each
|
||||
with their own security risks. At the end of the day, the team wouldn't be
|
||||
comfortable shipping a mixed-elevation solution, because there's simply no way
|
||||
for us to be confident that we haven't introduced an escalation-of-privilege
|
||||
vector utilizing the Terminal. No matter how small the attack surface might be,
|
||||
we wouldn't be confident that there are _no_ vectors for an attack.
|
||||
|
||||
Some things we considered during this investigation:
|
||||
|
||||
* If a user requests a new elevated tab from an otherwise unelevated window, we
|
||||
could use UAC to create a new, elevated window process, and "move" all the
|
||||
current tabs to that window process, as well as the new elevated client. Now,
|
||||
the window process would be elevated, preventing it from input injection, and
|
||||
it would still contains all the previously existing tabs. The original window
|
||||
process could now be discarded, as the new elevated window process will
|
||||
pretend to be the original window.
|
||||
- However, it is unfortunately not possible with COM to have an elevated
|
||||
client attach to an unelevated server that's registered at runtime. Even in
|
||||
a packaged environment, the OS will reject the attempt to `CoCreateInstance`
|
||||
the content process object. this will prevent elevated windows from
|
||||
re-connecting to unelevated client processes.
|
||||
- We could theoretically build an RPC tunnel between content and window
|
||||
processes, and use the RPC connection to marshal the content process to the
|
||||
elevated window. However, then _we_ would need to be responsible for
|
||||
securing access the the RPC endpoint, and we feel even less confident doing
|
||||
that.
|
||||
- Attempts were also made to use a window-broker-content architecture, with
|
||||
the broker process having a static CLSID in the registry, and having the
|
||||
window and content processes at mixed elevation levels `CoCreateInstance`
|
||||
that broker. This however _also_ did not work across elevation levels. This
|
||||
may be due to a lack of Packaged COM support for mixed elevation levels.
|
||||
|
||||
It's also possible that the author forgot that packaged WinRT doesn't play
|
||||
nicely with creating objects in an elevated context. The Terminal has
|
||||
previously needed to manually manifest all its classes in a SxS manifest for
|
||||
Unpackaged WinRT to allow the classes to be activated, rather than relying
|
||||
on the packaged catalog. It's theoretically possible that doing that would
|
||||
have allowed the broker to be activated across integrity levels.
|
||||
|
||||
Even if this approach did end up working, we would still need to be
|
||||
responsible for securing the elevated windows so that an unelevated attacker
|
||||
couldn't hijack a content process and trigger unexpected code in the window
|
||||
process. We didn't feel confident that we could properly secure this channel
|
||||
either.
|
||||
|
||||
We also considered allowing mixed content in windows that were _originally_
|
||||
elevated. If the window is already elevated, then it can launch new unelevated
|
||||
processes. We could allow elevated windows to still create unelevated
|
||||
connections. However, we'd want to indicate per-pane what the elevation state
|
||||
of each connection is. The user would then need to keep track themselves of
|
||||
which terminal instances are elevated, and which are not.
|
||||
|
||||
This also marks a departure from the current behavior, where everything in an
|
||||
elevated window would be elevated by default. The user would need to specify for
|
||||
each thing in the elevated window that they'd want to create it elevated. Or the
|
||||
Terminal would need to provide some setting like
|
||||
`"autoElevateEverythingInAnElevatedWindow"`.
|
||||
|
||||
We cannot support mixed elevation when starting in a unelevated window.
|
||||
Therefore, it doesn't make a lot of UX sense to support it in the other
|
||||
direction. It's a cleaner UX story to just have everything in a single window at
|
||||
the same elevation level.
|
||||
|
||||
## Solution Design
|
||||
|
||||
Instead of supporting mixed elevation in the same window, we'll introduce the
|
||||
following features to the Terminal. These are meant as a way of improving the
|
||||
quality of life for users who work in mixed-elevation (or even just elevated)
|
||||
environments.
|
||||
|
||||
### Visible indicator for elevated windows
|
||||
|
||||
As requested in [#1939], it would be nice if it was easy to visibly identify if
|
||||
a Terminal window was elevated or not.
|
||||
|
||||
One easy way of doing this is by adding a simple UAC shield to the left of the
|
||||
tabs for elevated windows. This shield could be configured by the theme (see
|
||||
[#3327]). We could provide the following states:
|
||||
* Colored (the default)
|
||||
* Monochrome
|
||||
* Hidden, to hide the shield even on elevated windows. This is the current
|
||||
behavior.
|
||||
|
||||
![UAC-shield-in-titlebar](UAC-shield-in-titlebar.png)
|
||||
_figure 1: a monochrome UAC shield in the titlebar of the window, courtesy of @mdtauk_
|
||||
|
||||
We could also simplify this to only allow a boolean true/false for displaying
|
||||
the shield. As we do often with other enums, we could define `true` to be the
|
||||
same as the default appearance, and `false` to be the hidden option. As always,
|
||||
the development of the Terminal is an iterative process, where we can
|
||||
incrementally improve from no setting, to a boolean setting, to a enum-backed
|
||||
one.
|
||||
|
||||
### Configuring a profile to always run elevated
|
||||
|
||||
Oftentimes, users might have a particular tool chain that only works when
|
||||
running elevated. In these scenarios, it would be convenient for the user to be
|
||||
able to identify that the profile should _always_ run elevated. That way, they
|
||||
could open the profile from the dropdown menu of an otherwise unelevated window
|
||||
and have the elevated window open with the profile automatically.
|
||||
|
||||
We'll be adding the `"elevate": true|false` setting as a per-profile setting,
|
||||
with a default value of `false`. When set to `true`, we'll try to auto-elevate
|
||||
the profile whenever it's launched. We'll check to see if this window is
|
||||
elevated before creating the connection for this profile. If the window is not
|
||||
elevated, then we'll create a new window with the requested elevation level to
|
||||
handle the new connection.
|
||||
|
||||
`"elevate": false` will do nothing. If the window is already elevated, then the
|
||||
profile won't open an un-elevated window.
|
||||
|
||||
If the user tries to open an `"elevate": true` profile in a window that's
|
||||
already elevated, then a new tab/split will open in the existing window, rather
|
||||
than spawning an additional elevated window.
|
||||
|
||||
There are three situations where we're creating new terminal instances: new
|
||||
tabs, new splits, and new windows. Currently, these are all actions that are
|
||||
also exposed in the `wt` commandline as subcommands. We can convert from the
|
||||
commandline arguments into these actions already. Therefore, it shouldn't be too
|
||||
challenging to convert these actions back into the equal commandline arguments.
|
||||
|
||||
For the following examples, let's assume the user is currently in an unelevated
|
||||
Terminal window.
|
||||
|
||||
When the user tries to create a new elevated **tab**, we'll need to create a new
|
||||
process, elevated, with the following commandline:
|
||||
|
||||
```
|
||||
wt new-tab [args...]
|
||||
```
|
||||
|
||||
When we create this new `wt` instance, it will obey the glomming rules as
|
||||
specified in [Session Management Spec]. It might end up glomming to another
|
||||
existing window at that elevation level, or possibly create its own window.
|
||||
|
||||
Similarly, for a new elevated **window**, we can make sure to pass the `-w new`
|
||||
arguments to `wt`. These parameters indicate that we definitely want this
|
||||
command to run in a new window, regardless of the current glomming settings.
|
||||
|
||||
```
|
||||
wt -w new new-tab [args...]
|
||||
```
|
||||
|
||||
However, creating a new **pane** is a little trickier. Invoking the `wt
|
||||
split-pane [args...]` is straightforward enough.
|
||||
|
||||
<!-- Discussion notes follow:
|
||||
If the current window doesn't have the same elevation level as the
|
||||
requested profile, do we always want to just create a new split? If the command
|
||||
ends up glomming to an existing window, does that even make sense? That invoking
|
||||
an elevated split in an unelevated window would end up splitting the elevated
|
||||
window? It's very possible that the user wanted a split in the tab they're
|
||||
currently in, in the unelevated window, but they don't want a split in the
|
||||
elevated window.
|
||||
|
||||
What if there's not space in the elevated window to create the split (but there
|
||||
would be in the current window)? That would sure make it seem like nothing
|
||||
happened, silently.
|
||||
|
||||
We could alternatively have cross-elevation splits default to always opening a
|
||||
new tab. That might mitigate some of the odd behaviors. Until we actually have
|
||||
support for running commands in existing windows, we'll always need to make a
|
||||
new window when running elevated. We'll need to make the new window for new tabs
|
||||
and splits, because there's no way to invoke another existing window.
|
||||
|
||||
A third proposal is to pop a warning dialog at the user when they try to open an
|
||||
elevated split from and unelevated window. This dialog could be something like
|
||||
|
||||
> What you requested couldn't be completed. Do you want to:
|
||||
> A. Make me a new tab instead.
|
||||
> B. Forget it and cancel. I'll go fix my config.
|
||||
|
||||
I'm certainly leaning towards proposal 2 - always create a new tab. This is how
|
||||
it's implemented in [#8514]. In that PR, this seems to work sensibly.
|
||||
-->
|
||||
|
||||
After discussing with the team, we have decided that the most sensible approach
|
||||
for handling a cross-elevation `split-pane` is to just create a new tab in the
|
||||
elevated window. The user can always re-attach the pane as a split with the
|
||||
`move-pane` command once the new pane in the elevated window.
|
||||
|
||||
#### Configure the Terminal to _always_ run elevated
|
||||
|
||||
`elevate` is a per-profile property, not a global property. If a user
|
||||
wants to always have all instances of the Terminal run elevated, they
|
||||
could set `"elevate": true` in their profile defaults. That would cause _all_
|
||||
profiles they launch to always spawn as elevated windows.
|
||||
|
||||
#### `elevate` in Actions
|
||||
|
||||
Additionally, we'll add the `elevate` property to the `NewTerminalArgs` used in
|
||||
the `newTab`, `splitPane`, and `newWindow` actions. This is similar to how other
|
||||
properties of profiles can be overridden at launch time. This will allow
|
||||
windows, tabs and panes to all be created specifically as elevated windows.
|
||||
|
||||
In the `NewTerminalArgs`, `elevate` will be an optional boolean, with the
|
||||
following behavior:
|
||||
* `null` (_default_): Don't modify the `elevate` property for this profile
|
||||
* `true`: This launch should act like the profile had `"elevate": true` in its
|
||||
properties.
|
||||
* `false`: This launch should act like the profile had `"elevate": false` in its
|
||||
properties.
|
||||
|
||||
We'll also add an iterable command for opening a profile in an
|
||||
elevated tab, with the following json:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// New elevated tab...
|
||||
"name": { "key": "NewElevatedTabParentCommandName", "icon": "UAC-Shield.png" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": "${profile.name}",
|
||||
"command": { "action": "newTab", "profile": "${profile.name}", "elevated": true }
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
#### Elevation from the dropdown
|
||||
|
||||
Currently, the new tab dropdown supports opening a new pane by
|
||||
<kbd>Alt+click</kbd>ing on a profile. We could similarly add support to open a
|
||||
tab elevated with <kbd>Ctrl+click</kbd>. This is similar to the behavior of the
|
||||
Windows taskbar. It supports creating an elevated instance of a program by
|
||||
<kbd>Ctrl+click</kbd>ing on entries as well.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Starting an elevated process from an unelevated process
|
||||
|
||||
It seems that we're able to create an elevated process by passing the `"runas"`
|
||||
verb to
|
||||
[`ShellExecute`](https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea).
|
||||
So we could use something like
|
||||
|
||||
```c++
|
||||
ShellExecute(nullptr,
|
||||
L"runas",
|
||||
L"wt.exe",
|
||||
L"-w new new-tab [args...]",
|
||||
nullptr,
|
||||
SW_SHOWNORMAL);
|
||||
```
|
||||
|
||||
This will ask the shell to perform a UAC prompt before spawning `wt.exe` as an
|
||||
elevated process.
|
||||
|
||||
> 👉 NOTE: This mechanism won't always work on non-Desktop SKUs of Windows. For
|
||||
> more discussion, see [Elevation on OneCore SKUs](#Elevation-on-OneCore-SKUs).
|
||||
|
||||
## Potential Issues
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Accessibility</strong></td>
|
||||
<td>
|
||||
|
||||
The set of changes proposed here are not expected to introduce any new
|
||||
accessibility issues. Users can already create elevated Terminal windows. Making
|
||||
it easier to create these windows doesn't really change our accessibility story.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Security</strong></td>
|
||||
<td>
|
||||
|
||||
We won't be doing anything especially unique, so there aren't expected to be any
|
||||
substantial security risks associated with these changes. Users can already
|
||||
create elevated Terminal windows, so we're not really introducing any new
|
||||
functionality, from a security perspective.
|
||||
|
||||
We're relying on the inherent security of the `runas` verb of `ShellExecute` to
|
||||
prevent any sort of unexpected escalation-of-privilege.
|
||||
|
||||
<hr>
|
||||
|
||||
One security concern is the fact that the `settings.json` file is currently a
|
||||
totally unsecured file. It's completely writable by any medium-IL process. That
|
||||
means it's totally possible for a malicious program to change the file. The
|
||||
malicious program could find a user's "Elevated PowerShell" profile, and change
|
||||
the commandline to `malicious.exe`. The user might then think that their
|
||||
"Elevated PowerShell" will run `powershell.exe` elevated, but will actually
|
||||
auto-elevate this attacker.
|
||||
|
||||
If all we expose to the user is the name of the profile in the UAC dialog, then
|
||||
there's no way for the user to be sure that the program that's about to be
|
||||
launched is actually what they expect.
|
||||
|
||||
To help mitigate this, we should _always_ pass the evaluated `commandline` as a
|
||||
part of the call to `ShellExecute`. the arguments that are passed to
|
||||
`ShellExecute` are visible to the user, though they need to click the "More
|
||||
Details" dropdown to reveal them.
|
||||
|
||||
We will need to mitigate this vulnerability regardless of adding support for the
|
||||
auto-elevation of individual terminal tabs/panes. If a user is launching the
|
||||
Terminal elevated (i.e. from the Win+X menu in Windows 11), then it's possible
|
||||
for a malicious program to overwrite the `commandline` of their default profile.
|
||||
The user may now unknowingly invoke this malicious program while thinking they
|
||||
are simply launching the Terminal.
|
||||
|
||||
To deal with this more broadly, we will display a dialog within the Terminal
|
||||
window before creating **any** elevated terminal instance. In that dialog, we'll
|
||||
display the commandline that will be executed, so the user can very easily
|
||||
confirm the commandline.
|
||||
|
||||
This will need to happen for all elevated terminal instances. For an elevated
|
||||
Windows Terminal window, this means _all_ connections made by the Terminal.
|
||||
Every time the user opens a new profile or a new commandline in a pane, we'll
|
||||
need to prompt them first to confirm the commandline. This dialog within the
|
||||
elevated window will also prevent an attacker from editing the `settings.json`
|
||||
file while the user already has an elevated Terminal window open and hijacking a
|
||||
profile.
|
||||
|
||||
The dialog options will certainly be annoying to users who don't want to be
|
||||
taken out of their flow to confirm the commandline that they wish to launch.
|
||||
There's precedent for a similar warning being implemented by VSCode, with their
|
||||
[Workspace Trust] feature. They too faced a similar backlash when the feature
|
||||
first shipped. However, in light of recent global cybersecurity attacks, this is
|
||||
seen as an acceptable UX degradation in the name of application trust. We don't
|
||||
want to provide an avenue that's too easy to abuse.
|
||||
|
||||
When the user confirms the commandline of this profile as something safe to run,
|
||||
we'll add it to an elevated-only version of `state.json`. (see [#7972] for more
|
||||
details). This elevated version of the file will only be accessible by the
|
||||
elevated Terminal, so an attacker cannot hijack the contents of the file. This
|
||||
will help mitigate the UX discomfort caused by prompting on every commandline
|
||||
launched. This should mean that the discomfort is only limited to the first
|
||||
elevated launch of a particular profile. Subsequent launches (without modifying
|
||||
the `commandline`) will work as they always have.
|
||||
|
||||
The dialog for confirming these commandlines should have a link to the docs for
|
||||
"Learn more...". Transparency in the face of this dialog should
|
||||
mitigate some dissatisfaction.
|
||||
|
||||
The dialog will _not_ appear if the user does not have a split token - if the
|
||||
user's PC does not have UAC enabled, then they're _already_ running as an
|
||||
Administrator. Everything they do is elevated, so they shouldn't be prompted in
|
||||
this way.
|
||||
|
||||
The Settings UI should also expose a way of viewing and removing these cached
|
||||
entries. This page should only be populated in the elevated version of the
|
||||
Terminal.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Reliability</strong></td>
|
||||
<td>
|
||||
|
||||
No changes to our reliability are expected as a part of this change.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
|
||||
There are no serious compatibility concerns expected with this changelist. The
|
||||
new `elevate` property will be unset by default, so users will heed to opt-in
|
||||
to the new auto-elevating behavior.
|
||||
|
||||
There is one minor concern regarding introducing the UAC shield on the window.
|
||||
We're planning on using themes to configure the appearance of the shield. That
|
||||
means we'll need to ship themes before the user will be able to hide the shield
|
||||
again.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Performance, Power, and Efficiency</strong></td>
|
||||
<td>
|
||||
|
||||
No changes to our performance are expected as a part of this change.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Centennial Applications
|
||||
|
||||
In the past, we've had a notoriously rough time with the Centennial app
|
||||
infrastructure and running the Terminal elevated. Notably, we've had to list all
|
||||
our WinRT classes in our SxS manifest so they could be activated using
|
||||
unpackaged WinRT while running elevated. Additionally, there are plenty of
|
||||
issues running the Terminal in an "over the shoulder" elevation (OTS) scenario.
|
||||
|
||||
Specifically, we're concerned with the following scenario:
|
||||
* the current user account has the Terminal installed,
|
||||
* but they aren't an Administrator,
|
||||
* the Administrator account doesn't have the Terminal installed.
|
||||
|
||||
In that scenario, the user can run into issues launching the Terminal in an
|
||||
elevated context (even after entering the Admin's credentials in the UAC
|
||||
prompt).
|
||||
|
||||
This spec proposes no new mitigations for dealing with these issues. It may in
|
||||
fact make them more prevalent, by making elevated contexts more easily
|
||||
accessible.
|
||||
|
||||
Unfortunately, these issues are OS bugs that are largely out of our own control.
|
||||
We will continue to apply pressure to the centennial app team internally as we
|
||||
encounter these issues. They are are team best equipped to resolve these issues.
|
||||
|
||||
### Default Terminal & auto-elevation
|
||||
|
||||
In the future, when we support setting the Terminal as the "default terminal
|
||||
emulator" on Windows. When that lands, we will use the `profiles.defaults`
|
||||
settings to create the tab where we'll be hosting the commandline client. If the user has
|
||||
`"elevate": true` in their `profiles.defaults`, we'd usually try to
|
||||
auto-elevate the profile. In this scenario, however, we can't do that. The
|
||||
Terminal is being invoked on behalf of the client app launching, instead of the
|
||||
Terminal invoking the client application.
|
||||
|
||||
**2021-08-17 edit**: Now that "defterm" has shipped, we're a little more aware
|
||||
of some of the limitations with packaged COM and elevation boundaries. Defterm
|
||||
cannot be used with elevated processes _at all_ currently (see [#10276]). When
|
||||
an elevated commandline application is launched, it will always just appear in
|
||||
`conhost.exe`. Furthermore, An unelevated peasant can't communicate with an
|
||||
elevated monarch so we can't toss the connection to the elevated monarch and
|
||||
have them handle it.
|
||||
|
||||
The simplest solution here is to just _always_ ignore the `elevate` property for
|
||||
incoming defterm connections. This is not an ideal solution, and one that we're
|
||||
willing to revisit if/when [#10276] is ever fixed.
|
||||
|
||||
### Elevation on OneCore SKUs
|
||||
|
||||
This spec proposes using `ShellExecute` to elevate the Terminal window. However,
|
||||
not all Windows SKUs have support for `ShellExecute`. Notably, the non-Desktop
|
||||
SKUs, which are often referred to as "OneCore" SKUs. On these platforms, we
|
||||
won't be able to use `ShellExecute` to elevate the Terminal. There might not
|
||||
even be the concept of multiple elevation levels, or different users, depending
|
||||
on the SKU.
|
||||
|
||||
Fortunately, this is a mostly hypothetical concern for the moment. Desktop is
|
||||
the only publicly supported SKU for the Terminal currently. If the Terminal ever
|
||||
does become available on those SKUs, we can use these proposals as mitigations.
|
||||
|
||||
* If elevation is supported, there must be some other way of elevating a
|
||||
process. We could always use that mechanism instead.
|
||||
* If elevation isn't supported (I'm thinking 10X is one of these), then we could
|
||||
instead display a warning dialog whenever a user tries to open an elevated
|
||||
profile.
|
||||
- We could take the warning a step further. We could add another settings
|
||||
validation step. This would warn the user if they try to mark any profiles
|
||||
or actions as `"elevate":true`
|
||||
|
||||
## Future considerations
|
||||
|
||||
* If we wanted to go even further down the visual differentiation route, we
|
||||
could consider allowing the user to set an entirely different theme ([#3327])
|
||||
based on the elevation state. Something like `elevatedTheme`, to pick another
|
||||
theme from the set of themes. This would allow them to force elevated windows
|
||||
to have a red titlebar, for example.
|
||||
* Over the course of discussion concerning appearance objects ([#8345]), it
|
||||
became clear that having separate "elevated" appearances defined for
|
||||
`profile`s was overly complicated. This is left as a consideration for a
|
||||
possible future extension that could handle this scenario in a cleaner way.
|
||||
* Similarly, we're going to leave [#3637] "different profiles when elevated vs
|
||||
unelevated" for the future. This also plays into the design of "configure the
|
||||
new tab dropdown" ([#1571]), and reconciling those two designs is out-of-scope
|
||||
for this particular release.
|
||||
* Tangentially, we may want to have a separate Terminal icon we ship with the
|
||||
UAC shield present on it. This would be especially useful for the tray icon.
|
||||
Since there will be different tray icon instances for elevated and unelevated
|
||||
windows, having unique icons may help users identify which is which.
|
||||
|
||||
### De-elevating a Terminal
|
||||
|
||||
the original version of this spec proposed that `"elevated":false` from an
|
||||
elevated Terminal window should create a new unelevated Terminal instance. The
|
||||
mechanism for doing this is described in [The Old New Thing: How can I launch an
|
||||
unelevated process from my elevated process, redux].
|
||||
|
||||
This works well when the Terminal is running unpackaged. However, de-elevating a
|
||||
process does not play well with packaged centennial applications. When asking
|
||||
the OS to run the packaged application from an elevated context, the system will
|
||||
still create the child process _elevated_. This means the packaged version of
|
||||
the Terminal won't be able to create a new unelevated Terminal instance.
|
||||
|
||||
From an internal mail thread:
|
||||
|
||||
> App model intercepts the `CreateProcess` call and redirects it to a COM
|
||||
> service. The parent of a packaged app is not the launching app, it’s some COM
|
||||
> service. So none of the parent process nonsense will work because the
|
||||
> parameters you passed to `CreateProcess` aren’t being used to create the
|
||||
> process.
|
||||
|
||||
If this is fixed in the future, we could theoretically re-introduce de-elevating
|
||||
a profile. The original spec proposed a `"elevated": bool?` setting, with the
|
||||
following behaviors:
|
||||
* `null` (_default_): Don't modify the elevation level when running this profile
|
||||
* `true`: If the current window is unelevated, try to create a new elevated
|
||||
window to host this connection.
|
||||
* `false`: If the current window is elevated, try to create a new unelevated
|
||||
window to host this connection.
|
||||
|
||||
We could always re-introduce this setting, to supercede `elevate`.
|
||||
|
||||
### Change profile appearance for elevated windows
|
||||
|
||||
In [#3062] and [#8345], we're planning on allowing users to set different
|
||||
appearances for a profile whether it's focused or not. We could do similar thing
|
||||
to enable a profile to have a different appearance when elevated. In the
|
||||
simplest case, this could allow the user to set `"background": "#ff0000"`. This
|
||||
would make a profile always appear to have a red background when in an elevated
|
||||
window.
|
||||
|
||||
The more specific details of this implementation are left to the spec
|
||||
[Configuration object for profiles].
|
||||
|
||||
In discussion of that spec, we decided that it would be far too complicated to
|
||||
try and overload the `unfocusedAppearance` machinery for differentiating between
|
||||
elevated and unelevated versions of the same profile. Already, that would lead
|
||||
to 4 states: [`appearance`, `unfocusedAppearance`, `elevatedAppearance`,
|
||||
`elevatedUnfocusedAppearance`]. This would lead to a combinatorial explosion if
|
||||
we decided in the future that there should also be other states for a profile.
|
||||
|
||||
This particular QoL improvement is currently being left as a future
|
||||
consideration, should someone come up with a clever way of defining
|
||||
elevated-specific settings.
|
||||
|
||||
<!--
|
||||
Brainstorming notes for future readers:
|
||||
|
||||
You could have a profile that layers on an existing profile, with elevated-specific settings:
|
||||
|
||||
{
|
||||
"name": "foo",
|
||||
"background": "#0000ff",
|
||||
"commandline": "cmd.exe /k echo I am unelevated"
|
||||
},
|
||||
{
|
||||
"inheritsFrom": "foo",
|
||||
"background": "#ff0000",
|
||||
"elevate": true,
|
||||
"commandline": "cmd.exe /k echo I am ELEVATED"
|
||||
}
|
||||
-->
|
||||
|
||||
<!-- Footnotes -->
|
||||
|
||||
[#632]: https://github.com/microsoft/terminal/issues/632
|
||||
[#1032]: https://github.com/microsoft/terminal/issues/1032
|
||||
[#1571]: https://github.com/microsoft/terminal/issues/1571
|
||||
[#1939]: https://github.com/microsoft/terminal/issues/1939
|
||||
[#3062]: https://github.com/microsoft/terminal/issues/3062
|
||||
[#3327]: https://github.com/microsoft/terminal/issues/3327
|
||||
[#3637]: https://github.com/microsoft/terminal/issues/3637
|
||||
[#4472]: https://github.com/microsoft/terminal/issues/4472
|
||||
[#5000]: https://github.com/microsoft/terminal/issues/5000
|
||||
[#7972]: https://github.com/microsoft/terminal/pull/7972
|
||||
[#8311]: https://github.com/microsoft/terminal/issues/8311
|
||||
[#8345]: https://github.com/microsoft/terminal/issues/8345
|
||||
[#8514]: https://github.com/microsoft/terminal/issues/8514
|
||||
[#10276]: https://github.com/microsoft/terminal/issues/10276
|
||||
|
||||
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0.md
|
||||
[Configuration object for profiles]: https://github.com/microsoft/terminal/blob/main/doc/specs/Configuration%20object%20for%20profiles.md
|
||||
[Session Management Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234472%20-%20Windows%20Terminal%20Session%20Management.md
|
||||
[The Old New Thing: How can I launch an unelevated process from my elevated process, redux]: https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443
|
||||
[Workspace Trust]: https://code.visualstudio.com/docs/editor/workspace-trust
|
Binary file not shown.
Before Width: | Height: | Size: 565 KiB |
Binary file not shown.
Before Width: | Height: | Size: 142 KiB |
Binary file not shown.
Before Width: | Height: | Size: 140 KiB |
Binary file not shown.
Before Width: | Height: | Size: 142 KiB |
Binary file not shown.
Before Width: | Height: | Size: 142 KiB |
|
@ -1,130 +0,0 @@
|
|||
---
|
||||
author: Kayla Cinnamon - cinnamon-msft
|
||||
created on: 2021-03-04
|
||||
last updated: 2021-03-09
|
||||
issue id: 6900
|
||||
---
|
||||
|
||||
# Actions Page
|
||||
|
||||
## Abstract
|
||||
|
||||
We need to represent actions inside the settings UI. This spec goes through the possible use cases and reasoning for including specific features for actions inside the settings UI.
|
||||
|
||||
## Background
|
||||
|
||||
### Inspiration
|
||||
|
||||
It would be ideal if we could get the settings UI to have parity with the JSON file. This will take some design work if we want every feature possible in relation to actions. There is also the option of not having parity with the JSON file in order to present a simpler UX.
|
||||
|
||||
### User Stories
|
||||
|
||||
All of these features are possible with the JSON file. This spec will go into discussion of which (possibly all) of these user stories need to be handled by the settings UI.
|
||||
|
||||
- Add key bindings to an action that does not already have keys assigned
|
||||
- Edit key bindings for an action
|
||||
- Remove key bindings from an action
|
||||
- Add multiple key bindings for the same action
|
||||
- Create an iterable action
|
||||
- Create a nested action
|
||||
- Choose which actions appear inside the command palette
|
||||
- See all possible actions, regardless of keys
|
||||
|
||||
Commands with properties:
|
||||
- sendInput has "input"
|
||||
- closeOtherTabs has "index"
|
||||
- closeTabsAfter has "index"
|
||||
- renameTab has "title"*
|
||||
- setTabColor has "color"*
|
||||
- newWindow has "commandline", "startingDirectory", "tabTitle", "index", "profile"
|
||||
- splitPane has "split", "commandline", "startingDirectory", "tabTitle", "index", "profile", "splitMode", "size"
|
||||
- copy has "singleLine", "copyFormatting"
|
||||
- scrollUp has "rowsToScroll"
|
||||
- scrollDown has "rowsToScroll"
|
||||
- setColorScheme has "colorScheme"
|
||||
|
||||
Majority of these commands listed above are intended for the command palette, so they wouldn't make much sense with keys assigned to them anyway.
|
||||
|
||||
### Future Considerations
|
||||
|
||||
One day we'll have actions that can be invoked by items in the dropdown menu. This setting will have to live somewhere. Also, once we get a status bar, people may want to invoke actions from there.
|
||||
|
||||
## Solution Design
|
||||
|
||||
### Proposal 1: Keyboard and Command Palette pages
|
||||
|
||||
Implement a Keyboard page in place of the Actions page. Also plan for a Command Palette page in the future if it's something that's heavily requested. The Command Palette page would cover the missing use cases listed below.
|
||||
|
||||
When users want to add a new key binding, the dropdown will list every action, regardless if it already has keys assigned. This page should show every key binding assigned to an action, even if there are multiple bindings to the same action.
|
||||
|
||||
Users will be able to view every possible action from the command palette if they'd like.
|
||||
|
||||
Use cases covered:
|
||||
- Add key bindings to an action that does not already have keys assigned
|
||||
- Edit key bindings for an action
|
||||
- Remove key bindings from an action
|
||||
- Add multiple key bindings for the same action
|
||||
- See all actions that have keys assigned
|
||||
|
||||
Use cases missing:
|
||||
- Create an iterable action
|
||||
- Create a nested action
|
||||
- Choose which actions appear inside the command palette
|
||||
- See all possible actions, regardless of keys
|
||||
|
||||
* **Pros**:
|
||||
- This allows people to edit their actions in most of their scenarios.
|
||||
- This gives us some wiggle room to cover majority of the use cases we need and seeing if people want the other use cases that are missing.
|
||||
|
||||
* **Cons**:
|
||||
- Unfortunately we couldn't cover every single use case with this design.
|
||||
- You can't edit the properties that are on some commands, however the default commands from the command palette include options with properties anyway. For example "decrease font size" has the `delta` property already included.
|
||||
|
||||
### Proposal 2: Have everything on one Actions page
|
||||
|
||||
Implement an Actions page that allows you to create actions designed for the command palette as well as actions with keys.
|
||||
|
||||
Use cases covered:
|
||||
- Add key bindings to an action that does not already have keys assigned
|
||||
- Edit key bindings for an action
|
||||
- Remove key bindings from an action
|
||||
- Add multiple key bindings for the same action
|
||||
- See all actions that have keys assigned
|
||||
- Create an iterable action
|
||||
- Create a nested action
|
||||
- Choose which actions appear inside the command palette
|
||||
- See all possible actions, regardless of keys
|
||||
|
||||
I could not come up with a UX design that wasn't too complicated or confusing for this scenario.
|
||||
|
||||
**Pros**:
|
||||
- There is full parity with the JSON file.
|
||||
|
||||
**Cons**:
|
||||
- Could not come up with a simplistic design to represent all of the use cases (which makes the settings UI not as enticing since it promotes ease of use).
|
||||
|
||||
## Conclusion
|
||||
|
||||
We considered Proposal 2, however the design became cluttered very quickly and we agreed to create two pages and start off with Proposal 1.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
![Click edit on key binding](./edit-click.png)
|
||||
|
||||
The Add new button is using the secondary color, to align with the button on the Color schemes page.
|
||||
|
||||
![Edit key binding](./edit-keys.png)
|
||||
|
||||
![Click add new](./add-click.png)
|
||||
|
||||
![Add key binding](./add-keys.png)
|
||||
|
||||
## Potential Issues
|
||||
|
||||
This design is not 1:1 with the JSON file, so actions that don't have keys will not appear on this page. Additionally, you can't add a new action without keys with this current design.
|
||||
|
||||
You also cannot specify properties on commands (like the `newTab` command) and these will have to be added through the JSON file. Considering there are only a few of these and we're planning to iterate on this and add a Command Palette page, we were okay with this decision.
|
||||
|
||||
## Resources
|
||||
|
||||
### Footnotes
|
|
@ -268,7 +268,7 @@ Today, if the deserialization of `CascadiaSettings` encounters any errors, an ex
|
|||
To get around this issue, when `CascadiaSettings` encounters a serialization error, it must internally record
|
||||
any pertinent information for that error, and return the simple `CascadiaSettings` as if nothing happened.
|
||||
The consumer must then call `CascadiaSettings::GetErrors()` and `CascadiaSettings::GetWarnings()` to properly
|
||||
understand whether an error occurred and how to present that to the user.
|
||||
understand whether an error ocurred and how to present that to the user.
|
||||
|
||||
|
||||
#### TerminalApp: Loading and Reloading Changes
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
---
|
||||
author: Carlos Zamora @carlos-zamora
|
||||
created on: 2019-08-30
|
||||
last updated: 2021-09-17
|
||||
issue id: 715
|
||||
---
|
||||
|
||||
# Keyboard Selection
|
||||
|
||||
## Abstract
|
||||
|
||||
This spec describes a new set of non-configurable keybindings that allows the user to update a selection without the use of a mouse or stylus.
|
||||
|
||||
## Inspiration
|
||||
|
||||
ConHost allows the user to modify a selection using the keyboard. Holding `Shift` allows the user to move the second selection endpoint in accordance with the arrow keys. The selection endpoint updates by one cell per key event, allowing the user to refine the selected region.
|
||||
|
||||
Mark mode allows the user to create a selection using only the keyboard, then edit it as mentioned above.
|
||||
|
||||
|
||||
## Solution Design
|
||||
|
||||
The fundamental solution design for keyboard selection is that the responsibilities between the Terminal Control and Terminal Core must be very distinct. The Terminal Control is responsible for handling user interaction and directing the Terminal Core to update the selection. The Terminal Core will need to update the selection according to the preferences of the Terminal Control.
|
||||
|
||||
Relatively recently, TerminalControl was split into `TerminalControl`, `ControlInteractivity`, and `ControlCore`. Changes made to `ControlInteractivity`, `ControlCore`, and below propagate functionality to all consumers, meaning that the WPF terminal would benefit from these changes with no additional work required.
|
||||
|
||||
### Fundamental Terminal Control Changes
|
||||
|
||||
`ControlCore::TrySendKeyEvent()` is responsible for handling the key events after key bindings are dealt with in `TermControl`. At the time of writing this spec, there are 2 cases handled in this order:
|
||||
- Clear the selection (except in a few key scenarios)
|
||||
- Send Key Event
|
||||
|
||||
The first branch will be updated to _modify_ the selection instead of usually _clearing_ it. This will happen by converting the key event into parameters to forward to `TerminalCore`, which then updates the selection appropriately.
|
||||
|
||||
#### Idea: Make keyboard selection a collection of standard keybindings
|
||||
One idea is to introduce an `updateSelection` action that conditionally works if a selection is active (similar to the `copy` action). For these key bindings, if there is no selection, the key events are forwarded to the application.
|
||||
|
||||
Thanks to Keybinding Args, there would only be 1 new command:
|
||||
| Action | Keybinding Args | Description |
|
||||
|--|--|--|
|
||||
| `updateSelection` | | If a selection exists, moves the last selection endpoint. |
|
||||
| | `Enum direction { up, down, left, right }` | The direction the selection will be moved in. |
|
||||
| | `Enum mode { char, word, view, buffer }` | The context for which to move the selection endpoint to. (defaults to `char`) |
|
||||
|
||||
|
||||
By default, the following keybindings will be set:
|
||||
```JS
|
||||
// Character Selection
|
||||
{ "command": {"action": "updateSelection", "direction": "left", "mode": "char" }, "keys": "shift+left" },
|
||||
{ "command": {"action": "updateSelection", "direction": "right", "mode": "char" }, "keys": "shift+right" },
|
||||
{ "command": {"action": "updateSelection", "direction": "up", "mode": "char" }, "keys": "shift+up" },
|
||||
{ "command": {"action": "updateSelection", "direction": "down", "mode": "char" }, "keys": "shift+down" },
|
||||
|
||||
// Word Selection
|
||||
{ "command": {"action": "updateSelection", "direction": "left", "mode": "word" }, "keys": "ctrl+shift+left" },
|
||||
{ "command": {"action": "updateSelection", "direction": "right", "mode": "word" }, "keys": "ctrl+shift+right" },
|
||||
|
||||
// Viewport Selection
|
||||
{ "command": {"action": "updateSelection", "direction": "left", "mode": "view" }, "keys": "shift+home" },
|
||||
{ "command": {"action": "updateSelection", "direction": "right", "mode": "view" }, "keys": "shift+end" },
|
||||
{ "command": {"action": "updateSelection", "direction": "up", "mode": "view" }, "keys": "shift+pgup" },
|
||||
{ "command": {"action": "updateSelection", "direction": "down", "mode": "view" }, "keys": "shift+pgdn" },
|
||||
|
||||
// Buffer Corner Selection
|
||||
{ "command": {"action": "updateSelection", "direction": "up", "mode": "buffer" }, "keys": "ctrl+shift+home" },
|
||||
{ "command": {"action": "updateSelection", "direction": "down", "mode": "buffer" }, "keys": "ctrl+shift+end" },
|
||||
```
|
||||
These are in accordance with ConHost's keyboard selection model.
|
||||
|
||||
This idea was abandoned due to several reasons:
|
||||
1. Keyboard selection should be a standard way to interact with a terminal across all consumers (i.e. WPF control, etc.)
|
||||
2. There isn't really another set of key bindings that makes sense for this. We already hardcoded <kbd>ESC</kbd> as a way to clear the selection. This is just an extension of that.
|
||||
3. Adding 12 conditionally effective key bindings takes the spot of 12 potential non-conditional key bindings. It would be nice if a different key binding could be set when the selection is not active, but that makes the settings design much more complicated.
|
||||
4. 12 new items in the command palette is also pretty excessive.
|
||||
5. If proven wrong when this is in WT Preview, we can revisit this and make them customizable then. It's better to add the ability to customize it later than take it away.
|
||||
|
||||
#### Idea: Make keyboard selection a simulation of mouse selection
|
||||
It may seem that some effort can be saved by making the keyboard selection act as a simulation of mouse selection. There is a union of mouse and keyboard activity that can be represented in a single set of selection motion interfaces that are commanded by the TermControl's Mouse/Keyboard handler and adapted into appropriate motions in the Terminal Core.
|
||||
|
||||
However, the mouse handler operates by translating a pixel coordinate on the screen to a text buffer coordinate. This would have to be rewritten and the approach was deemed unworthy.
|
||||
|
||||
|
||||
### Fundamental Terminal Core Changes
|
||||
|
||||
The Terminal Core will need to expose a `UpdateSelection()` function that is called by the keybinding handler. The following parameters will need to be passed in:
|
||||
- `enum SelectionDirection`: the direction that the selection endpoint will attempt to move to. Possible values include `Up`, `Down`, `Left`, and `Right`.
|
||||
- `enum SelectionExpansion`: the selection expansion mode that the selection endpoint will adhere to. Possible values include `Char`, `Word`, `View`, `Buffer`.
|
||||
|
||||
#### Moving by Cell
|
||||
For `SelectionExpansion = Char`, the selection endpoint will be updated according to the buffer's output pattern. For **horizontal movements**, the selection endpoint will attempt to move left or right. If a viewport boundary is hit, the endpoint will wrap appropriately (i.e.: hitting the left boundary moves it to the last cell of the line above it).
|
||||
|
||||
For **vertical movements**, the selection endpoint will attempt to move up or down. If a **viewport boundary** is hit and there is a scroll buffer, the endpoint will move and scroll accordingly by a line.
|
||||
|
||||
If a **buffer boundary** is hit, the endpoint will not move. In this case, however, the event will still be considered handled.
|
||||
|
||||
**NOTE**: An important thing to handle properly in all cases is wide glyphs. The user should not be allowed to select a portion of a wide glyph; it should be all or none of it. When calling `_ExpandWideGlyphSelection` functions, the result must be saved to the endpoint.
|
||||
|
||||
#### Moving by Word
|
||||
For `SelectionExpansion = Word`, the selection endpoint will also be updated according to the buffer's output pattern, as above. However, the selection will be updated in accordance with "chunk selection" (performing a double-click and dragging the mouse to expand the selection). For **horizontal movements**, the selection endpoint will be updated according to the `_ExpandDoubleClickSelection` functions. The result must be saved to the endpoint. As before, if a boundary is hit, the endpoint will wrap appropriately. See [Future Considerations](#FutureConsiderations) for how this will interact with line wrapping.
|
||||
|
||||
For **vertical movements**, the movement is a little more complicated than before. The selection will still respond to buffer and viewport boundaries as before. If the user is trying to move up, the selection endpoint will attempt to move up by one line, then selection will be expanded leftwards. Alternatively, if the user is trying to move down, the selection endpoint will attempt to move down by one line, then the selection will be expanded rightwards.
|
||||
|
||||
#### Moving by Viewport
|
||||
For `SelectionExpansion = View`, the selection endpoint will be updated according to the viewport's height. Horizontal movements will be updated according to the viewport's width, thus resulting in the endpoint being moved to the left/right boundary of the viewport.
|
||||
|
||||
#### Moving by Buffer
|
||||
|
||||
For `SelectionExpansion = Buffer`, the selection endpoint will be moved to the beginning or end of all the text within the buffer. If moving up or left, set the position to 0,0 (the origin of the buffer). If moving down or right, set the position to the last character in the buffer.
|
||||
|
||||
|
||||
**NOTE**: In all cases, horizontal movements attempting to move past the left/right viewport boundaries result in a wrap. Vertical movements attempting to move past the top/bottom viewport boundaries will scroll such that the selection is at the edge of the screen. Vertical movements attempting to move past the top/bottom buffer boundaries will be clamped to be within buffer boundaries.
|
||||
|
||||
Every combination of the `SelectionDirection` and `SelectionExpansion` will map to a keybinding. These pairings are shown below in the UI/UX Design --> Keybindings section.
|
||||
|
||||
**NOTE**: If `copyOnSelect` is enabled, we need to make sure we **DO NOT** update the clipboard on every change in selection. The user must explicitly choose to copy the selected text from the buffer.
|
||||
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
### Key Bindings
|
||||
|
||||
There will only be 1 new command that needs to be added:
|
||||
| Action | Keybinding Args | Description |
|
||||
|--|--|--|
|
||||
| `selectAll` | | Select the entire text buffer.
|
||||
|
||||
By default, the following key binding will be set:
|
||||
```JS
|
||||
{ "command": "selectAll", "keys": "ctrl+shift+a" },
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
Using the keyboard is generally a more accessible experience than using the mouse. Being able to modify a selection by using the keyboard is a good first step towards making selecting text more accessible.
|
||||
|
||||
### Security
|
||||
|
||||
N/A
|
||||
|
||||
### Reliability
|
||||
|
||||
With regards to the Terminal Core, the newly introduced code should rely on already existing and tested code. Thus no crash-related bugs are expected.
|
||||
|
||||
With regards to Terminal Control and the settings model, crash-related bugs are not expected. However, ensuring that the selection is updated and cleared in general use-case scenarios must be ensured.
|
||||
|
||||
### Compatibility
|
||||
|
||||
N/A
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
## Potential Issues
|
||||
|
||||
### Grapheme Clusters
|
||||
When grapheme cluster support is inevitably added to the Text Buffer, moving by "cell" is expected to move by "character" or "cluster". This is similar to how wide glyphs are handled today. Either all of it is selected, or none of it.
|
||||
|
||||
## Future considerations
|
||||
|
||||
### Word Selection Wrap
|
||||
At the time of writing this spec, expanding or moving by word is interrupted by the beginning or end of the line, regardless of the wrap flag being set. In the future, selection and the accessibility models will respect the wrap flag on the text buffer.
|
||||
|
||||
## Mark Mode
|
||||
|
||||
This functionality will be expanded to create a feature similar to Mark Mode. This will allow a user to create a selection using only the keyboard.
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
- https://blogs.windows.com/windowsdeveloper/2014/10/07/console-improvements-in-the-windows-10-technical-preview/
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
This document outlines the roadmap towards delivering Windows Terminal 2.0.
|
||||
This document outlines the roadmap towards delivering Windows Terminal 2.0 by Winter 2021.
|
||||
|
||||
## Milestones
|
||||
|
||||
|
@ -29,11 +29,11 @@ Below is the schedule for when milestones will be included in release builds of
|
|||
| 2021-03-01 | [1.7] in Windows Terminal Preview<br>[1.6] in Windows Terminal | [Windows Terminal Preview 1.7 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-7-release/) |
|
||||
| 2021-04-14 | [1.8] in Windows Terminal Preview<br>[1.7] in Windows Terminal | [Windows Terminal Preview 1.8 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-8-release/) |
|
||||
| 2021-05-31 | [1.9] in Windows Terminal Preview<br>[1.8] in Windows Terminal | [Windows Terminal Preview 1.9 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-9-release/) |
|
||||
| 2021-07-14 | [1.10] in Windows Terminal Preview<br>[1.9] in Windows Terminal | [Windows Terminal Preview 1.10 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-10-release/) |
|
||||
| 2021-08-31 | [1.11] in Windows Terminal Preview<br>[1.10] in Windows Terminal | [Windows Terminal Preview 1.11 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-11-release/) |
|
||||
| 2021-10-20 | [1.12] in Windows Terminal Preview<br>[1.11] in Windows Terminal | [Windows Terminal Preview 1.12 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-12-release/) |
|
||||
| | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
|
||||
| | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
|
||||
| 2021-07-31 | 1.10 in Windows Terminal Preview<br>[1.9] in Windows Terminal | |
|
||||
| 2021-08-30 | 1.11 in Windows Terminal Preview<br>1.10 in Windows Terminal | |
|
||||
| 2021-10-31 | 1.12 in Windows Terminal Preview<br>1.11 in Windows Terminal | |
|
||||
| 2021-11-30 | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
|
||||
| 2021-12-31 | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
|
||||
|
||||
## Issue Triage & Prioritization
|
||||
|
||||
|
@ -49,32 +49,28 @@ The following are a list of the key scenarios we're aiming to deliver for Termin
|
|||
|
||||
> 👉 Note: There are many other features that don't fit within 2.0, but will be re-assessed and prioritized for 3.0, the plan for which will be published in 2021.
|
||||
|
||||
| Priority\* | Scenario | Description/Notes | State |
|
||||
| ---------- | -------- | ----------------- | ----- |
|
||||
| 0 | Settings UI | A user interface that connects to settings.json. This provides a way for people to edit their settings without having to edit a JSON file.<br><br>Issue: [#1564]<br>Specs: [#6720], [#6904]<br>Implementation: [#7283], [#7370], [#8048] | ✔️ |
|
||||
| 0 | Command palette | A popup menu to list possible actions and commands.<br><br>Issues: [#5400], [#2046]<br>Spec: [#2193]<br>Implementation: [#6635] | ✔️ |
|
||||
| 1 | Tab tear-off | The ability to tear a tab out of the current window and spawn a new window or attach it to a separate window.<br><br>Issue: [#1256], [#5000]<br>Spec: [#2080], [#7240] | 📝 |
|
||||
| 1 | Clickable links | Hyperlinking any links that appear in the text buffer. When clicking on the link, the link will open in your default browser.<br><br>Issue: [#574]<br>Implementation: [#7251] | ✔️ |
|
||||
| 1 | Default terminal | If a command-line application is spawned, it should open in Windows Terminal (if installed) or your preferred terminal<br><br>Issue: [#492]<br>Spec: [#2080], [#7414] | ✔️ |
|
||||
| 1 | Overall theme support | Tab coloring, title bar coloring, pane border coloring, pane border width, definition of what makes a theme<br><br>Issue: [#3327]<br>Spec: [#5772] | 🦶 |
|
||||
| 1 | Open profile elevated | Configure profiles to always open elevated (if Terminal was run unelevated)<br><br>Issue: [#5000], [#632]<br>Spec: [#8455] | 📝 |
|
||||
| 1 | Open tab in existing window | Open new tabs in existing Terminal windows<br><br>Issue: [#5000], [#4472]<br>Spec: [#8135] | ✔️ |
|
||||
| 1 | Traditional opacity | Have a transparent background without the acrylic blur.<br><br>Issue: [#603] | ✔️ |
|
||||
| 2 | SnapOnOutput, scroll lock | Pause output or scrolling on click.<br><br>Issue: [#980]<br>Spec: [#2529]<br>Implementation: [#6062] | ✔️ |
|
||||
| 2 | Infinite scrollback | Have an infinite history for the text buffer.<br><br>Issue: [#1410] | 🦶 |
|
||||
| 2 | Pane management | All issues listed out in the original issue. Some features include pane resizing with mouse, pane zooming, and opening a pane by prompting which profile to use.<br><br>Issue: [#1000] | 📝 |
|
||||
| 2 | Theme marketplace | Marketplace for creation and distribution of themes.<br>Dependent on overall theming | 🦶 |
|
||||
| 2 | Jump list | Show profiles from task bar (on right click)/start menu.<br><br>Issue: [#576]<br>Implementation: [#7515] | ✔️ |
|
||||
| 2 | Open with multiple tabs | A setting that allows Windows Terminal to launch with a specific tab configuration (not using only command line arguments).<br><br>Issue: [#756] | ✔️ |
|
||||
| 3 | Open in Windows Terminal | Functionality to right click on a file or folder and select Open in Windows Terminal.<br><br>Issue: [#1060]<br>Implementation: [#6100] | ✔️ |
|
||||
| 3 | Session restoration | Launch Windows Terminal and the previous session is restored with the proper tab and pane configuration and starting directories.<br><br>Issues: [#961], [#960], [#766] | ✔️ |
|
||||
| 3 | Quake mode | Provide a quick launch terminal that appears and disappears when a hotkey is pressed.<br><br>Issue: [#653] | ✔️ |
|
||||
| 3 | Settings migration infrastructure | Migrate people's settings without breaking them. Hand-in-hand with settings UI. | 🦶 |
|
||||
| 3 | Pointer bindings | Provide settings that can be bound to the mouse.<br><br>Issue: [#1553] | 🦶 |
|
||||
|
||||
* 📝: The feature is currently in progress
|
||||
* ✔️: The feature is complete and shipped in a Preview build
|
||||
* 🦶: The feature is at risk of being punted to a future release cycle (beyond 2.0)
|
||||
| Priority\* | Scenario | Description/Notes |
|
||||
| ---------- | -------- | ----------------- |
|
||||
| 0 | Settings UI | A user interface that connects to settings.json. This provides a way for people to edit their settings without having to edit a JSON file.<br><br>Issue: [#1564]<br>Specs: [#6720], [#6904]<br>Implementation: [#7283], [#7370], [#8048] |
|
||||
| 0 | Command palette | A popup menu to list possible actions and commands.<br><br>Issues: [#5400], [#2046]<br>Spec: [#2193]<br>Implementation: [#6635] |
|
||||
| 1 | Tab tear-off | The ability to tear a tab out of the current window and spawn a new window or attach it to a separate window.<br><br>Issue: [#1256], [#5000]<br>Spec: [#2080], [#7240] |
|
||||
| 1 | Clickable links | Hyperlinking any links that appear in the text buffer. When clicking on the link, the link will open in your default browser.<br><br>Issue: [#574]<br>Implementation: [#7251] |
|
||||
| 1 | Default terminal | If a command-line application is spawned, it should open in Windows Terminal (if installed) or your preferred terminal<br><br>Issue: [#492]<br>Spec: [#2080], [#7414] |
|
||||
| 1 | Overall theme support | Tab coloring, title bar coloring, pane border coloring, pane border width, definition of what makes a theme<br><br>Issue: [#3327]<br>Spec: [#5772] |
|
||||
| 1 | Open profile elevated | Configure profiles to always open elevated (if Terminal was run unelevated)<br><br>Issue: [#5000], [#632]<br>Spec: [#8455] |
|
||||
| 1 | Open tab in existing window | Open new tabs in existing Terminal windows<br><br>Issue: [#5000], [#4472]<br>Spec: [#8135] |
|
||||
| 1 | Traditional opacity | Have a transparent background without the acrylic blur.<br><br>Issue: [#603] <br>**Current State**: Blocked on WinUI 3.0 |
|
||||
| 2 | SnapOnOutput, scroll lock | Pause output or scrolling on click.<br><br>Issue: [#980]<br>Spec: [#2529]<br>Implementation: [#6062] |
|
||||
| 2 | Infinite scrollback | Have an infinite history for the text buffer.<br><br>Issue: [#1410] |
|
||||
| 2 | Pane management | All issues listed out in the original issue. Some features include pane resizing with mouse, pane zooming, and opening a pane by prompting which profile to use.<br><br>Issue: [#1000] |
|
||||
| 2 | Theme marketplace | Marketplace for creation and distribution of themes.<br>Dependent on overall theming |
|
||||
| 2 | Jump list | Show profiles from task bar (on right click)/start menu.<br><br>Issue: [#576]<br>Implementation: [#7515] |
|
||||
| 2 | Open with multiple tabs | A setting that allows Windows Terminal to launch with a specific tab configuration (not using only command line arguments).<br><br>Issue: [#756] |
|
||||
| 3 | Open in Windows Terminal | Functionality to right click on a file or folder and select Open in Windows Terminal.<br><br>Issue: [#1060]<br>Implementation: [#6100] |
|
||||
| 3 | Session restoration | Launch Windows Terminal and the previous session is restored with the proper tab and pane configuration and starting directories.<br><br>Issues: [#961], [#960], [#766] |
|
||||
| 3 | Quake mode | Provide a quick launch terminal that appears and disappears when a hotkey is pressed.<br><br>Issue: [#653] |
|
||||
| 3 | Settings migration infrastructure | Migrate people's settings without breaking them. Hand-in-hand with settings UI. |
|
||||
| 3 | Pointer bindings | Provide settings that can be bound to the mouse.<br><br>Issue: [#1553] |
|
||||
|
||||
Feature Notes:
|
||||
|
||||
|
@ -93,10 +89,6 @@ Feature Notes:
|
|||
[1.7]: https://github.com/microsoft/terminal/milestone/32
|
||||
[1.8]: https://github.com/microsoft/terminal/milestone/33
|
||||
[1.9]: https://github.com/microsoft/terminal/milestone/34
|
||||
[1.10]: https://github.com/microsoft/terminal/milestone/35
|
||||
[1.11]: https://github.com/microsoft/terminal/milestone/36
|
||||
[1.12]: https://github.com/microsoft/terminal/milestone/38
|
||||
[1.13]: https://github.com/microsoft/terminal/milestone/39
|
||||
[2.0]: https://github.com/microsoft/terminal/milestone/22
|
||||
[#1564]: https://github.com/microsoft/terminal/issues/1564
|
||||
[#6720]: https://github.com/microsoft/terminal/pull/6720
|
||||
|
|
|
@ -4,7 +4,7 @@ This was originally imported by @Austin-Lamb in December 2020.
|
|||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
|
||||
|
||||
## What should be done to update this in the future?
|
||||
|
||||
|
@ -12,4 +12,4 @@ That provenance file is automatically read and inventoried by Microsoft systems
|
|||
2. Take the parts you want, but leave most of it behind since it's HUGE and will bloat the repo to take it all. At the time of this writing, we only use small_vector.hpp and its dependencies as a header-only library.
|
||||
3. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in a version-specific subdirectory below this readme.
|
||||
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
|
||||
4. Submit the pull.
|
||||
4. Submit the pull.
|
|
@ -4,7 +4,7 @@ This was originally imported by @miniksa in January 2020.
|
|||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
|
||||
|
||||
## What should be done to update this in the future?
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ This was originally imported by @miniksa in March 2020.
|
|||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
|
||||
|
||||
## What should be done to update this in the future?
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ This was originally imported by @DHowett-MSFT in April 2020.
|
|||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
|
||||
|
||||
## What should be done to update this in the future?
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ This was originally imported by @PankajBhojwani in September 2020.
|
|||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
|
||||
|
||||
## What should be done to update this in the future?
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ This was originally imported by @miniksa in March 2020.
|
|||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
|
||||
|
||||
## What should be done to update this in the future?
|
||||
|
||||
|
|
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors
|
||||
|
||||
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.
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"Registrations": [
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/imneme/pcg-cpp",
|
||||
"commitHash": "ffd522e7188bef30a00c74dc7eb9de5faff90092"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 1
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
// PCG Random Number Generation for C++
|
||||
//
|
||||
// Copyright 2014-2019 Melissa O'Neill <oneill@pcg-random.org>,
|
||||
// and the PCG Project contributors.
|
||||
//
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (provided in
|
||||
// LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0)
|
||||
// or under the MIT license (provided in LICENSE-MIT.txt and at
|
||||
// http://opensource.org/licenses/MIT), at your option. This file may not
|
||||
// be copied, modified, or distributed except according to those terms.
|
||||
//
|
||||
// Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either
|
||||
// express or implied. See your chosen license for details.
|
||||
//
|
||||
// For additional information about the PCG random number generation scheme,
|
||||
// visit http://www.pcg-random.org/.
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Leonard Hecker <lhecker@microsoft.com>:
|
||||
// The following contents are an extract of pcg_engines::oneseq_dxsm_64_32
|
||||
// reduced down to the bare essentials, while retaining base functionality.
|
||||
|
||||
namespace pcg_engines {
|
||||
class oneseq_dxsm_64_32 {
|
||||
using xtype = uint32_t;
|
||||
using itype = uint64_t;
|
||||
|
||||
itype state_;
|
||||
|
||||
static constexpr uint64_t multiplier() {
|
||||
return 6364136223846793005ULL;
|
||||
}
|
||||
|
||||
static constexpr uint64_t increment() {
|
||||
return 1442695040888963407ULL;
|
||||
}
|
||||
|
||||
static itype bump(itype state) {
|
||||
return state * multiplier() + increment();
|
||||
}
|
||||
|
||||
itype base_generate0() {
|
||||
itype old_state = state_;
|
||||
state_ = bump(state_);
|
||||
return old_state;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit oneseq_dxsm_64_32(itype state = 0xcafef00dd15ea5e5ULL) : state_(bump(state + increment())) {
|
||||
}
|
||||
|
||||
// Returns a value in the interval [0, UINT32_MAX].
|
||||
xtype operator()() {
|
||||
constexpr auto xtypebits = uint8_t(sizeof(xtype) * 8);
|
||||
constexpr auto itypebits = uint8_t(sizeof(itype) * 8);
|
||||
|
||||
auto internal = base_generate0();
|
||||
auto hi = xtype(internal >> (itypebits - xtypebits));
|
||||
auto lo = xtype(internal);
|
||||
|
||||
lo |= 1;
|
||||
hi ^= hi >> (xtypebits / 2);
|
||||
hi *= xtype(multiplier());
|
||||
hi ^= hi >> (3 * (xtypebits / 4));
|
||||
hi *= lo;
|
||||
return hi;
|
||||
}
|
||||
|
||||
// Returns a value in the interval [0, upper_bound).
|
||||
xtype operator()(xtype upper_bound) {
|
||||
uint32_t threshold = (UINT64_MAX + uint32_t(1) - upper_bound) % upper_bound;
|
||||
for (;;) {
|
||||
auto r = operator()();
|
||||
if (r >= threshold)
|
||||
return r % upper_bound;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -4,4 +4,4 @@ This manifest anchors our usage of rgb.txt from the X11 distribution.
|
|||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<ProjectGuid>{96274800-9574-423E-892A-909FBE2AC8BE}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>EchoCon</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.22000.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
@ -163,4 +163,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
|
@ -27,7 +27,7 @@
|
|||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22000.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
|
|
@ -140,12 +140,12 @@
|
|||
<!-- **END VC LIBS HACK** -->
|
||||
|
||||
<!-- This is required to get the package dependency in the AppXManifest. -->
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
</Target>
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include "MyPage.h"
|
||||
#include <LibraryResources.h>
|
||||
#include "MyPage.g.cpp"
|
||||
#include "MySettings.h"
|
||||
#include "..\..\..\src\cascadia\UnitTests_Control\MockControlSettings.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
|
@ -26,24 +26,17 @@ namespace winrt::SampleApp::implementation
|
|||
|
||||
void MyPage::Create()
|
||||
{
|
||||
auto settings = winrt::make_self<implementation::MySettings>();
|
||||
TerminalConnection::EchoConnection conn{};
|
||||
auto settings = winrt::make_self<ControlUnitTests::MockControlSettings>();
|
||||
|
||||
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
|
||||
winrt::hstring{},
|
||||
L"",
|
||||
nullptr,
|
||||
32,
|
||||
80,
|
||||
winrt::guid()) };
|
||||
|
||||
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
|
||||
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
|
||||
TerminalConnection::ConnectionInformation connectInfo{ myClass, connectionSettings };
|
||||
|
||||
TerminalConnection::ITerminalConnection conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectInfo) };
|
||||
Control::TermControl control{ *settings, conn };
|
||||
|
||||
InProcContent().Children().Append(control);
|
||||
|
||||
// Once the control loads (and not before that), write some text for debugging:
|
||||
control.Initialized([conn](auto&&, auto&&) {
|
||||
conn.WriteInput(L"This TermControl is hosted in-proc...");
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -19,15 +19,10 @@
|
|||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox x:Name="GuidInput"
|
||||
Width="400"
|
||||
PlaceholderText="{}{guid here}" />
|
||||
<Button Grid.Row="0">
|
||||
Create
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
<Button x:Name="TabRow"
|
||||
Grid.Row="0">
|
||||
"Tabs"
|
||||
</Button>
|
||||
|
||||
<Grid x:Name="TabContent"
|
||||
Grid.Row="1"
|
||||
|
|
|
@ -4,8 +4,6 @@ Licensed under the MIT license.
|
|||
--*/
|
||||
#pragma once
|
||||
#include "../../inc/cppwinrt_utils.h"
|
||||
#include "../types/inc/colorTable.hpp"
|
||||
|
||||
#include <DefaultSettings.h>
|
||||
#include <conattrs.hpp>
|
||||
#include "MySettings.g.h"
|
||||
|
@ -14,6 +12,9 @@ namespace winrt::SampleApp::implementation
|
|||
{
|
||||
struct MySettings : MySettingsT<MySettings>
|
||||
{
|
||||
public:
|
||||
MySettings() = default;
|
||||
|
||||
// --------------------------- Core Settings ---------------------------
|
||||
// All of these settings are defined in ICoreSettings.
|
||||
|
||||
|
@ -45,7 +46,7 @@ namespace winrt::SampleApp::implementation
|
|||
|
||||
WINRT_PROPERTY(winrt::hstring, ProfileName);
|
||||
WINRT_PROPERTY(bool, UseAcrylic, false);
|
||||
WINRT_PROPERTY(double, Opacity, .5);
|
||||
WINRT_PROPERTY(double, TintOpacity, 0.5);
|
||||
WINRT_PROPERTY(winrt::hstring, Padding, DEFAULT_PADDING);
|
||||
WINRT_PROPERTY(winrt::hstring, FontFace, L"Consolas");
|
||||
WINRT_PROPERTY(int32_t, FontSize, DEFAULT_FONT_SIZE);
|
||||
|
@ -87,14 +88,6 @@ namespace winrt::SampleApp::implementation
|
|||
winrt::Microsoft::Terminal::Core::Color GetColorTableEntry(int32_t index) noexcept { return _ColorTable.at(index); }
|
||||
std::array<winrt::Microsoft::Terminal::Core::Color, 16> ColorTable() { return _ColorTable; }
|
||||
void ColorTable(std::array<winrt::Microsoft::Terminal::Core::Color, 16> /*colors*/) {}
|
||||
|
||||
MySettings()
|
||||
{
|
||||
const auto campbellSpan = ::Microsoft::Console::Utils::CampbellColorTable();
|
||||
std::transform(campbellSpan.begin(), campbellSpan.end(), _ColorTable.begin(), [](auto&& color) {
|
||||
return static_cast<winrt::Microsoft::Terminal::Core::Color>(til::color{ color });
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -147,13 +147,13 @@
|
|||
<!-- ========================= Globals ======================== -->
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
|
||||
</Target>
|
||||
|
||||
|
|
|
@ -48,10 +48,6 @@
|
|||
<Private>true</Private>
|
||||
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
|
||||
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
@ -80,13 +76,13 @@
|
|||
</ItemGroup>
|
||||
|
||||
|
||||
<Import Project="$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
|
||||
</Target>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.7.0-prerelease.210913003" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
|
||||
</packages>
|
||||
|
|
|
@ -118,33 +118,16 @@
|
|||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
BODGY
|
||||
|
||||
The wapproj `GetResolvedWinMD` target tries to get a winmd from every cppwinrt
|
||||
executable we put in the package. But we DON'T produce a winmd. This makes the
|
||||
FastUpToDate check fail every time, and leads to the whole wapproj build
|
||||
running even if you're just f5'ing the package. EVEN AFTER A SUCCESSFUL BUILD.
|
||||
|
||||
Setting GenerateWindowsMetadata=false is enough to tell the build system that
|
||||
we don't produce one, and get it off our backs.
|
||||
-->
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets" Condition="Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets'))" />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?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.210309.3" targetFramework="native" />
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.7.0-prerelease.210913003" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" />
|
||||
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />
|
||||
</packages>
|
||||
|
|
|
@ -13,15 +13,6 @@
|
|||
<EventProvider Id="EventProvider_TerminalRemoting" Name="d6f04aad-629f-539a-77c1-73f5c3e4aa7b" />
|
||||
<EventProvider Id="EventProvider_TerminalDirectX" Name="c93e739e-ae50-5a14-78e7-f171e947535d" />
|
||||
<EventProvider Id="EventProvider_TerminalUIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
|
||||
<!-- Console providers here -->
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Launcher" Name="770aa552-671a-5e97-579b-151709ec0dbd"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Host" Name="fe1ff234-1f09-50a8-d38d-c44fab43e818"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Server" Name="1A541C01-589A-496E-85A7-A9E02170166D"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser" Name="c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Render.VtEngine" Name="c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.UIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
|
||||
|
||||
<!-- Profile for General Terminal logging -->
|
||||
<Profile Id="Terminal.Verbose.File" Name="Terminal" Description="Terminal" LoggingMode="File" DetailLevel="Verbose">
|
||||
<Collectors>
|
||||
<EventCollectorId Value="EventCollector_Terminal">
|
||||
|
@ -41,27 +32,5 @@
|
|||
<Profile Id="Terminal.Light.File" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="File" DetailLevel="Light" />
|
||||
<Profile Id="Terminal.Verbose.Memory" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="Memory" DetailLevel="Verbose" />
|
||||
<Profile Id="Terminal.Light.Memory" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="Memory" DetailLevel="Light" />
|
||||
|
||||
<!-- Profile for DefTerm logging. Includes some conhost logging. -->
|
||||
<Profile Id="DefTerm.Verbose.File" Name="DefTerm" Description="DefTerm" LoggingMode="File" DetailLevel="Verbose">
|
||||
<Collectors>
|
||||
<EventCollectorId Value="EventCollector_Terminal">
|
||||
<EventProviders>
|
||||
<EventProviderId Value="EventProvider_TerminalControl" />
|
||||
<EventProviderId Value="EventProvider_TerminalConnection" />
|
||||
<EventProviderId Value="EventProvider_TerminalSettingsModel" />
|
||||
<EventProviderId Value="EventProvider_TerminalApp" />
|
||||
<EventProviderId Value="EventProvider_TerminalWin32Host" />
|
||||
<EventProviderId Value="EventProvider_TerminalRemoting" />
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Launcher" />
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Host" />
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Server" />
|
||||
</EventProviders>
|
||||
</EventCollectorId>
|
||||
</Collectors>
|
||||
</Profile>
|
||||
<Profile Id="DefTerm.Light.File" Name="DefTerm" Description="DefTerm" Base="DefTerm.Verbose.File" LoggingMode="File" DetailLevel="Light" />
|
||||
<Profile Id="DefTerm.Verbose.Memory" Name="DefTerm" Description="DefTerm" Base="DefTerm.Verbose.File" LoggingMode="Memory" DetailLevel="Verbose" />
|
||||
<Profile Id="DefTerm.Light.Memory" Name="DefTerm" Description="DefTerm" Base="DefTerm.Verbose.File" LoggingMode="Memory" DetailLevel="Light" />
|
||||
</Profiles>
|
||||
</WindowsPerformanceRecorder>
|
||||
</WindowsPerformanceRecorder>
|
|
@ -12,30 +12,8 @@ static_assert(alignof(TextAttribute) == 2);
|
|||
// Ensure that we can memcpy() and memmove() the struct for performance.
|
||||
static_assert(std::is_trivially_copyable_v<TextAttribute>);
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr std::array<TextColor, 16> s_initLegacyColorMap(const BYTE defaultIndex)
|
||||
{
|
||||
std::array<TextColor, 16> legacyColorMap;
|
||||
for (auto i = 0u; i < legacyColorMap.size(); i++)
|
||||
{
|
||||
const auto legacyIndex = TextColor::TransposeLegacyIndex(i);
|
||||
gsl::at(legacyColorMap, i) = i == defaultIndex ? TextColor{} : TextColor{ legacyIndex, true };
|
||||
}
|
||||
return legacyColorMap;
|
||||
}
|
||||
|
||||
BYTE s_legacyDefaultForeground = 7;
|
||||
BYTE s_legacyDefaultBackground = 0;
|
||||
BYTE s_ansiDefaultForeground = 7;
|
||||
BYTE s_ansiDefaultBackground = 0;
|
||||
}
|
||||
|
||||
// These maps allow for an efficient conversion from a legacy attribute index
|
||||
// to a TextColor with the corresponding ANSI index, also taking into account
|
||||
// the legacy index values that need to be converted to a default TextColor.
|
||||
std::array<TextColor, 16> TextAttribute::s_legacyForegroundColorMap = s_initLegacyColorMap(7);
|
||||
std::array<TextColor, 16> TextAttribute::s_legacyBackgroundColorMap = s_initLegacyColorMap(0);
|
||||
BYTE TextAttribute::s_legacyDefaultForeground = 7;
|
||||
BYTE TextAttribute::s_legacyDefaultBackground = 0;
|
||||
|
||||
// Routine Description:
|
||||
// - Sets the legacy attributes which map to and from the default colors.
|
||||
|
@ -45,22 +23,8 @@ std::array<TextColor, 16> TextAttribute::s_legacyBackgroundColorMap = s_initLega
|
|||
// - None
|
||||
void TextAttribute::SetLegacyDefaultAttributes(const WORD defaultAttributes) noexcept
|
||||
{
|
||||
// First we reset the current default color map entries to what they should
|
||||
// be for a regular translation from a legacy index to an ANSI TextColor.
|
||||
gsl::at(s_legacyForegroundColorMap, s_legacyDefaultForeground) = TextColor{ s_ansiDefaultForeground, true };
|
||||
gsl::at(s_legacyBackgroundColorMap, s_legacyDefaultBackground) = TextColor{ s_ansiDefaultBackground, true };
|
||||
|
||||
// Then we save the new default attribute values and their corresponding
|
||||
// ANSI translations. We use the latter values to more efficiently handle
|
||||
// the "VT Quirk" conversion below.
|
||||
s_legacyDefaultForeground = defaultAttributes & FG_ATTRS;
|
||||
s_legacyDefaultBackground = (defaultAttributes & BG_ATTRS) >> 4;
|
||||
s_ansiDefaultForeground = TextColor::TransposeLegacyIndex(s_legacyDefaultForeground);
|
||||
s_ansiDefaultBackground = TextColor::TransposeLegacyIndex(s_legacyDefaultBackground);
|
||||
|
||||
// Finally we set the new default color map entries.
|
||||
gsl::at(s_legacyForegroundColorMap, s_legacyDefaultForeground) = TextColor{};
|
||||
gsl::at(s_legacyBackgroundColorMap, s_legacyDefaultBackground) = TextColor{};
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -91,13 +55,13 @@ TextAttribute TextAttribute::StripErroneousVT16VersionsOfLegacyDefaults(const Te
|
|||
const auto bg{ attribute.GetBackground() };
|
||||
auto copy{ attribute };
|
||||
if (fg.IsIndex16() &&
|
||||
attribute.IsBold() == WI_IsFlagSet(s_ansiDefaultForeground, FOREGROUND_INTENSITY) &&
|
||||
fg.GetIndex() == (s_ansiDefaultForeground & ~FOREGROUND_INTENSITY))
|
||||
attribute.IsBold() == WI_IsFlagSet(s_legacyDefaultForeground, FOREGROUND_INTENSITY) &&
|
||||
fg.GetIndex() == (s_legacyDefaultForeground & ~FOREGROUND_INTENSITY))
|
||||
{
|
||||
// We don't want to turn 1;37m into 39m (or even 1;39m), as this was meant to mimic a legacy color.
|
||||
copy.SetDefaultForeground();
|
||||
}
|
||||
if (bg.IsIndex16() && bg.GetIndex() == s_ansiDefaultBackground)
|
||||
if (bg.IsIndex16() && bg.GetIndex() == s_legacyDefaultBackground)
|
||||
{
|
||||
copy.SetDefaultBackground();
|
||||
}
|
||||
|
@ -128,22 +92,22 @@ bool TextAttribute::IsLegacy() const noexcept
|
|||
// - Calculates rgb colors based off of current color table and active modification attributes.
|
||||
// Arguments:
|
||||
// - colorTable: the current color table rgb values.
|
||||
// - defaultFgIndex: the color table index of the default foreground color.
|
||||
// - defaultBgIndex: the color table index of the default background color.
|
||||
// - defaultFgColor: the default foreground color rgb value.
|
||||
// - defaultBgColor: the default background color rgb value.
|
||||
// - reverseScreenMode: true if the screen mode is reversed.
|
||||
// - blinkingIsFaint: true if blinking should be interpreted as faint. (defaults to false)
|
||||
// - boldIsBright: true if "bold" should be interpreted as bright. (defaults to true)
|
||||
// Return Value:
|
||||
// - the foreground and background colors that should be displayed.
|
||||
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const std::array<COLORREF, TextColor::TABLE_SIZE>& colorTable,
|
||||
const size_t defaultFgIndex,
|
||||
const size_t defaultBgIndex,
|
||||
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultFgColor,
|
||||
const COLORREF defaultBgColor,
|
||||
const bool reverseScreenMode,
|
||||
const bool blinkingIsFaint,
|
||||
const bool boldIsBright) const noexcept
|
||||
{
|
||||
auto fg = _foreground.GetColor(colorTable, defaultFgIndex, boldIsBright && IsBold());
|
||||
auto bg = _background.GetColor(colorTable, defaultBgIndex);
|
||||
auto fg = _foreground.GetColor(colorTable, defaultFgColor, boldIsBright && IsBold());
|
||||
auto bg = _background.GetColor(colorTable, defaultBgColor);
|
||||
if (IsFaint() || (IsBlinking() && blinkingIsFaint))
|
||||
{
|
||||
fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two.
|
||||
|
|
|
@ -41,8 +41,8 @@ public:
|
|||
|
||||
explicit constexpr TextAttribute(const WORD wLegacyAttr) noexcept :
|
||||
_wAttrLegacy{ gsl::narrow_cast<WORD>(wLegacyAttr & META_ATTRS) },
|
||||
_foreground{ gsl::at(s_legacyForegroundColorMap, wLegacyAttr & FG_ATTRS) },
|
||||
_background{ gsl::at(s_legacyBackgroundColorMap, (wLegacyAttr & BG_ATTRS) >> 4) },
|
||||
_foreground{ s_LegacyIndexOrDefault(wLegacyAttr & FG_ATTRS, s_legacyDefaultForeground) },
|
||||
_background{ s_LegacyIndexOrDefault((wLegacyAttr & BG_ATTRS) >> 4, s_legacyDefaultBackground) },
|
||||
_extendedAttrs{ ExtendedAttributes::Normal },
|
||||
_hyperlinkId{ 0 }
|
||||
{
|
||||
|
@ -64,9 +64,9 @@ public:
|
|||
static TextAttribute StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept;
|
||||
WORD GetLegacyAttributes() const noexcept;
|
||||
|
||||
std::pair<COLORREF, COLORREF> CalculateRgbColors(const std::array<COLORREF, TextColor::TABLE_SIZE>& colorTable,
|
||||
const size_t defaultFgIndex,
|
||||
const size_t defaultBgIndex,
|
||||
std::pair<COLORREF, COLORREF> CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultFgColor,
|
||||
const COLORREF defaultBgColor,
|
||||
const bool reverseScreenMode = false,
|
||||
const bool blinkingIsFaint = false,
|
||||
const bool boldIsBright = true) const noexcept;
|
||||
|
@ -167,8 +167,13 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
static std::array<TextColor, 16> s_legacyForegroundColorMap;
|
||||
static std::array<TextColor, 16> s_legacyBackgroundColorMap;
|
||||
static constexpr TextColor s_LegacyIndexOrDefault(const BYTE requestedIndex, const BYTE defaultIndex)
|
||||
{
|
||||
return requestedIndex == defaultIndex ? TextColor{} : TextColor{ requestedIndex, true };
|
||||
}
|
||||
|
||||
static BYTE s_legacyDefaultForeground;
|
||||
static BYTE s_legacyDefaultBackground;
|
||||
|
||||
uint16_t _wAttrLegacy; // sizeof: 2, alignof: 2
|
||||
uint16_t _hyperlinkId; // sizeof: 2, alignof: 2
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
#include "precomp.h"
|
||||
#include "TextColor.h"
|
||||
|
||||
#include <til/bit.h>
|
||||
|
||||
// clang-format off
|
||||
|
||||
// A table mapping 8-bit RGB colors, in the form RRRGGGBB,
|
||||
|
@ -32,7 +30,7 @@ constexpr std::array<BYTE, 256> CompressedRgbToIndex16 = {
|
|||
// A table mapping indexed colors from the 256-color palette,
|
||||
// down to one of the 16 colors in the legacy palette.
|
||||
constexpr std::array<BYTE, 256> Index256ToIndex16 = {
|
||||
0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
0, 1, 1, 1, 9, 9, 2, 1, 1, 1, 1, 1, 2, 2, 3, 3,
|
||||
3, 3, 2, 2, 11, 11, 3, 3, 10, 10, 11, 11, 11, 11, 10, 10,
|
||||
10, 10, 11, 11, 5, 5, 5, 5, 1, 1, 8, 8, 1, 1, 9, 9,
|
||||
|
@ -52,9 +50,6 @@ constexpr std::array<BYTE, 256> Index256ToIndex16 = {
|
|||
|
||||
// clang-format on
|
||||
|
||||
// We should only need 4B for TextColor. Any more than that is just waste.
|
||||
static_assert(sizeof(TextColor) == 4);
|
||||
|
||||
bool TextColor::CanBeBrightened() const noexcept
|
||||
{
|
||||
return IsIndex16() || IsDefault();
|
||||
|
@ -139,18 +134,19 @@ void TextColor::SetDefault() noexcept
|
|||
// Arguments:
|
||||
// - colorTable: The table of colors we should use to look up the value of
|
||||
// an indexed attribute from.
|
||||
// - defaultIndex: The color table index to use if we're a default attribute.
|
||||
// - defaultColor: The color value to use if we're a default attribute.
|
||||
// - brighten: if true, we'll brighten a dark color table index.
|
||||
// Return Value:
|
||||
// - a COLORREF containing the real value of this TextColor.
|
||||
COLORREF TextColor::GetColor(const std::array<COLORREF, TextColor::TABLE_SIZE>& colorTable, const size_t defaultIndex, bool brighten) const noexcept
|
||||
COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultColor,
|
||||
bool brighten) const noexcept
|
||||
{
|
||||
if (IsDefault())
|
||||
{
|
||||
const auto defaultColor = til::at(colorTable, defaultIndex);
|
||||
|
||||
if (brighten)
|
||||
{
|
||||
FAIL_FAST_IF(colorTable.size() < 16);
|
||||
// See MSFT:20266024 for context on this fix.
|
||||
// Additionally todo MSFT:20271956 to fix this better for 19H2+
|
||||
// If we're a default color, check to see if the defaultColor exists
|
||||
|
@ -160,61 +156,6 @@ COLORREF TextColor::GetColor(const std::array<COLORREF, TextColor::TABLE_SIZE>&
|
|||
// (Settings::_DefaultForeground==INVALID_COLOR, and the index
|
||||
// from _wFillAttribute is being used instead.)
|
||||
// If we find a match, return instead the bright version of this color
|
||||
|
||||
static_assert(sizeof(COLORREF) * 8 == 32, "The vectorized code broke. If you can't fix COLORREF, just remove the vectorized code.");
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
|
||||
#ifdef __AVX2__
|
||||
// I wrote this vectorized code one day, because the sun was shining so nicely.
|
||||
// There's no other reason for this to exist here, except for being pretty.
|
||||
// This code implements the exact same for loop you can find below, but is ~3x faster.
|
||||
//
|
||||
// A brief explanation for people unfamiliar with vectorized instructions:
|
||||
// Vectorized instructions, like "SSE" or "AVX", allow you to run
|
||||
// common operations like additions, multiplications, comparisons,
|
||||
// or bitwise operations concurrently on multiple values at once.
|
||||
//
|
||||
// We want to find the given defaultColor in the first 8 values of colorTable.
|
||||
// Coincidentally a COLORREF is a DWORD and 8 of them are exactly 256 bits.
|
||||
// -- The size of a single AVX register.
|
||||
//
|
||||
// Thus, the code works like this:
|
||||
// 1. Load all 8 DWORDs at once into one register
|
||||
// 2. Set the same defaultColor 8 times in another register
|
||||
// 3. Compare all 8 values at once
|
||||
// The result is either 0xffffffff or 0x00000000.
|
||||
// 4. Extract the most significant bit of each DWORD
|
||||
// Assuming that no duplicate colors exist in colorTable,
|
||||
// the result will be something like 0b00100000.
|
||||
// 5. Use BitScanForward (bsf) to find the index of the most significant 1 bit.
|
||||
const auto haystack = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(colorTable.data())); // 1.
|
||||
const auto needle = _mm256_set1_epi32(til::bit_cast<int>(defaultColor)); // 2.
|
||||
const auto result = _mm256_cmpeq_epi32(haystack, needle); // 3.
|
||||
const auto mask = _mm256_movemask_ps(_mm256_castsi256_ps(result)); // 4.
|
||||
unsigned long index;
|
||||
return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast<size_t>(index) + 8) : defaultColor; // 5.
|
||||
#elif _M_AMD64
|
||||
// If you look closely this SSE2 algorithm is the same as the AVX one.
|
||||
// The two differences are that we need to:
|
||||
// * do everything twice, because SSE is limited to 128 bits and not 256.
|
||||
// * use _mm_packs_epi32 to merge two 128 bits vectors into one in step 3.5.
|
||||
// _mm_packs_epi32 takes two SSE registers and truncates all 8 DWORDs into 8 WORDs,
|
||||
// the latter of which fits into a single register (which is then used in the identical step 4).
|
||||
// * since the result are now 8 WORDs, we need to use _mm_movemask_epi8 (there's no 16-bit variant),
|
||||
// which unlike AVX's step 4 results in in something like 0b0000110000000000.
|
||||
// --> the index returned by _BitScanForward must be divided by 2.
|
||||
const auto haystack1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(colorTable.data() + 0));
|
||||
const auto haystack2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(colorTable.data() + 4));
|
||||
const auto needle = _mm_set1_epi32(til::bit_cast<int>(defaultColor));
|
||||
const auto result1 = _mm_cmpeq_epi32(haystack1, needle);
|
||||
const auto result2 = _mm_cmpeq_epi32(haystack2, needle);
|
||||
const auto result = _mm_packs_epi32(result1, result2); // 3.5
|
||||
const auto mask = _mm_movemask_epi8(result);
|
||||
unsigned long index;
|
||||
return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast<size_t>(index / 2) + 8) : defaultColor;
|
||||
#else
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
{
|
||||
if (til::at(colorTable, i) == defaultColor)
|
||||
|
@ -222,8 +163,6 @@ COLORREF TextColor::GetColor(const std::array<COLORREF, TextColor::TABLE_SIZE>&
|
|||
return til::at(colorTable, i + 8);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
return defaultColor;
|
||||
|
@ -254,9 +193,13 @@ BYTE TextColor::GetLegacyIndex(const BYTE defaultIndex) const noexcept
|
|||
{
|
||||
return defaultIndex;
|
||||
}
|
||||
else if (IsIndex16() || IsIndex256())
|
||||
else if (IsIndex16())
|
||||
{
|
||||
return til::at(Index256ToIndex16, GetIndex());
|
||||
return GetIndex();
|
||||
}
|
||||
else if (IsIndex256())
|
||||
{
|
||||
return Index256ToIndex16.at(GetIndex());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -265,7 +208,7 @@ BYTE TextColor::GetLegacyIndex(const BYTE defaultIndex) const noexcept
|
|||
const BYTE compressedRgb = (_red & 0b11100000) +
|
||||
((_green >> 3) & 0b00011100) +
|
||||
((_blue >> 6) & 0b00000011);
|
||||
return til::at(CompressedRgbToIndex16, compressedRgb);
|
||||
return CompressedRgbToIndex16.at(compressedRgb);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,28 +48,6 @@ enum class ColorType : BYTE
|
|||
struct TextColor
|
||||
{
|
||||
public:
|
||||
static constexpr BYTE DARK_BLACK = 0;
|
||||
static constexpr BYTE DARK_RED = 1;
|
||||
static constexpr BYTE DARK_GREEN = 2;
|
||||
static constexpr BYTE DARK_YELLOW = 3;
|
||||
static constexpr BYTE DARK_BLUE = 4;
|
||||
static constexpr BYTE DARK_MAGENTA = 5;
|
||||
static constexpr BYTE DARK_CYAN = 6;
|
||||
static constexpr BYTE DARK_WHITE = 7;
|
||||
static constexpr BYTE BRIGHT_BLACK = 8;
|
||||
static constexpr BYTE BRIGHT_RED = 9;
|
||||
static constexpr BYTE BRIGHT_GREEN = 10;
|
||||
static constexpr BYTE BRIGHT_YELLOW = 11;
|
||||
static constexpr BYTE BRIGHT_BLUE = 12;
|
||||
static constexpr BYTE BRIGHT_MAGENTA = 13;
|
||||
static constexpr BYTE BRIGHT_CYAN = 14;
|
||||
static constexpr BYTE BRIGHT_WHITE = 15;
|
||||
|
||||
static constexpr size_t DEFAULT_FOREGROUND = 256;
|
||||
static constexpr size_t DEFAULT_BACKGROUND = 257;
|
||||
static constexpr size_t CURSOR_COLOR = 258;
|
||||
static constexpr size_t TABLE_SIZE = 259;
|
||||
|
||||
constexpr TextColor() noexcept :
|
||||
_meta{ ColorType::IsDefault },
|
||||
_red{ 0 },
|
||||
|
@ -108,7 +86,10 @@ public:
|
|||
void SetIndex(const BYTE index, const bool isIndex256) noexcept;
|
||||
void SetDefault() noexcept;
|
||||
|
||||
COLORREF GetColor(const std::array<COLORREF, TABLE_SIZE>& colorTable, const size_t defaultIndex, bool brighten = false) const noexcept;
|
||||
COLORREF GetColor(gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultColor,
|
||||
const bool brighten = false) const noexcept;
|
||||
|
||||
BYTE GetLegacyIndex(const BYTE defaultIndex) const noexcept;
|
||||
|
||||
constexpr BYTE GetIndex() const noexcept
|
||||
|
@ -118,16 +99,6 @@ public:
|
|||
|
||||
COLORREF GetRGB() const noexcept;
|
||||
|
||||
static constexpr BYTE TransposeLegacyIndex(const size_t index)
|
||||
{
|
||||
// When converting a 16-color index in the legacy Windows order to or
|
||||
// from an ANSI-compatible order, we need to swap the bits in positions
|
||||
// 0 and 2. We do this by XORing the index with 00000101, but only if
|
||||
// one (but not both) of those bit positions is set.
|
||||
const auto oneBitSet = (index ^ (index >> 2)) & 1;
|
||||
return gsl::narrow_cast<BYTE>(index ^ oneBitSet ^ (oneBitSet << 2));
|
||||
}
|
||||
|
||||
private:
|
||||
union
|
||||
{
|
||||
|
@ -186,3 +157,5 @@ namespace WEX
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static_assert(sizeof(TextColor) <= 4 * sizeof(BYTE), "We should only need 4B for an entire TextColor. Any more than that is just waste");
|
||||
|
|
|
@ -27,7 +27,9 @@ Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept :
|
|||
_fDeferCursorRedraw(false),
|
||||
_fHaveDeferredCursorRedraw(false),
|
||||
_ulSize(ulSize),
|
||||
_cursorType(CursorType::Legacy)
|
||||
_cursorType(CursorType::Legacy),
|
||||
_fUseColor(false),
|
||||
_color(s_InvertCursorColor)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -141,9 +143,10 @@ void Cursor::SetSize(const ULONG ulSize) noexcept
|
|||
_RedrawCursor();
|
||||
}
|
||||
|
||||
void Cursor::SetStyle(const ULONG ulSize, const CursorType type) noexcept
|
||||
void Cursor::SetStyle(const ULONG ulSize, const COLORREF color, const CursorType type) noexcept
|
||||
{
|
||||
_ulSize = ulSize;
|
||||
_color = color;
|
||||
_cursorType = type;
|
||||
|
||||
_RedrawCursor();
|
||||
|
@ -282,6 +285,7 @@ void Cursor::CopyProperties(const Cursor& OtherCursor) noexcept
|
|||
// Size will be handled separately in the resize operation.
|
||||
//_ulSize = OtherCursor._ulSize;
|
||||
_cursorType = OtherCursor._cursorType;
|
||||
_color = OtherCursor._color;
|
||||
}
|
||||
|
||||
void Cursor::DelayEOLWrap(const COORD coordDelayedAt) noexcept
|
||||
|
@ -311,11 +315,6 @@ void Cursor::StartDeferDrawing() noexcept
|
|||
_fDeferCursorRedraw = true;
|
||||
}
|
||||
|
||||
bool Cursor::IsDeferDrawing() noexcept
|
||||
{
|
||||
return _fDeferCursorRedraw;
|
||||
}
|
||||
|
||||
void Cursor::EndDeferDrawing() noexcept
|
||||
{
|
||||
if (_fHaveDeferredCursorRedraw)
|
||||
|
@ -331,6 +330,21 @@ const CursorType Cursor::GetType() const noexcept
|
|||
return _cursorType;
|
||||
}
|
||||
|
||||
const bool Cursor::IsUsingColor() const noexcept
|
||||
{
|
||||
return GetColor() != INVALID_COLOR;
|
||||
}
|
||||
|
||||
const COLORREF Cursor::GetColor() const noexcept
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
void Cursor::SetColor(const unsigned int color) noexcept
|
||||
{
|
||||
_color = gsl::narrow_cast<COLORREF>(color);
|
||||
}
|
||||
|
||||
void Cursor::SetType(const CursorType type) noexcept
|
||||
{
|
||||
_cursorType = type;
|
||||
|
|
|
@ -24,6 +24,7 @@ class TextBuffer;
|
|||
class Cursor final
|
||||
{
|
||||
public:
|
||||
static const unsigned int s_InvertCursorColor = INVALID_COLOR;
|
||||
// the following values are used to create the textmode cursor.
|
||||
static constexpr unsigned int CURSOR_SMALL_SIZE = 25; // large enough to be one pixel on a six pixel font
|
||||
|
||||
|
@ -50,9 +51,10 @@ public:
|
|||
COORD GetPosition() const noexcept;
|
||||
|
||||
const CursorType GetType() const noexcept;
|
||||
const bool IsUsingColor() const noexcept;
|
||||
const COLORREF GetColor() const noexcept;
|
||||
|
||||
void StartDeferDrawing() noexcept;
|
||||
bool IsDeferDrawing() noexcept;
|
||||
void EndDeferDrawing() noexcept;
|
||||
|
||||
void SetHasMoved(const bool fHasMoved) noexcept;
|
||||
|
@ -64,7 +66,7 @@ public:
|
|||
void SetIsPopupShown(const bool fIsPopupShown) noexcept;
|
||||
void SetDelay(const bool fDelay) noexcept;
|
||||
void SetSize(const ULONG ulSize) noexcept;
|
||||
void SetStyle(const ULONG ulSize, const CursorType type) noexcept;
|
||||
void SetStyle(const ULONG ulSize, const COLORREF color, const CursorType type) noexcept;
|
||||
|
||||
void SetPosition(const COORD cPosition) noexcept;
|
||||
void SetXPosition(const int NewX) noexcept;
|
||||
|
@ -81,6 +83,7 @@ public:
|
|||
COORD GetDelayedAtPosition() const noexcept;
|
||||
bool IsDelayedEOLWrap() const noexcept;
|
||||
|
||||
void SetColor(const unsigned int color) noexcept;
|
||||
void SetType(const CursorType type) noexcept;
|
||||
|
||||
private:
|
||||
|
@ -113,4 +116,6 @@ private:
|
|||
void _RedrawCursorAlways() noexcept;
|
||||
|
||||
CursorType _cursorType;
|
||||
bool _fUseColor;
|
||||
COLORREF _color;
|
||||
};
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<ClInclude Include="..\Row.hpp" />
|
||||
<ClInclude Include="..\search.h" />
|
||||
<ClInclude Include="..\TextColor.h" />
|
||||
<ClInclude Include="..\TextAttribute.hpp" />
|
||||
<ClInclude Include="..\TextAttribute.h" />
|
||||
<ClInclude Include="..\textBuffer.hpp" />
|
||||
<ClInclude Include="..\textBufferCellIterator.hpp" />
|
||||
<ClInclude Include="..\textBufferTextIterator.hpp" />
|
||||
|
|
|
@ -418,6 +418,7 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars,
|
|||
|
||||
// Store character and double byte data
|
||||
CharRow& charRow = Row.GetCharRow();
|
||||
short const cBufferWidth = GetSize().Width();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -1057,10 +1058,9 @@ const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std
|
|||
// - accessibilityMode - when enabled, we continue expanding left until we are at the beginning of a readable word.
|
||||
// Otherwise, expand left until a character of a new delimiter class is found
|
||||
// (or a row boundary is encountered)
|
||||
// - limitOptional - (optional) the last possible position in the buffer that can be explored. This can be used to improve performance.
|
||||
// Return Value:
|
||||
// - The COORD for the first character on the "word" (inclusive)
|
||||
const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode, std::optional<til::point> limitOptional) const
|
||||
const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const
|
||||
{
|
||||
// Consider a buffer with this text in it:
|
||||
// " word other "
|
||||
|
@ -1073,9 +1073,10 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view
|
|||
// NOTE: the start anchor (this one) is inclusive, whereas the end anchor (GetWordEnd) is exclusive
|
||||
|
||||
#pragma warning(suppress : 26496)
|
||||
// GH#7664: Treat EndExclusive as EndInclusive so
|
||||
// that it actually points to a space in the buffer
|
||||
auto copy{ target };
|
||||
const auto bufferSize{ GetSize() };
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
if (target == bufferSize.Origin())
|
||||
{
|
||||
// can't expand left
|
||||
|
@ -1083,15 +1084,9 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view
|
|||
}
|
||||
else if (target == bufferSize.EndExclusive())
|
||||
{
|
||||
// GH#7664: Treat EndExclusive as EndInclusive so
|
||||
// that it actually points to a space in the buffer
|
||||
// treat EndExclusive as EndInclusive
|
||||
copy = { bufferSize.RightInclusive(), bufferSize.BottomInclusive() };
|
||||
}
|
||||
else if (bufferSize.CompareInBounds(target, limit, true) >= 0)
|
||||
{
|
||||
// if at/past the limit --> clamp to limit
|
||||
copy = *limitOptional;
|
||||
}
|
||||
|
||||
if (accessibilityMode)
|
||||
{
|
||||
|
@ -1185,10 +1180,9 @@ const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std:
|
|||
// - accessibilityMode - when enabled, we continue expanding right until we are at the beginning of the next READABLE word
|
||||
// Otherwise, expand right until a character of a new delimiter class is found
|
||||
// (or a row boundary is encountered)
|
||||
// - limitOptional - (optional) the last possible position in the buffer that can be explored. This can be used to improve performance.
|
||||
// Return Value:
|
||||
// - The COORD for the last character on the "word" (inclusive)
|
||||
const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode, std::optional<til::point> limitOptional) const
|
||||
const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const
|
||||
{
|
||||
// Consider a buffer with this text in it:
|
||||
// " word other "
|
||||
|
@ -1200,17 +1194,16 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w
|
|||
// so the words in the example include ["word ", "other "]
|
||||
// NOTE: the end anchor (this one) is exclusive, whereas the start anchor (GetWordStart) is inclusive
|
||||
|
||||
// Already at/past the limit. Can't move forward.
|
||||
const auto bufferSize{ GetSize() };
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
if (bufferSize.CompareInBounds(target, limit, true) >= 0)
|
||||
// Already at the end. Can't move forward.
|
||||
if (target == GetSize().EndExclusive())
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
if (accessibilityMode)
|
||||
{
|
||||
return _GetWordEndForAccessibility(target, wordDelimiters, limit);
|
||||
const auto lastCharPos{ GetLastNonSpaceCharacter() };
|
||||
return _GetWordEndForAccessibility(target, wordDelimiters, lastCharPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1223,46 +1216,44 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w
|
|||
// Arguments:
|
||||
// - target - a COORD on the word you are currently on
|
||||
// - wordDelimiters - what characters are we considering for the separation of words
|
||||
// - limit - the last "valid" position in the text buffer (to improve performance)
|
||||
// - lastCharPos - the position of the last nonspace character in the text buffer (to improve performance)
|
||||
// Return Value:
|
||||
// - The COORD for the first character of the next readable "word". If no next word, return one past the end of the buffer
|
||||
const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD limit) const
|
||||
const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const
|
||||
{
|
||||
const auto bufferSize{ GetSize() };
|
||||
COORD result{ target };
|
||||
const auto bufferSize = GetSize();
|
||||
COORD result = target;
|
||||
|
||||
if (bufferSize.CompareInBounds(target, limit, true) >= 0)
|
||||
// Check if we're already on/past the last RegularChar
|
||||
if (bufferSize.CompareInBounds(result, lastCharPos, true) >= 0)
|
||||
{
|
||||
// if we're already on/past the last RegularChar,
|
||||
// clamp result to that position
|
||||
result = limit;
|
||||
|
||||
// make the result exclusive
|
||||
bufferSize.IncrementInBounds(result, true);
|
||||
return bufferSize.EndExclusive();
|
||||
}
|
||||
else
|
||||
|
||||
// ignore right boundary. Continue through readable text found
|
||||
while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
{
|
||||
auto iter{ GetCellDataAt(result, bufferSize) };
|
||||
while (iter && iter.Pos() != limit && _GetDelimiterClassAt(iter.Pos(), wordDelimiters) == DelimiterClass::RegularChar)
|
||||
if (!bufferSize.IncrementInBounds(result, true))
|
||||
{
|
||||
// Iterate through readable text
|
||||
++iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (iter && iter.Pos() != limit && _GetDelimiterClassAt(iter.Pos(), wordDelimiters) != DelimiterClass::RegularChar)
|
||||
// we are already on/past the last RegularChar
|
||||
if (bufferSize.CompareInBounds(result, lastCharPos, true) >= 0)
|
||||
{
|
||||
return bufferSize.EndExclusive();
|
||||
}
|
||||
|
||||
// make sure we expand to the beginning of the NEXT word
|
||||
while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
{
|
||||
if (!bufferSize.IncrementInBounds(result, true))
|
||||
{
|
||||
// expand to the beginning of the NEXT word
|
||||
++iter;
|
||||
}
|
||||
|
||||
result = iter.Pos();
|
||||
|
||||
// Special case: we tried to move one past the end of the buffer,
|
||||
// but iter prevented that (because that pos doesn't exist).
|
||||
// Manually increment onto the EndExclusive point.
|
||||
if (!iter)
|
||||
{
|
||||
bufferSize.IncrementInBounds(result, true);
|
||||
// we are at the EndInclusive COORD
|
||||
// this signifies that we must include the last char in the buffer
|
||||
// but the position of the COORD points to nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1355,20 +1346,18 @@ void TextBuffer::_PruneHyperlinks()
|
|||
// Arguments:
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// - wordDelimiters - what characters are we considering for the separation of words
|
||||
// - limitOptional - (optional) the last possible position in the buffer that can be explored. This can be used to improve performance.
|
||||
// - lastCharPos - the position of the last nonspace character in the text buffer (to improve performance)
|
||||
// Return Value:
|
||||
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
|
||||
// - pos - The COORD for the first character on the "word" (inclusive)
|
||||
bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, std::optional<til::point> limitOptional) const
|
||||
bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const
|
||||
{
|
||||
// move to the beginning of the next word
|
||||
// NOTE: _GetWordEnd...() returns the exclusive position of the "end of the word"
|
||||
// This is also the inclusive start of the next word.
|
||||
const auto bufferSize{ GetSize() };
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
const auto copy{ _GetWordEndForAccessibility(pos, wordDelimiters, limit) };
|
||||
auto copy{ _GetWordEndForAccessibility(pos, wordDelimiters, lastCharPos) };
|
||||
|
||||
if (bufferSize.CompareInBounds(copy, limit, true) >= 0)
|
||||
if (copy == GetSize().EndExclusive())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -1405,23 +1394,19 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
|
|||
// - Update pos to be the beginning of the current glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// - limitOptional - (optional) the last possible position in the buffer that can be explored. This can be used to improve performance.
|
||||
// Return Value:
|
||||
// - pos - The COORD for the first cell of the current glyph (inclusive)
|
||||
const til::point TextBuffer::GetGlyphStart(const til::point pos, std::optional<til::point> limitOptional) const
|
||||
const til::point TextBuffer::GetGlyphStart(const til::point pos) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
const auto bufferSize = GetSize();
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
|
||||
// Clamp pos to limit
|
||||
if (bufferSize.CompareInBounds(resultPos, limit, true) > 0)
|
||||
if (resultPos == bufferSize.EndExclusive())
|
||||
{
|
||||
resultPos = limit;
|
||||
bufferSize.DecrementInBounds(resultPos, true);
|
||||
}
|
||||
|
||||
// limit is exclusive, so we need to move back to be within valid bounds
|
||||
if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
|
||||
{
|
||||
bufferSize.DecrementInBounds(resultPos, true);
|
||||
}
|
||||
|
@ -1430,34 +1415,23 @@ const til::point TextBuffer::GetGlyphStart(const til::point pos, std::optional<t
|
|||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update pos to be the end of the current glyph/character.
|
||||
// - Update pos to be the end of the current glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// - accessibilityMode - this is being used for accessibility; make the end exclusive.
|
||||
// Return Value:
|
||||
// - pos - The COORD for the last cell of the current glyph (exclusive)
|
||||
const til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilityMode, std::optional<til::point> limitOptional) const
|
||||
const til::point TextBuffer::GetGlyphEnd(const til::point pos) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
|
||||
const auto bufferSize = GetSize();
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
|
||||
// Clamp pos to limit
|
||||
if (bufferSize.CompareInBounds(resultPos, limit, true) > 0)
|
||||
{
|
||||
resultPos = limit;
|
||||
}
|
||||
|
||||
if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
|
||||
{
|
||||
bufferSize.IncrementInBounds(resultPos, true);
|
||||
}
|
||||
|
||||
// increment one more time to become exclusive
|
||||
if (accessibilityMode)
|
||||
{
|
||||
bufferSize.IncrementInBounds(resultPos, true);
|
||||
}
|
||||
bufferSize.IncrementInBounds(resultPos, true);
|
||||
return resultPos;
|
||||
}
|
||||
|
||||
|
@ -1465,43 +1439,29 @@ const til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilit
|
|||
// - Update pos to be the beginning of the next glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// - allowExclusiveEnd - allow result to be the exclusive limit (one past limit)
|
||||
// - limit - boundaries for the iterator to operate within
|
||||
// - allowBottomExclusive - allow the nonexistent end-of-buffer cell to be encountered
|
||||
// Return Value:
|
||||
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
|
||||
// - pos - The COORD for the first cell of the current glyph (inclusive)
|
||||
bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowExclusiveEnd, std::optional<til::point> limitOptional) const
|
||||
bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
const auto bufferSize = GetSize();
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
|
||||
const auto distanceToLimit{ bufferSize.CompareInBounds(pos, limit, true) };
|
||||
if (distanceToLimit >= 0)
|
||||
if (resultPos == GetSize().EndExclusive())
|
||||
{
|
||||
// Corner Case: we're on/past the limit
|
||||
// Clamp us to the limit
|
||||
pos = limit;
|
||||
return false;
|
||||
}
|
||||
else if (!allowExclusiveEnd && distanceToLimit == -1)
|
||||
{
|
||||
// Corner Case: we're just before the limit
|
||||
// and we are not allowed onto the exclusive end.
|
||||
// Fail to move.
|
||||
// we're already at the end
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to move forward, but if we hit the buffer boundary, we fail to move.
|
||||
auto iter{ GetCellDataAt(pos, bufferSize) };
|
||||
const bool success{ ++iter };
|
||||
|
||||
// Move again if we're on a wide glyph
|
||||
if (success && iter->DbcsAttr().IsTrailing())
|
||||
// try to move. If we can't, we're done.
|
||||
const bool success = bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
|
||||
{
|
||||
++iter;
|
||||
bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
|
||||
}
|
||||
|
||||
pos = iter.Pos();
|
||||
pos = resultPos;
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -1512,21 +1472,12 @@ bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowExclusiveEnd, std::o
|
|||
// Return Value:
|
||||
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
|
||||
// - pos - The COORD for the first cell of the previous glyph (inclusive)
|
||||
bool TextBuffer::MoveToPreviousGlyph(til::point& pos, std::optional<til::point> limitOptional) const
|
||||
bool TextBuffer::MoveToPreviousGlyph(til::point& pos) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
const auto bufferSize = GetSize();
|
||||
const auto limit{ limitOptional.value_or(bufferSize.EndExclusive()) };
|
||||
|
||||
if (bufferSize.CompareInBounds(pos, limit, true) > 0)
|
||||
{
|
||||
// we're past the end
|
||||
// clamp us to the limit
|
||||
pos = limit;
|
||||
return true;
|
||||
}
|
||||
|
||||
// try to move. If we can't, we're done.
|
||||
const auto bufferSize = GetSize();
|
||||
const bool success = bufferSize.DecrementInBounds(resultPos, true);
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
|
||||
{
|
||||
|
@ -1699,14 +1650,13 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
|
|||
|
||||
if (!cell.DbcsAttr().IsTrailing())
|
||||
{
|
||||
const auto chars = cell.Chars();
|
||||
selectionText.append(chars);
|
||||
selectionText.append(cell.Chars());
|
||||
|
||||
if (copyTextColor)
|
||||
{
|
||||
const auto cellData = cell.TextAttr();
|
||||
const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData);
|
||||
for (size_t j = 0; j < chars.size(); ++j)
|
||||
for (const wchar_t wch : cell.Chars())
|
||||
{
|
||||
selectionFgAttr.push_back(CellFgAttr);
|
||||
selectionBkAttr.push_back(CellBkAttr);
|
||||
|
|
|
@ -141,15 +141,15 @@ public:
|
|||
|
||||
Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept;
|
||||
|
||||
const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const;
|
||||
const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const;
|
||||
bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const;
|
||||
bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
const til::point GetGlyphStart(const til::point pos, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
const til::point GetGlyphEnd(const til::point pos, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
bool MoveToPreviousGlyph(til::point& pos, std::optional<til::point> limitOptional = std::nullopt) const;
|
||||
const til::point GetGlyphStart(const til::point pos) const;
|
||||
const til::point GetGlyphEnd(const til::point pos) const;
|
||||
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
|
||||
bool MoveToPreviousGlyph(til::point& pos) const;
|
||||
|
||||
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const;
|
||||
|
||||
|
@ -242,7 +242,7 @@ private:
|
|||
const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD limit) const;
|
||||
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const;
|
||||
const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
void _PruneHyperlinks();
|
||||
|
|
|
@ -24,11 +24,11 @@ class TextAttributeTests
|
|||
TEST_METHOD(TestRoundtripDefaultColors);
|
||||
TEST_METHOD(TestBoldAsBright);
|
||||
|
||||
std::array<COLORREF, TextColor::TABLE_SIZE> _colorTable;
|
||||
const COLORREF _defaultFg = RGB(1, 2, 3);
|
||||
const COLORREF _defaultBg = RGB(4, 5, 6);
|
||||
const size_t _defaultFgIndex = TextColor::DEFAULT_FOREGROUND;
|
||||
const size_t _defaultBgIndex = TextColor::DEFAULT_BACKGROUND;
|
||||
static const int COLOR_TABLE_SIZE = 16;
|
||||
COLORREF _colorTable[COLOR_TABLE_SIZE];
|
||||
COLORREF _defaultFg = RGB(1, 2, 3);
|
||||
COLORREF _defaultBg = RGB(4, 5, 6);
|
||||
gsl::span<const COLORREF> _GetTableView();
|
||||
};
|
||||
|
||||
bool TextAttributeTests::ClassSetup()
|
||||
|
@ -49,11 +49,14 @@ bool TextAttributeTests::ClassSetup()
|
|||
_colorTable[13] = RGB(180, 0, 158); // Bright Magenta
|
||||
_colorTable[14] = RGB(249, 241, 165); // Bright Yellow
|
||||
_colorTable[15] = RGB(242, 242, 242); // White
|
||||
_colorTable[_defaultFgIndex] = _defaultFg;
|
||||
_colorTable[_defaultBgIndex] = _defaultBg;
|
||||
return true;
|
||||
}
|
||||
|
||||
gsl::span<const COLORREF> TextAttributeTests::_GetTableView()
|
||||
{
|
||||
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
|
||||
}
|
||||
|
||||
void TextAttributeTests::TestRoundtripLegacy()
|
||||
{
|
||||
WORD expectedLegacy = FOREGROUND_BLUE | BACKGROUND_RED;
|
||||
|
@ -131,22 +134,23 @@ void TextAttributeTests::TestTextAttributeColorGetters()
|
|||
const COLORREF faintRed = RGB(127, 0, 0);
|
||||
const COLORREF green = RGB(0, 255, 0);
|
||||
TextAttribute attr(red, green);
|
||||
auto view = _GetTableView();
|
||||
|
||||
// verify that calculated foreground/background are the same as the direct
|
||||
// values when reverse video is not set
|
||||
VERIFY_IS_FALSE(attr.IsReverseVideo());
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(red, green), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(red, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// with reverse video set, calculated foreground/background values should be
|
||||
// switched while getters stay the same
|
||||
attr.SetReverseVideo(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, red), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// reset the reverse video
|
||||
attr.SetReverseVideo(false);
|
||||
|
@ -155,17 +159,17 @@ void TextAttributeTests::TestTextAttributeColorGetters()
|
|||
// while the background and getters stay the same
|
||||
attr.SetFaint(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(faintRed, green), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(faintRed, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// with reverse video set, calculated foreground/background values should be
|
||||
// switched, and the background fainter, while getters stay the same
|
||||
attr.SetReverseVideo(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, faintRed), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, faintRed), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// reset the reverse video and faint attributes
|
||||
attr.SetReverseVideo(false);
|
||||
|
@ -175,17 +179,17 @@ void TextAttributeTests::TestTextAttributeColorGetters()
|
|||
// background, while getters stay the same
|
||||
attr.SetInvisible(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, green), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// with reverse video set, the calculated background value should match
|
||||
// the foreground, while getters stay the same
|
||||
attr.SetReverseVideo(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(red, red), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(red, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
}
|
||||
|
||||
void TextAttributeTests::TestReverseDefaultColors()
|
||||
|
@ -193,39 +197,40 @@ void TextAttributeTests::TestReverseDefaultColors()
|
|||
const COLORREF red = RGB(255, 0, 0);
|
||||
const COLORREF green = RGB(0, 255, 0);
|
||||
TextAttribute attr{};
|
||||
auto view = _GetTableView();
|
||||
|
||||
// verify that calculated foreground/background are the same as the direct
|
||||
// values when reverse video is not set
|
||||
VERIFY_IS_FALSE(attr.IsReverseVideo());
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// with reverse video set, calculated foreground/background values should be
|
||||
// switched while getters stay the same
|
||||
attr.SetReverseVideo(true);
|
||||
VERIFY_IS_TRUE(attr.IsReverseVideo());
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, _defaultFg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, _defaultFg), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
attr.SetForeground(red);
|
||||
VERIFY_IS_TRUE(attr.IsReverseVideo());
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, red), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
attr.Invert();
|
||||
VERIFY_IS_FALSE(attr.IsReverseVideo());
|
||||
attr.SetDefaultForeground();
|
||||
attr.SetBackground(green);
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, green), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
}
|
||||
|
||||
void TextAttributeTests::TestRoundtripDefaultColors()
|
||||
|
@ -241,7 +246,7 @@ void TextAttributeTests::TestRoundtripDefaultColors()
|
|||
Log::Comment(L"Foreground legacy default index should map to default text color.");
|
||||
legacyAttribute = fgLegacyDefault | BACKGROUND_GREEN;
|
||||
textAttribute.SetDefaultForeground();
|
||||
textAttribute.SetIndexedBackground256(TextColor::DARK_GREEN);
|
||||
textAttribute.SetIndexedBackground256(BACKGROUND_GREEN >> 4);
|
||||
VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute });
|
||||
|
||||
Log::Comment(L"Default foreground text color should map back to legacy default index.");
|
||||
|
@ -249,7 +254,7 @@ void TextAttributeTests::TestRoundtripDefaultColors()
|
|||
|
||||
Log::Comment(L"Background legacy default index should map to default text color.");
|
||||
legacyAttribute = FOREGROUND_GREEN | bgLegacyDefault;
|
||||
textAttribute.SetIndexedForeground256(TextColor::DARK_GREEN);
|
||||
textAttribute.SetIndexedForeground256(FOREGROUND_GREEN);
|
||||
textAttribute.SetDefaultBackground();
|
||||
VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute });
|
||||
|
||||
|
@ -271,9 +276,9 @@ void TextAttributeTests::TestRoundtripDefaultColors()
|
|||
|
||||
void TextAttributeTests::TestBoldAsBright()
|
||||
{
|
||||
const COLORREF darkBlack = til::at(_colorTable, 0);
|
||||
const COLORREF brightBlack = til::at(_colorTable, 8);
|
||||
const COLORREF darkGreen = til::at(_colorTable, 2);
|
||||
const COLORREF darkBlack = _colorTable[0];
|
||||
const COLORREF brightBlack = _colorTable[8];
|
||||
const COLORREF darkGreen = _colorTable[2];
|
||||
|
||||
TextAttribute attr{};
|
||||
|
||||
|
@ -281,43 +286,43 @@ void TextAttributeTests::TestBoldAsBright()
|
|||
// values when not bold
|
||||
VERIFY_IS_FALSE(attr.IsBold());
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
|
||||
|
||||
// with bold set, calculated foreground/background values shouldn't change for the default colors.
|
||||
attr.SetBold(true);
|
||||
VERIFY_IS_TRUE(attr.IsBold());
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
|
||||
|
||||
attr.SetIndexedForeground(TextColor::DARK_BLACK);
|
||||
attr.SetIndexedForeground(0);
|
||||
VERIFY_IS_TRUE(attr.IsBold());
|
||||
|
||||
Log::Comment(L"Foreground should be bright black when bold is bright is enabled");
|
||||
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
|
||||
|
||||
Log::Comment(L"Foreground should be dark black when bold is bright is disabled");
|
||||
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
|
||||
|
||||
attr.SetIndexedBackground(TextColor::DARK_GREEN);
|
||||
attr.SetIndexedBackground(2);
|
||||
VERIFY_IS_TRUE(attr.IsBold());
|
||||
|
||||
Log::Comment(L"background should be unaffected by 'bold is bright'");
|
||||
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
|
||||
|
||||
attr.SetBold(false);
|
||||
VERIFY_IS_FALSE(attr.IsBold());
|
||||
Log::Comment(L"when not bold, 'bold is bright' changes nothing");
|
||||
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
|
||||
|
||||
Log::Comment(L"When set to a bright color, and bold, 'bold is bright' changes nothing");
|
||||
attr.SetBold(true);
|
||||
attr.SetIndexedForeground(TextColor::BRIGHT_BLACK);
|
||||
attr.SetIndexedForeground(8);
|
||||
VERIFY_IS_TRUE(attr.IsBold());
|
||||
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ class TextColorTests
|
|||
TEST_METHOD(TestRgbColor);
|
||||
TEST_METHOD(TestChangeColor);
|
||||
|
||||
std::array<COLORREF, TextColor::TABLE_SIZE> _colorTable;
|
||||
const COLORREF _defaultFg = RGB(1, 2, 3);
|
||||
const COLORREF _defaultBg = RGB(4, 5, 6);
|
||||
const size_t _defaultFgIndex = TextColor::DEFAULT_FOREGROUND;
|
||||
const size_t _defaultBgIndex = TextColor::DEFAULT_BACKGROUND;
|
||||
static const int COLOR_TABLE_SIZE = 16;
|
||||
COLORREF _colorTable[COLOR_TABLE_SIZE];
|
||||
COLORREF _defaultFg = RGB(1, 2, 3);
|
||||
COLORREF _defaultBg = RGB(4, 5, 6);
|
||||
gsl::span<const COLORREF> _GetTableView();
|
||||
};
|
||||
|
||||
bool TextColorTests::ClassSetup()
|
||||
|
@ -48,11 +48,14 @@ bool TextColorTests::ClassSetup()
|
|||
_colorTable[13] = RGB(180, 0, 158); // Bright Magenta
|
||||
_colorTable[14] = RGB(249, 241, 165); // Bright Yellow
|
||||
_colorTable[15] = RGB(242, 242, 242); // White
|
||||
_colorTable[_defaultFgIndex] = _defaultFg;
|
||||
_colorTable[_defaultBgIndex] = _defaultBg;
|
||||
return true;
|
||||
}
|
||||
|
||||
gsl::span<const COLORREF> TextColorTests::_GetTableView()
|
||||
{
|
||||
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
|
||||
}
|
||||
|
||||
void TextColorTests::TestDefaultColor()
|
||||
{
|
||||
TextColor defaultColor;
|
||||
|
@ -61,16 +64,18 @@ void TextColorTests::TestDefaultColor()
|
|||
VERIFY_IS_FALSE(defaultColor.IsLegacy());
|
||||
VERIFY_IS_FALSE(defaultColor.IsRgb());
|
||||
|
||||
auto color = defaultColor.GetColor(_colorTable, _defaultFgIndex, false);
|
||||
auto view = _GetTableView();
|
||||
|
||||
auto color = defaultColor.GetColor(view, _defaultFg, false);
|
||||
VERIFY_ARE_EQUAL(_defaultFg, color);
|
||||
|
||||
color = defaultColor.GetColor(_colorTable, _defaultFgIndex, true);
|
||||
color = defaultColor.GetColor(view, _defaultFg, true);
|
||||
VERIFY_ARE_EQUAL(_defaultFg, color);
|
||||
|
||||
color = defaultColor.GetColor(_colorTable, _defaultBgIndex, false);
|
||||
color = defaultColor.GetColor(view, _defaultBg, false);
|
||||
VERIFY_ARE_EQUAL(_defaultBg, color);
|
||||
|
||||
color = defaultColor.GetColor(_colorTable, _defaultBgIndex, true);
|
||||
color = defaultColor.GetColor(view, _defaultBg, true);
|
||||
VERIFY_ARE_EQUAL(_defaultBg, color);
|
||||
}
|
||||
|
||||
|
@ -82,16 +87,18 @@ void TextColorTests::TestDarkIndexColor()
|
|||
VERIFY_IS_TRUE(indexColor.IsLegacy());
|
||||
VERIFY_IS_FALSE(indexColor.IsRgb());
|
||||
|
||||
auto color = indexColor.GetColor(_colorTable, _defaultFgIndex, false);
|
||||
auto view = _GetTableView();
|
||||
|
||||
auto color = indexColor.GetColor(view, _defaultFg, false);
|
||||
VERIFY_ARE_EQUAL(_colorTable[7], color);
|
||||
|
||||
color = indexColor.GetColor(_colorTable, _defaultFgIndex, true);
|
||||
color = indexColor.GetColor(view, _defaultFg, true);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
color = indexColor.GetColor(_colorTable, _defaultBgIndex, false);
|
||||
color = indexColor.GetColor(view, _defaultBg, false);
|
||||
VERIFY_ARE_EQUAL(_colorTable[7], color);
|
||||
|
||||
color = indexColor.GetColor(_colorTable, _defaultBgIndex, true);
|
||||
color = indexColor.GetColor(view, _defaultBg, true);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
}
|
||||
|
||||
|
@ -103,16 +110,18 @@ void TextColorTests::TestBrightIndexColor()
|
|||
VERIFY_IS_TRUE(indexColor.IsLegacy());
|
||||
VERIFY_IS_FALSE(indexColor.IsRgb());
|
||||
|
||||
auto color = indexColor.GetColor(_colorTable, _defaultFgIndex, false);
|
||||
auto view = _GetTableView();
|
||||
|
||||
auto color = indexColor.GetColor(view, _defaultFg, false);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
color = indexColor.GetColor(_colorTable, _defaultFgIndex, true);
|
||||
color = indexColor.GetColor(view, _defaultFg, true);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
color = indexColor.GetColor(_colorTable, _defaultBgIndex, false);
|
||||
color = indexColor.GetColor(view, _defaultBg, false);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
color = indexColor.GetColor(_colorTable, _defaultBgIndex, true);
|
||||
color = indexColor.GetColor(view, _defaultBg, true);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
}
|
||||
|
||||
|
@ -125,16 +134,18 @@ void TextColorTests::TestRgbColor()
|
|||
VERIFY_IS_FALSE(rgbColor.IsLegacy());
|
||||
VERIFY_IS_TRUE(rgbColor.IsRgb());
|
||||
|
||||
auto color = rgbColor.GetColor(_colorTable, _defaultFgIndex, false);
|
||||
auto view = _GetTableView();
|
||||
|
||||
auto color = rgbColor.GetColor(view, _defaultFg, false);
|
||||
VERIFY_ARE_EQUAL(myColor, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultFgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultFg, true);
|
||||
VERIFY_ARE_EQUAL(myColor, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, false);
|
||||
color = rgbColor.GetColor(view, _defaultBg, false);
|
||||
VERIFY_ARE_EQUAL(myColor, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultBg, true);
|
||||
VERIFY_ARE_EQUAL(myColor, color);
|
||||
}
|
||||
|
||||
|
@ -147,55 +158,57 @@ void TextColorTests::TestChangeColor()
|
|||
VERIFY_IS_FALSE(rgbColor.IsLegacy());
|
||||
VERIFY_IS_TRUE(rgbColor.IsRgb());
|
||||
|
||||
auto color = rgbColor.GetColor(_colorTable, _defaultFgIndex, false);
|
||||
auto view = _GetTableView();
|
||||
|
||||
auto color = rgbColor.GetColor(view, _defaultFg, false);
|
||||
VERIFY_ARE_EQUAL(myColor, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultFgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultFg, true);
|
||||
VERIFY_ARE_EQUAL(myColor, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, false);
|
||||
color = rgbColor.GetColor(view, _defaultBg, false);
|
||||
VERIFY_ARE_EQUAL(myColor, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultBg, true);
|
||||
VERIFY_ARE_EQUAL(myColor, color);
|
||||
|
||||
rgbColor.SetDefault();
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultFgIndex, false);
|
||||
color = rgbColor.GetColor(view, _defaultFg, false);
|
||||
VERIFY_ARE_EQUAL(_defaultFg, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultFgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultFg, true);
|
||||
VERIFY_ARE_EQUAL(_defaultFg, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, false);
|
||||
color = rgbColor.GetColor(view, _defaultBg, false);
|
||||
VERIFY_ARE_EQUAL(_defaultBg, color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultBg, true);
|
||||
VERIFY_ARE_EQUAL(_defaultBg, color);
|
||||
|
||||
rgbColor.SetIndex(7, false);
|
||||
color = rgbColor.GetColor(_colorTable, _defaultFgIndex, false);
|
||||
color = rgbColor.GetColor(view, _defaultFg, false);
|
||||
VERIFY_ARE_EQUAL(_colorTable[7], color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultFgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultFg, true);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, false);
|
||||
color = rgbColor.GetColor(view, _defaultBg, false);
|
||||
VERIFY_ARE_EQUAL(_colorTable[7], color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultBg, true);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
rgbColor.SetIndex(15, false);
|
||||
color = rgbColor.GetColor(_colorTable, _defaultFgIndex, false);
|
||||
color = rgbColor.GetColor(view, _defaultFg, false);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultFgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultFg, true);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, false);
|
||||
color = rgbColor.GetColor(view, _defaultBg, false);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
|
||||
color = rgbColor.GetColor(_colorTable, _defaultBgIndex, true);
|
||||
color = rgbColor.GetColor(view, _defaultBg, true);
|
||||
VERIFY_ARE_EQUAL(_colorTable[15], color);
|
||||
}
|
||||
|
|
|
@ -61,26 +61,13 @@
|
|||
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\CascadiaResources.build.items" />
|
||||
<Import Project="$(OpenConsoleDir)src\wap-common.build.post.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\WindowsTerminal\WindowsTerminal.vcxproj">
|
||||
<Project>{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalAzBridge\TerminalAzBridge.vcxproj">
|
||||
<Project>{067F0A06-FCB7-472C-96E9-B03B54E8E18D}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\ShellExtension\WindowsTerminalShellExt.vcxproj">
|
||||
<Project>{f2ed628a-db22-446f-a081-4cc845b51a2b}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\wt\wt.vcxproj">
|
||||
<Project>{506fd703-baa7-4f6e-9361-64f550ec8fca}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\host\exe\Host.EXE.vcxproj">
|
||||
<Project>{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\host\proxy\Host.Proxy.vcxproj">
|
||||
<Project>{71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\WindowsTerminal\WindowsTerminal.vcxproj" />
|
||||
<ProjectReference Include="..\..\host\exe\Host.EXE.vcxproj" />
|
||||
<ProjectReference Include="..\..\host\proxy\Host.Proxy.vcxproj" />
|
||||
<ProjectReference Include="..\TerminalAzBridge\TerminalAzBridge.vcxproj" />
|
||||
<ProjectReference Include="..\ShellExtension\WindowsTerminalShellExt.vcxproj" />
|
||||
<ProjectReference Include="..\wt\wt.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="OpenConsoleStompSourceProjectForWapProject" BeforeTargets="_ConvertItems">
|
||||
|
@ -159,12 +146,12 @@
|
|||
<!-- **END VC LIBS HACK** -->
|
||||
|
||||
<!-- This is required to get the package dependency in the AppXManifest. -->
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
</Target>
|
||||
|
||||
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22000.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22000.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22000.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
@ -74,7 +74,7 @@
|
|||
Enabled="false"
|
||||
DisplayName="ms-resource:AppName" />
|
||||
</uap5:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<!-- <uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.console.host"
|
||||
Id="OpenConsole"
|
||||
DisplayName="OpenConsole"
|
||||
|
@ -102,15 +102,15 @@
|
|||
<com:Interface Id="E686C757-9A35-4A1C-B3CE-0BCC8B5C69F4" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
<com:Interface Id="59D55CCE-FC8A-48B4-ACE8-0A9286C6557F" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
</com:ComInterface>
|
||||
</com:Extension>
|
||||
</com:Extension> -->
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer DisplayName="OpenConsole" Executable="OpenConsole.exe">
|
||||
<!-- <com:ExeServer DisplayName="OpenConsole" Executable="OpenConsole.exe">
|
||||
<com:Class Id="2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69"/>
|
||||
</com:ExeServer>
|
||||
<com:ExeServer DisplayName="WindowsTerminal" Executable="WindowsTerminal.exe">
|
||||
<com:Class Id="E12CFF52-A866-4C77-9A90-F570A7AA2C6B"/>
|
||||
</com:ExeServer>
|
||||
</com:ExeServer> -->
|
||||
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
|
||||
<com:Class Id="9f156763-7844-4dc4-b2b1-901f640f5155" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
</com:SurrogateServer>
|
||||
|
|
|
@ -5,11 +5,9 @@
|
|||
|
||||
#include "../TerminalSettingsModel/ColorScheme.h"
|
||||
#include "../TerminalSettingsModel/CascadiaSettings.h"
|
||||
#include "../types/inc/colorTable.hpp"
|
||||
#include "JsonTestClass.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;
|
||||
|
@ -34,293 +32,339 @@ namespace SettingsModelLocalTests
|
|||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(ParseSimpleColorScheme);
|
||||
TEST_METHOD(CanLayerColorScheme);
|
||||
TEST_METHOD(LayerColorSchemeProperties);
|
||||
TEST_METHOD(LayerColorSchemesOnArray);
|
||||
TEST_METHOD(UpdateSchemeReferences);
|
||||
|
||||
static Core::Color rgb(uint8_t r, uint8_t g, uint8_t b) noexcept
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
return Core::Color{ r, g, b, 255 };
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void ColorSchemeTests::ParseSimpleColorScheme()
|
||||
void ColorSchemeTests::CanLayerColorScheme()
|
||||
{
|
||||
const std::string campbellScheme{ "{"
|
||||
"\"background\" : \"#0C0C0C\","
|
||||
"\"black\" : \"#0C0C0C\","
|
||||
"\"blue\" : \"#0037DA\","
|
||||
"\"brightBlack\" : \"#767676\","
|
||||
"\"brightBlue\" : \"#3B78FF\","
|
||||
"\"brightCyan\" : \"#61D6D6\","
|
||||
"\"brightGreen\" : \"#16C60C\","
|
||||
"\"brightPurple\" : \"#B4009E\","
|
||||
"\"brightRed\" : \"#E74856\","
|
||||
"\"brightWhite\" : \"#F2F2F2\","
|
||||
"\"brightYellow\" : \"#F9F1A5\","
|
||||
"\"cursorColor\" : \"#FFFFFF\","
|
||||
"\"cyan\" : \"#3A96DD\","
|
||||
"\"foreground\" : \"#F2F2F2\","
|
||||
"\"green\" : \"#13A10E\","
|
||||
"\"name\" : \"Campbell\","
|
||||
"\"purple\" : \"#881798\","
|
||||
"\"red\" : \"#C50F1F\","
|
||||
"\"selectionBackground\" : \"#131313\","
|
||||
"\"white\" : \"#CCCCCC\","
|
||||
"\"yellow\" : \"#C19C00\""
|
||||
"}" };
|
||||
const std::string scheme0String{ R"({
|
||||
"name": "scheme0",
|
||||
"foreground": "#000000",
|
||||
"background": "#010101"
|
||||
})" };
|
||||
const std::string scheme1String{ R"({
|
||||
"name": "scheme1",
|
||||
"foreground": "#020202",
|
||||
"background": "#030303"
|
||||
})" };
|
||||
const std::string scheme2String{ R"({
|
||||
"name": "scheme0",
|
||||
"foreground": "#040404",
|
||||
"background": "#050505"
|
||||
})" };
|
||||
const std::string scheme3String{ R"({
|
||||
// "name": "scheme3",
|
||||
"foreground": "#060606",
|
||||
"background": "#070707"
|
||||
})" };
|
||||
|
||||
const auto schemeObject = VerifyParseSucceeded(campbellScheme);
|
||||
auto scheme = ColorScheme::FromJson(schemeObject);
|
||||
VERIFY_ARE_EQUAL(L"Campbell", scheme->Name());
|
||||
VERIFY_ARE_EQUAL(til::color(0xf2, 0xf2, 0xf2, 255), til::color{ scheme->Foreground() });
|
||||
VERIFY_ARE_EQUAL(til::color(0x0c, 0x0c, 0x0c, 255), til::color{ scheme->Background() });
|
||||
VERIFY_ARE_EQUAL(til::color(0x13, 0x13, 0x13, 255), til::color{ scheme->SelectionBackground() });
|
||||
VERIFY_ARE_EQUAL(til::color(0xFF, 0xFF, 0xFF, 255), til::color{ scheme->CursorColor() });
|
||||
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
|
||||
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
|
||||
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
|
||||
const auto scheme3Json = VerifyParseSucceeded(scheme3String);
|
||||
|
||||
std::array<COLORREF, COLOR_TABLE_SIZE> expectedCampbellTable;
|
||||
const auto campbellSpan = gsl::make_span(expectedCampbellTable);
|
||||
Utils::InitializeColorTable(campbellSpan);
|
||||
Utils::SetColorTableAlpha(campbellSpan, 0);
|
||||
const auto scheme0 = ColorScheme::FromJson(scheme0Json);
|
||||
|
||||
for (size_t i = 0; i < expectedCampbellTable.size(); i++)
|
||||
{
|
||||
const auto& expected = expectedCampbellTable.at(i);
|
||||
const til::color actual{ scheme->Table().at(static_cast<uint32_t>(i)) };
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme0Json));
|
||||
VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme1Json));
|
||||
VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme2Json));
|
||||
VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme3Json));
|
||||
|
||||
Log::Comment(L"Roundtrip Test for Color Scheme");
|
||||
Json::Value outJson{ scheme->ToJson() };
|
||||
VERIFY_ARE_EQUAL(schemeObject, outJson);
|
||||
const auto scheme1 = ColorScheme::FromJson(scheme1Json);
|
||||
|
||||
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme0Json));
|
||||
VERIFY_IS_TRUE(scheme1->ShouldBeLayered(scheme1Json));
|
||||
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme2Json));
|
||||
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme3Json));
|
||||
|
||||
const auto scheme3 = ColorScheme::FromJson(scheme3Json);
|
||||
|
||||
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme0Json));
|
||||
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme1Json));
|
||||
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme2Json));
|
||||
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme3Json));
|
||||
}
|
||||
|
||||
void ColorSchemeTests::LayerColorSchemeProperties()
|
||||
{
|
||||
const std::string scheme0String{ R"({
|
||||
"name": "scheme0",
|
||||
"foreground": "#000000",
|
||||
"background": "#010101",
|
||||
"selectionBackground": "#010100",
|
||||
"cursorColor": "#010001",
|
||||
"red": "#010000",
|
||||
"green": "#000100",
|
||||
"blue": "#000001"
|
||||
})" };
|
||||
const std::string scheme1String{ R"({
|
||||
"name": "scheme1",
|
||||
"foreground": "#020202",
|
||||
"background": "#030303",
|
||||
"selectionBackground": "#020200",
|
||||
"cursorColor": "#040004",
|
||||
"red": "#020000",
|
||||
|
||||
"blue": "#000002"
|
||||
})" };
|
||||
const std::string scheme2String{ R"({
|
||||
"name": "scheme0",
|
||||
"foreground": "#040404",
|
||||
"background": "#050505",
|
||||
"selectionBackground": "#030300",
|
||||
"cursorColor": "#060006",
|
||||
"red": "#030000",
|
||||
"green": "#000300"
|
||||
})" };
|
||||
|
||||
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
|
||||
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
|
||||
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
|
||||
|
||||
auto scheme0 = ColorScheme::FromJson(scheme0Json);
|
||||
VERIFY_ARE_EQUAL(L"scheme0", scheme0->_Name);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 0), scheme0->_SelectionBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 1), scheme0->_CursorColor);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0->_table[XTERM_GREEN_ATTR]);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 1), scheme0->_table[XTERM_BLUE_ATTR]);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Layering scheme1 on top of scheme0"));
|
||||
scheme0->LayerJson(scheme1Json);
|
||||
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0->_Background);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 0), scheme0->_SelectionBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 4, 0, 4), scheme0->_CursorColor);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0->_table[XTERM_GREEN_ATTR]);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0->_table[XTERM_BLUE_ATTR]);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Layering scheme2Json on top of (scheme0+scheme1)"));
|
||||
scheme0->LayerJson(scheme2Json);
|
||||
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 0), scheme0->_SelectionBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 6, 0, 6), scheme0->_CursorColor);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 3, 0), scheme0->_table[XTERM_GREEN_ATTR]);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0->_table[XTERM_BLUE_ATTR]);
|
||||
}
|
||||
|
||||
void ColorSchemeTests::LayerColorSchemesOnArray()
|
||||
{
|
||||
static constexpr std::string_view inboxSettings{ R"({
|
||||
"schemes": [
|
||||
{
|
||||
"background": "#0C0C0C",
|
||||
"black": "#0C0C0C",
|
||||
"blue": "#0037DA",
|
||||
"brightBlack": "#767676",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightRed": "#E74856",
|
||||
"brightWhite": "#F2F2F2",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#3A96DD",
|
||||
"foreground": "#CCCCCC",
|
||||
"green": "#13A10E",
|
||||
"name": "Campbell",
|
||||
"purple": "#881798",
|
||||
"red": "#C50F1F",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#CCCCCC",
|
||||
"yellow": "#C19C00"
|
||||
}
|
||||
]
|
||||
const std::string scheme0String{ R"({
|
||||
"name": "scheme0",
|
||||
"foreground": "#000000",
|
||||
"background": "#010101"
|
||||
})" };
|
||||
static constexpr std::string_view userSettings{ R"({
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
{
|
||||
"background": "#121314",
|
||||
"black": "#121314",
|
||||
"blue": "#121314",
|
||||
"brightBlack": "#121314",
|
||||
"brightBlue": "#121314",
|
||||
"brightCyan": "#121314",
|
||||
"brightGreen": "#121314",
|
||||
"brightPurple": "#121314",
|
||||
"brightRed": "#121314",
|
||||
"brightWhite": "#121314",
|
||||
"brightYellow": "#121314",
|
||||
"cursorColor": "#121314",
|
||||
"cyan": "#121314",
|
||||
"foreground": "#121314",
|
||||
"green": "#121314",
|
||||
"name": "Campbell",
|
||||
"purple": "#121314",
|
||||
"red": "#121314",
|
||||
"selectionBackground": "#121314",
|
||||
"white": "#121314",
|
||||
"yellow": "#121314"
|
||||
},
|
||||
{
|
||||
"background": "#012456",
|
||||
"black": "#0C0C0C",
|
||||
"blue": "#0037DA",
|
||||
"brightBlack": "#767676",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightRed": "#E74856",
|
||||
"brightWhite": "#F2F2F2",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#3A96DD",
|
||||
"foreground": "#CCCCCC",
|
||||
"green": "#13A10E",
|
||||
"name": "Campbell Powershell",
|
||||
"purple": "#881798",
|
||||
"red": "#C50F1F",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#CCCCCC",
|
||||
"yellow": "#C19C00"
|
||||
}
|
||||
]
|
||||
const std::string scheme1String{ R"({
|
||||
"name": "scheme1",
|
||||
"foreground": "#020202",
|
||||
"background": "#030303"
|
||||
})" };
|
||||
const std::string scheme2String{ R"({
|
||||
"name": "scheme0",
|
||||
"foreground": "#040404",
|
||||
"background": "#050505"
|
||||
})" };
|
||||
const std::string scheme3String{ R"({
|
||||
// by not providing a name, the scheme will have the name ""
|
||||
"foreground": "#060606",
|
||||
"background": "#070707"
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<CascadiaSettings>(userSettings, inboxSettings);
|
||||
const auto scheme0Json = VerifyParseSucceeded(scheme0String);
|
||||
const auto scheme1Json = VerifyParseSucceeded(scheme1String);
|
||||
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
|
||||
const auto scheme3Json = VerifyParseSucceeded(scheme3String);
|
||||
|
||||
const auto colorSchemes = settings->GlobalSettings().ColorSchemes();
|
||||
VERIFY_ARE_EQUAL(2u, colorSchemes.Size());
|
||||
auto settings = winrt::make_self<CascadiaSettings>();
|
||||
|
||||
const auto scheme0 = winrt::get_self<ColorScheme>(colorSchemes.Lookup(L"Campbell"));
|
||||
VERIFY_ARE_EQUAL(rgb(0x12, 0x13, 0x14), scheme0->Foreground());
|
||||
VERIFY_ARE_EQUAL(rgb(0x12, 0x13, 0x14), scheme0->Background());
|
||||
VERIFY_ARE_EQUAL(0u, settings->_globals->ColorSchemes().Size());
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme0Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme1Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme2Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
|
||||
|
||||
const auto scheme1 = winrt::get_self<ColorScheme>(colorSchemes.Lookup(L"Campbell Powershell"));
|
||||
VERIFY_ARE_EQUAL(rgb(0xCC, 0xCC, 0xCC), scheme1->Foreground());
|
||||
VERIFY_ARE_EQUAL(rgb(0x01, 0x24, 0x56), scheme1->Background());
|
||||
settings->_LayerOrCreateColorScheme(scheme0Json);
|
||||
{
|
||||
for (auto kv : settings->_globals->ColorSchemes())
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"kv:%s->%s", kv.Key().data(), kv.Value().Name().data()));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(1u, settings->_globals->ColorSchemes().Size());
|
||||
|
||||
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
|
||||
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
|
||||
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
|
||||
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme1Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background);
|
||||
}
|
||||
|
||||
settings->_LayerOrCreateColorScheme(scheme1Json);
|
||||
|
||||
{
|
||||
VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size());
|
||||
|
||||
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
|
||||
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
|
||||
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
|
||||
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
|
||||
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
|
||||
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
|
||||
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_Background);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background);
|
||||
}
|
||||
settings->_LayerOrCreateColorScheme(scheme2Json);
|
||||
|
||||
{
|
||||
VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size());
|
||||
|
||||
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
|
||||
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
|
||||
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
|
||||
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
|
||||
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
|
||||
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
|
||||
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background);
|
||||
}
|
||||
settings->_LayerOrCreateColorScheme(scheme3Json);
|
||||
|
||||
{
|
||||
VERIFY_ARE_EQUAL(3u, settings->_globals->ColorSchemes().Size());
|
||||
|
||||
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
|
||||
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
|
||||
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
|
||||
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
|
||||
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
|
||||
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
|
||||
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L""));
|
||||
auto scheme2Proj = settings->_globals->ColorSchemes().Lookup(L"");
|
||||
auto scheme2 = winrt::get_self<ColorScheme>(scheme2Proj);
|
||||
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_Background);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_Background);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 6, 6, 6), scheme2->_Foreground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2->_Background);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorSchemeTests::UpdateSchemeReferences()
|
||||
{
|
||||
static constexpr std::string_view settingsString{ R"json({
|
||||
"defaultProfile": "Inherited reference",
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"colorScheme": "Campbell"
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"name": "Explicit scheme reference",
|
||||
"colorScheme": "Campbell"
|
||||
},
|
||||
{
|
||||
"name": "Explicit reference; hidden",
|
||||
"colorScheme": "Campbell",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "Inherited reference"
|
||||
},
|
||||
{
|
||||
"name": "Different reference",
|
||||
"colorScheme": "One Half Dark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemes": [
|
||||
{
|
||||
"background": "#0C0C0C",
|
||||
"black": "#0C0C0C",
|
||||
"blue": "#0037DA",
|
||||
"brightBlack": "#767676",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightRed": "#E74856",
|
||||
"brightWhite": "#F2F2F2",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#3A96DD",
|
||||
"foreground": "#CCCCCC",
|
||||
"green": "#13A10E",
|
||||
"name": "Campbell",
|
||||
"purple": "#881798",
|
||||
"red": "#C50F1F",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#CCCCCC",
|
||||
"yellow": "#C19C00"
|
||||
},
|
||||
{
|
||||
"background": "#0C0C0C",
|
||||
"black": "#0C0C0C",
|
||||
"blue": "#0037DA",
|
||||
"brightBlack": "#767676",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightRed": "#E74856",
|
||||
"brightWhite": "#F2F2F2",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#3A96DD",
|
||||
"foreground": "#CCCCCC",
|
||||
"green": "#13A10E",
|
||||
"name": "Campbell (renamed)",
|
||||
"purple": "#881798",
|
||||
"red": "#C50F1F",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#CCCCCC",
|
||||
"yellow": "#C19C00"
|
||||
},
|
||||
{
|
||||
"background": "#282C34",
|
||||
"black": "#282C34",
|
||||
"blue": "#61AFEF",
|
||||
"brightBlack": "#5A6374",
|
||||
"brightBlue": "#61AFEF",
|
||||
"brightCyan": "#56B6C2",
|
||||
"brightGreen": "#98C379",
|
||||
"brightPurple": "#C678DD",
|
||||
"brightRed": "#E06C75",
|
||||
"brightWhite": "#DCDFE4",
|
||||
"brightYellow": "#E5C07B",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cyan": "#56B6C2",
|
||||
"foreground": "#DCDFE4",
|
||||
"green": "#98C379",
|
||||
"name": "One Half Dark",
|
||||
"purple": "#C678DD",
|
||||
"red": "#E06C75",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"white": "#DCDFE4",
|
||||
"yellow": "#E5C07B"
|
||||
}
|
||||
]
|
||||
})json" };
|
||||
const std::string settingsString{ R"json({
|
||||
"defaultProfile": "Inherited reference",
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"colorScheme": "Scheme 1"
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"name": "Explicit scheme reference",
|
||||
"colorScheme": "Scheme 1"
|
||||
},
|
||||
{
|
||||
"name": "Explicit reference; hidden",
|
||||
"colorScheme": "Scheme 1",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "Inherited reference"
|
||||
},
|
||||
{
|
||||
"name": "Different reference",
|
||||
"colorScheme": "Scheme 2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemes": [
|
||||
{ "name": "Scheme 1" },
|
||||
{ "name": "Scheme 2" },
|
||||
{ "name": "Scheme 1 (renamed)" }
|
||||
]
|
||||
})json" };
|
||||
|
||||
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString) };
|
||||
auto settings{ winrt::make_self<CascadiaSettings>(false) };
|
||||
settings->_ParseJsonString(settingsString, false);
|
||||
settings->_ApplyDefaultsFromUserSettings();
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
const auto newName{ L"Campbell (renamed)" };
|
||||
settings->UpdateColorSchemeReferences(L"Campbell", newName);
|
||||
// update all references to "Scheme 1"
|
||||
const auto newName{ L"Scheme 1 (renamed)" };
|
||||
settings->UpdateColorSchemeReferences(L"Scheme 1", newName);
|
||||
|
||||
// verify profile defaults
|
||||
Log::Comment(L"Profile Defaults");
|
||||
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasColorSchemeName());
|
||||
|
||||
// verify all other profiles
|
||||
const auto& profiles{ settings->AllProfiles() };
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(0) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(1) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(2) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_FALSE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(3) };
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().ColorSchemeName());
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(L"Scheme 2", prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,12 @@ namespace SettingsModelLocalTests
|
|||
TEST_METHOD(TestLayerOnAutogeneratedName);
|
||||
|
||||
TEST_METHOD(TestGenerateCommandline);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void CommandTests::ManyCommandsSameAction()
|
||||
|
@ -146,10 +152,6 @@ namespace SettingsModelLocalTests
|
|||
{ "name": "command4", "command": { "action": "splitPane" } },
|
||||
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
|
||||
{ "name": "command6", "command": { "action": "splitPane", "size": 0.25 } },
|
||||
{ "name": "command7", "command": { "action": "splitPane", "split": "right" } },
|
||||
{ "name": "command8", "command": { "action": "splitPane", "split": "left" } },
|
||||
{ "name": "command9", "command": { "action": "splitPane", "split": "up" } },
|
||||
{ "name": "command10", "command": { "action": "splitPane", "split": "down" } },
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
@ -158,7 +160,7 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(9u, commands.Size());
|
||||
VERIFY_ARE_EQUAL(5u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"command1");
|
||||
|
@ -168,7 +170,7 @@ namespace SettingsModelLocalTests
|
|||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
|
@ -179,7 +181,7 @@ namespace SettingsModelLocalTests
|
|||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
|
@ -190,7 +192,7 @@ namespace SettingsModelLocalTests
|
|||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
|
@ -201,7 +203,7 @@ namespace SettingsModelLocalTests
|
|||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
|
@ -212,53 +214,9 @@ namespace SettingsModelLocalTests
|
|||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.25, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"command7");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"command8");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Left, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"command9");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Up, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"command10");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestSplitPaneBadSize()
|
||||
|
@ -286,7 +244,7 @@ namespace SettingsModelLocalTests
|
|||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.25, realArgs.SplitSize());
|
||||
}
|
||||
}
|
||||
|
@ -328,10 +286,8 @@ namespace SettingsModelLocalTests
|
|||
|
||||
const std::string commands0String{ R"([
|
||||
{ "command": { "action": "splitPane", "split": null } },
|
||||
{ "command": { "action": "splitPane", "split": "left" } },
|
||||
{ "command": { "action": "splitPane", "split": "right" } },
|
||||
{ "command": { "action": "splitPane", "split": "up" } },
|
||||
{ "command": { "action": "splitPane", "split": "down" } },
|
||||
{ "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "command": { "action": "splitPane", "split": "none" } },
|
||||
{ "command": { "action": "splitPane" } },
|
||||
{ "command": { "action": "splitPane", "split": "auto" } },
|
||||
|
@ -345,10 +301,10 @@ namespace SettingsModelLocalTests
|
|||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
|
||||
// There are only 5 commands here: all of the `"none"`, `"auto"`,
|
||||
// There are only 3 commands here: all of the `"none"`, `"auto"`,
|
||||
// `"foo"`, `null`, and <no args> bindings all generate the same action,
|
||||
// which will generate just a single name for all of them.
|
||||
VERIFY_ARE_EQUAL(5u, commands.Size());
|
||||
VERIFY_ARE_EQUAL(3u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane");
|
||||
|
@ -358,47 +314,27 @@ namespace SettingsModelLocalTests
|
|||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane, split: left");
|
||||
auto command = commands.Lookup(L"Split pane, split: vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Left, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane, split: right");
|
||||
auto command = commands.Lookup(L"Split pane, split: horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane, split: up");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Up, realArgs.SplitDirection());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane, split: down");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
void CommandTests::TestLayerOnAutogeneratedName()
|
||||
|
@ -424,7 +360,7 @@ namespace SettingsModelLocalTests
|
|||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,14 +397,6 @@ namespace SettingsModelLocalTests
|
|||
"name":"action6",
|
||||
"command": { "action": "newWindow", "startingDirectory":"C:\\foo", "commandline": "bar.exe" }
|
||||
},
|
||||
{
|
||||
"name":"action7_startingDirectoryWithTrailingSlash",
|
||||
"command": { "action": "newWindow", "startingDirectory":"C:\\", "commandline": "bar.exe" }
|
||||
},
|
||||
{
|
||||
"name":"action8_tabTitleEscaping",
|
||||
"command": { "action": "newWindow", "tabTitle":"\\\";foo\\" }
|
||||
}
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
@ -477,7 +405,7 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(9u, commands.Size());
|
||||
VERIFY_ARE_EQUAL(7u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action0");
|
||||
|
@ -575,35 +503,5 @@ namespace SettingsModelLocalTests
|
|||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\foo\" -- \"bar.exe\"", terminalArgs.ToCommandline());
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action7_startingDirectoryWithTrailingSlash");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\\\\" -- \"bar.exe\"", terminalArgs.ToCommandline());
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action8_tabTitleEscaping");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(LR"-(--title "\\\"\;foo\\")-", terminalArgs.ToCommandline());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,34 +7,44 @@ Module Name:
|
|||
|
||||
Abstract:
|
||||
- This class is a helper that can be used to quickly create tests that need to
|
||||
read & parse json data.
|
||||
read & parse json data. Test classes that need to read JSON should make sure
|
||||
to derive from this class, and also make sure to call InitializeJsonReader()
|
||||
in the TEST_CLASS_SETUP().
|
||||
|
||||
Author(s):
|
||||
Mike Griese (migrie) August-2019
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class JsonTestClass
|
||||
{
|
||||
public:
|
||||
static Json::Value VerifyParseSucceeded(const std::string_view& content)
|
||||
void InitializeJsonReader()
|
||||
{
|
||||
static const std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
_reader = std::unique_ptr<Json::CharReader>(Json::CharReaderBuilder::CharReaderBuilder().newCharReader());
|
||||
};
|
||||
|
||||
void InitializeJsonWriter()
|
||||
{
|
||||
_writer = std::unique_ptr<Json::StreamWriter>(Json::StreamWriterBuilder::StreamWriterBuilder().newStreamWriter());
|
||||
}
|
||||
|
||||
Json::Value VerifyParseSucceeded(std::string content)
|
||||
{
|
||||
Json::Value root;
|
||||
std::string errs;
|
||||
const bool parseResult = reader->parse(content.data(), content.data() + content.size(), &root, &errs);
|
||||
const bool parseResult = _reader->parse(content.c_str(), content.c_str() + content.size(), &root, &errs);
|
||||
VERIFY_IS_TRUE(parseResult, winrt::to_hstring(errs).c_str());
|
||||
return root;
|
||||
};
|
||||
|
||||
static std::string toString(const Json::Value& json)
|
||||
std::string toString(const Json::Value& json)
|
||||
{
|
||||
static const std::unique_ptr<Json::StreamWriter> writer{ Json::StreamWriterBuilder::StreamWriterBuilder().newStreamWriter() };
|
||||
|
||||
std::stringstream s;
|
||||
writer->write(json, &s);
|
||||
_writer->write(json, &s);
|
||||
return s.str();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Json::CharReader> _reader;
|
||||
std::unique_ptr<Json::StreamWriter> _writer;
|
||||
};
|
||||
|
|
|
@ -36,15 +36,10 @@ namespace SettingsModelLocalTests
|
|||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(KeyChords);
|
||||
TEST_METHOD(ManyKeysSameAction);
|
||||
TEST_METHOD(LayerKeybindings);
|
||||
TEST_METHOD(UnbindKeybindings);
|
||||
|
||||
TEST_METHOD(LayerScancodeKeybindings);
|
||||
|
||||
TEST_METHOD(TestExplicitUnbind);
|
||||
|
||||
TEST_METHOD(TestArbitraryArgs);
|
||||
TEST_METHOD(TestSplitPaneArgs);
|
||||
|
||||
|
@ -58,71 +53,13 @@ namespace SettingsModelLocalTests
|
|||
TEST_METHOD(TestMoveTabArgs);
|
||||
|
||||
TEST_METHOD(TestGetKeyBindingForAction);
|
||||
TEST_METHOD(KeybindingsWithoutVkey);
|
||||
};
|
||||
|
||||
void KeyBindingsTests::KeyChords()
|
||||
{
|
||||
struct testCase
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
VirtualKeyModifiers modifiers;
|
||||
int32_t vkey;
|
||||
int32_t scanCode;
|
||||
std::wstring_view expected;
|
||||
};
|
||||
|
||||
static constexpr std::array testCases{
|
||||
testCase{
|
||||
VirtualKeyModifiers::None,
|
||||
'A',
|
||||
0,
|
||||
L"a",
|
||||
},
|
||||
testCase{
|
||||
VirtualKeyModifiers::Control,
|
||||
'A',
|
||||
0,
|
||||
L"ctrl+a",
|
||||
},
|
||||
testCase{
|
||||
VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift,
|
||||
VK_OEM_PLUS,
|
||||
0,
|
||||
L"ctrl+shift+plus",
|
||||
},
|
||||
testCase{
|
||||
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
|
||||
255,
|
||||
0,
|
||||
L"win+ctrl+alt+shift+vk(255)",
|
||||
},
|
||||
testCase{
|
||||
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
|
||||
0,
|
||||
123,
|
||||
L"win+ctrl+alt+shift+sc(123)",
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& tc : testCases)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"Testing case:\"%s\"", tc.expected.data()));
|
||||
|
||||
const auto actualString = KeyChordSerialization::ToString({ tc.modifiers, tc.vkey, tc.scanCode });
|
||||
VERIFY_ARE_EQUAL(tc.expected, actualString);
|
||||
|
||||
auto expectedVkey = tc.vkey;
|
||||
if (!expectedVkey)
|
||||
{
|
||||
expectedVkey = MapVirtualKeyW(tc.scanCode, MAPVK_VSC_TO_VK_EX);
|
||||
}
|
||||
|
||||
const auto actualKeyChord = KeyChordSerialization::FromString(actualString);
|
||||
VERIFY_ARE_EQUAL(tc.modifiers, actualKeyChord.Modifiers());
|
||||
VERIFY_ARE_EQUAL(expectedVkey, actualKeyChord.Vkey());
|
||||
VERIFY_ARE_EQUAL(tc.scanCode, actualKeyChord.ScanCode());
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void KeyBindingsTests::ManyKeysSameAction()
|
||||
{
|
||||
|
@ -138,6 +75,7 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
|
@ -161,6 +99,7 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
|
@ -190,6 +129,7 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings5Json = VerifyParseSucceeded(bindings5String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
|
@ -202,7 +142,7 @@ namespace SettingsModelLocalTests
|
|||
L"Try unbinding a key using `\"unbound\"` to unbind the key"));
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key using `null` to unbind the key"));
|
||||
|
@ -212,7 +152,7 @@ namespace SettingsModelLocalTests
|
|||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings3Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key using an unrecognized command to unbind the key"));
|
||||
|
@ -222,7 +162,7 @@ namespace SettingsModelLocalTests
|
|||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings4Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key using a straight up invalid value to unbind the key"));
|
||||
|
@ -232,38 +172,13 @@ namespace SettingsModelLocalTests
|
|||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings5Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key that wasn't bound at all"));
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestExplicitUnbind()
|
||||
{
|
||||
const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
|
||||
const std::string bindings1String{ R"([ { "command": "unbound", "keys": ["ctrl+c"] } ])" };
|
||||
const std::string bindings2String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
|
||||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
|
||||
const KeyChord keyChord{ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 };
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
|
||||
|
||||
actionMap->LayerJson(bindings1Json);
|
||||
VERIFY_IS_TRUE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
|
||||
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') }));
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestArbitraryArgs()
|
||||
|
@ -288,6 +203,7 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size());
|
||||
|
@ -295,9 +211,10 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` without args parses as Copy(SingleLine=false)"));
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
|
@ -305,9 +222,10 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` with args parses them correctly"));
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('C'), 0 };
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
|
@ -315,9 +233,10 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` with args parses them correctly"));
|
||||
KeyChord kc{ false, true, true, false, static_cast<int32_t>('C'), 0 };
|
||||
KeyChord kc{ false, true, true, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_TRUE(realArgs.SingleLine());
|
||||
}
|
||||
|
@ -325,10 +244,11 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `newTab` without args parses as NewTab(Index=null)"));
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('T'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('T') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_NULL(realArgs.TerminalArgs().ProfileIndex());
|
||||
|
@ -336,10 +256,11 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `newTab` parses args correctly"));
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('T'), 0 };
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('T') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
|
||||
|
@ -349,10 +270,11 @@ namespace SettingsModelLocalTests
|
|||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `newTab` with an index greater than the legacy "
|
||||
L"args afforded parses correctly"));
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('Y'), 0 };
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('Y') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
|
||||
|
@ -362,10 +284,11 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` ignores args it doesn't understand"));
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
|
@ -373,10 +296,11 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` null as it's `args` parses as the default option"));
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
|
@ -384,10 +308,11 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `adjustFontSize` with a positive delta parses args correctly"));
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(1, realArgs.Delta());
|
||||
}
|
||||
|
@ -395,10 +320,11 @@ namespace SettingsModelLocalTests
|
|||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `adjustFontSize` with a negative delta parses args correctly"));
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(-1, realArgs.Delta());
|
||||
}
|
||||
|
@ -416,41 +342,46 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,33 +396,37 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TabColor());
|
||||
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
|
||||
VERIFY_ARE_EQUAL(til::color(0x563412), til::color(realArgs.TabColor().Value()));
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
|
@ -506,14 +441,16 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_FALSE(realArgs.SingleLine());
|
||||
}
|
||||
|
@ -533,56 +470,63 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.RowsToScroll());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.RowsToScroll());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.RowsToScroll());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.RowsToScroll());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
|
||||
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_DOWN), 0 };
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_DOWN) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
|
||||
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
|
||||
|
@ -591,6 +535,7 @@ namespace SettingsModelLocalTests
|
|||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "scrollDown", "rowsToScroll": -1 } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
|
@ -606,23 +551,26 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Forward);
|
||||
}
|
||||
{
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Backward);
|
||||
}
|
||||
|
@ -636,6 +584,7 @@ namespace SettingsModelLocalTests
|
|||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
|
@ -652,31 +601,35 @@ namespace SettingsModelLocalTests
|
|||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::CommandLine);
|
||||
}
|
||||
|
@ -684,6 +637,7 @@ namespace SettingsModelLocalTests
|
|||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "commandPalette", "launchMode": "bad" } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
|
@ -715,6 +669,7 @@ namespace SettingsModelLocalTests
|
|||
};
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
|
@ -722,7 +677,7 @@ namespace SettingsModelLocalTests
|
|||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) };
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }, kbd);
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A') }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with args");
|
||||
|
@ -733,7 +688,7 @@ namespace SettingsModelLocalTests
|
|||
args->SingleLine(true);
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) };
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }, kbd);
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B') }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with new terminal args");
|
||||
|
@ -745,7 +700,7 @@ namespace SettingsModelLocalTests
|
|||
auto args{ winrt::make_self<implementation::NewTabArgs>(*newTerminalArgs) };
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) };
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }, kbd);
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C') }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with hidden args");
|
||||
|
@ -753,45 +708,7 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P'), 0 }, kbd);
|
||||
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P') }, kbd);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::LayerScancodeKeybindings()
|
||||
{
|
||||
Log::Comment(L"Layering a keybinding with a character literal on top of"
|
||||
L" an equivalent sc() key should replace it.");
|
||||
|
||||
// Wrap the first one in `R"!(...)!"` because it has `()` internally.
|
||||
const std::string bindings0String{ R"!([ { "command": "quakeMode", "keys":"win+sc(41)" } ])!" };
|
||||
const std::string bindings1String{ R"([ { "keys": "win+`", "command": { "action": "globalSummon", "monitor": "any" } } ])" };
|
||||
const std::string bindings2String{ R"([ { "keys": "ctrl+shift+`", "command": { "action": "quakeMode" } } ])" };
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
|
||||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
|
||||
actionMap->LayerJson(bindings1Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size(), L"Layering the second action should replace the first one.");
|
||||
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
|
||||
}
|
||||
|
||||
void KeyBindingsTests::KeybindingsWithoutVkey()
|
||||
{
|
||||
const auto json = VerifyParseSucceeded(R"!([{"command": "quakeMode", "keys":"shift+sc(255)"}])!");
|
||||
|
||||
const auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
actionMap->LayerJson(json);
|
||||
|
||||
const auto action = actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Shift, 0, 255 });
|
||||
VERIFY_IS_NOT_NULL(action);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#include "../TerminalSettingsModel/CascadiaSettings.h"
|
||||
#include "JsonTestClass.h"
|
||||
|
||||
#include <defaults.h>
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace WEX::Logging;
|
||||
|
@ -34,86 +32,81 @@ namespace SettingsModelLocalTests
|
|||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(ProfileGeneratesGuid);
|
||||
TEST_METHOD(CanLayerProfile);
|
||||
TEST_METHOD(LayerProfileProperties);
|
||||
TEST_METHOD(LayerProfileIcon);
|
||||
TEST_METHOD(LayerProfilesOnArray);
|
||||
TEST_METHOD(DuplicateProfileTest);
|
||||
TEST_METHOD(TestGenGuidsForProfiles);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void ProfileTests::ProfileGeneratesGuid()
|
||||
void ProfileTests::CanLayerProfile()
|
||||
{
|
||||
// Parse some profiles without guids. We should NOT generate new guids
|
||||
// for them. If a profile doesn't have a GUID, we'll leave its _guid
|
||||
// set to nullopt. The Profile::Guid() getter will
|
||||
// ensure all profiles have a GUID that's actually set.
|
||||
// The null guid _is_ a valid guid, so we won't re-generate that
|
||||
// guid. null is _not_ a valid guid, so we'll leave that nullopt
|
||||
const std::string profile0String{ R"({
|
||||
"name" : "profile0",
|
||||
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
const std::string profile1String{ R"({
|
||||
"name" : "profile1",
|
||||
"guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
const std::string profile2String{ R"({
|
||||
"name" : "profile2",
|
||||
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
const std::string profile3String{ R"({
|
||||
"name" : "profile3"
|
||||
})" };
|
||||
|
||||
// See SettingsTests::ValidateProfilesGenerateGuids for a version of
|
||||
// this test that includes synthesizing GUIDS for profiles without GUIDs
|
||||
// set
|
||||
|
||||
const std::string profileWithoutGuid{ R"({
|
||||
"name" : "profile0"
|
||||
})" };
|
||||
const std::string secondProfileWithoutGuid{ R"({
|
||||
"name" : "profile1"
|
||||
})" };
|
||||
const std::string profileWithNullForGuid{ R"({
|
||||
"name" : "profile2",
|
||||
"guid" : null
|
||||
})" };
|
||||
const std::string profileWithNullGuid{ R"({
|
||||
"name" : "profile3",
|
||||
"guid" : "{00000000-0000-0000-0000-000000000000}"
|
||||
})" };
|
||||
const std::string profileWithGuid{ R"({
|
||||
"name" : "profile4",
|
||||
"guid" : "{6239a42c-1de4-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
|
||||
const auto profile0Json = VerifyParseSucceeded(profileWithoutGuid);
|
||||
const auto profile1Json = VerifyParseSucceeded(secondProfileWithoutGuid);
|
||||
const auto profile2Json = VerifyParseSucceeded(profileWithNullForGuid);
|
||||
const auto profile3Json = VerifyParseSucceeded(profileWithNullGuid);
|
||||
const auto profile4Json = VerifyParseSucceeded(profileWithGuid);
|
||||
const auto profile0Json = VerifyParseSucceeded(profile0String);
|
||||
const auto profile1Json = VerifyParseSucceeded(profile1String);
|
||||
const auto profile2Json = VerifyParseSucceeded(profile2String);
|
||||
const auto profile3Json = VerifyParseSucceeded(profile3String);
|
||||
|
||||
const auto profile0 = implementation::Profile::FromJson(profile0Json);
|
||||
|
||||
VERIFY_IS_FALSE(profile0->ShouldBeLayered(profile1Json));
|
||||
VERIFY_IS_TRUE(profile0->ShouldBeLayered(profile2Json));
|
||||
VERIFY_IS_FALSE(profile0->ShouldBeLayered(profile3Json));
|
||||
|
||||
const auto profile1 = implementation::Profile::FromJson(profile1Json);
|
||||
const auto profile2 = implementation::Profile::FromJson(profile2Json);
|
||||
|
||||
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile0Json));
|
||||
// A profile _can_ be layered with itself, though what's the point?
|
||||
VERIFY_IS_TRUE(profile1->ShouldBeLayered(profile1Json));
|
||||
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile2Json));
|
||||
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile3Json));
|
||||
|
||||
const auto profile3 = implementation::Profile::FromJson(profile3Json);
|
||||
const auto profile4 = implementation::Profile::FromJson(profile4Json);
|
||||
const winrt::guid cmdGuid = Utils::GuidFromString(L"{6239a42c-1de4-49a3-80bd-e8fdd045185c}");
|
||||
const winrt::guid nullGuid{};
|
||||
|
||||
VERIFY_IS_FALSE(profile0->HasGuid());
|
||||
VERIFY_IS_FALSE(profile1->HasGuid());
|
||||
VERIFY_IS_FALSE(profile2->HasGuid());
|
||||
VERIFY_IS_TRUE(profile3->HasGuid());
|
||||
VERIFY_IS_TRUE(profile4->HasGuid());
|
||||
|
||||
VERIFY_ARE_EQUAL(profile3->Guid(), nullGuid);
|
||||
VERIFY_ARE_EQUAL(profile4->Guid(), cmdGuid);
|
||||
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile0Json));
|
||||
// A profile _can_ be layered with itself, though what's the point?
|
||||
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile1Json));
|
||||
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile2Json));
|
||||
VERIFY_IS_TRUE(profile3->ShouldBeLayered(profile3Json));
|
||||
}
|
||||
|
||||
void ProfileTests::LayerProfileProperties()
|
||||
{
|
||||
static constexpr std::string_view profile0String{ R"({
|
||||
const std::string profile0String{ R"({
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"foreground": "#000000",
|
||||
"background": "#010101",
|
||||
"selectionBackground": "#010101"
|
||||
})" };
|
||||
static constexpr std::string_view profile1String{ R"({
|
||||
const std::string profile1String{ R"({
|
||||
"name": "profile1",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"foreground": "#020202",
|
||||
"startingDirectory": "C:/"
|
||||
})" };
|
||||
static constexpr std::string_view profile2String{ R"({
|
||||
const std::string profile2String{ R"({
|
||||
"name": "profile2",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"foreground": "#030303",
|
||||
|
@ -179,21 +172,21 @@ namespace SettingsModelLocalTests
|
|||
|
||||
void ProfileTests::LayerProfileIcon()
|
||||
{
|
||||
static constexpr std::string_view profile0String{ R"({
|
||||
const std::string profile0String{ R"({
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"icon": "not-null.png"
|
||||
})" };
|
||||
static constexpr std::string_view profile1String{ R"({
|
||||
const std::string profile1String{ R"({
|
||||
"name": "profile1",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"icon": null
|
||||
})" };
|
||||
static constexpr std::string_view profile2String{ R"({
|
||||
const std::string profile2String{ R"({
|
||||
"name": "profile2",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
static constexpr std::string_view profile3String{ R"({
|
||||
const std::string profile3String{ R"({
|
||||
"name": "profile3",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"icon": "another-real.png"
|
||||
|
@ -235,116 +228,102 @@ namespace SettingsModelLocalTests
|
|||
|
||||
void ProfileTests::LayerProfilesOnArray()
|
||||
{
|
||||
static constexpr std::string_view inboxProfiles{ R"({
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
|
||||
}, {
|
||||
"name" : "profile1",
|
||||
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
|
||||
}, {
|
||||
"name" : "profile2",
|
||||
"guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
|
||||
}
|
||||
]
|
||||
const std::string profile0String{ R"({
|
||||
"name" : "profile0",
|
||||
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
static constexpr std::string_view userProfiles{ R"({
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile3",
|
||||
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
|
||||
}, {
|
||||
"name" : "profile4",
|
||||
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
|
||||
}
|
||||
]
|
||||
const std::string profile1String{ R"({
|
||||
"name" : "profile1",
|
||||
"guid" : "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
const std::string profile2String{ R"({
|
||||
"name" : "profile2",
|
||||
"guid" : "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
const std::string profile3String{ R"({
|
||||
"name" : "profile3",
|
||||
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
const std::string profile4String{ R"({
|
||||
"name" : "profile4",
|
||||
"guid" : "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(userProfiles, inboxProfiles);
|
||||
const auto allProfiles = settings->AllProfiles();
|
||||
VERIFY_ARE_EQUAL(3u, allProfiles.Size());
|
||||
VERIFY_ARE_EQUAL(L"profile3", allProfiles.GetAt(0).Name());
|
||||
VERIFY_ARE_EQUAL(L"profile4", allProfiles.GetAt(1).Name());
|
||||
VERIFY_ARE_EQUAL(L"profile2", allProfiles.GetAt(2).Name());
|
||||
const auto profile0Json = VerifyParseSucceeded(profile0String);
|
||||
const auto profile1Json = VerifyParseSucceeded(profile1String);
|
||||
const auto profile2Json = VerifyParseSucceeded(profile2String);
|
||||
const auto profile3Json = VerifyParseSucceeded(profile3String);
|
||||
const auto profile4Json = VerifyParseSucceeded(profile4String);
|
||||
|
||||
auto settings = winrt::make_self<implementation::CascadiaSettings>();
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->_allProfiles.Size());
|
||||
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile0Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile1Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile3Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile4Json));
|
||||
|
||||
settings->_LayerOrCreateProfile(profile0Json);
|
||||
VERIFY_ARE_EQUAL(1u, settings->_allProfiles.Size());
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile1Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
|
||||
|
||||
settings->_LayerOrCreateProfile(profile1Json);
|
||||
VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size());
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
|
||||
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
|
||||
|
||||
settings->_LayerOrCreateProfile(profile2Json);
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
|
||||
VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name());
|
||||
|
||||
settings->_LayerOrCreateProfile(profile3Json);
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
|
||||
VERIFY_ARE_EQUAL(L"profile3", settings->_allProfiles.GetAt(0).Name());
|
||||
|
||||
settings->_LayerOrCreateProfile(profile4Json);
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
|
||||
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
|
||||
VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(0).Name());
|
||||
}
|
||||
|
||||
void ProfileTests::DuplicateProfileTest()
|
||||
{
|
||||
static constexpr std::string_view userProfiles{ R"({
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"font": {
|
||||
"size": 123
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"backgroundImage": "file:///some/path",
|
||||
"hidden": false,
|
||||
}
|
||||
]
|
||||
}
|
||||
const std::string profile0String{ R"({
|
||||
"name" : "profile0",
|
||||
"backgroundImage" : "some//path"
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(userProfiles);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto duplicatedProfile = settings->DuplicateProfile(profile);
|
||||
const auto profile0Json = VerifyParseSucceeded(profile0String);
|
||||
|
||||
// GH#11392: Ensure duplicated profiles properly inherit the base layer, even for nested objects.
|
||||
VERIFY_ARE_EQUAL(123, duplicatedProfile.FontInfo().FontSize());
|
||||
auto settings = winrt::make_self<implementation::CascadiaSettings>();
|
||||
|
||||
duplicatedProfile.Guid(profile.Guid());
|
||||
duplicatedProfile.Name(profile.Name());
|
||||
settings->_LayerOrCreateProfile(profile0Json);
|
||||
auto duplicatedProfile = settings->DuplicateProfile(*settings->_FindMatchingProfile(profile0Json));
|
||||
duplicatedProfile.Name(L"profile0");
|
||||
|
||||
const auto json = winrt::get_self<implementation::Profile>(profile)->ToJson();
|
||||
const auto duplicatedJson = winrt::get_self<implementation::Profile>(duplicatedProfile)->ToJson();
|
||||
VERIFY_ARE_EQUAL(json, duplicatedJson, til::u8u16(toString(duplicatedJson)).c_str());
|
||||
}
|
||||
|
||||
void ProfileTests::TestGenGuidsForProfiles()
|
||||
{
|
||||
// We'll generate GUIDs in the Profile::Guid getter. We should make sure that
|
||||
// the GUID generated for a dynamic profile (with a source) is different
|
||||
// than that of a profile without a source.
|
||||
|
||||
static constexpr std::string_view inboxSettings{ R"({
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"source": "Terminal.App.UnitTest.0"
|
||||
},
|
||||
{
|
||||
"name" : "profile1"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
static constexpr std::string_view userSettings{ R"({
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"source": "Terminal.App.UnitTest.0",
|
||||
},
|
||||
{
|
||||
"name": "profile0"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(userSettings, inboxSettings);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size());
|
||||
|
||||
VERIFY_ARE_EQUAL(L"profile0", settings->AllProfiles().GetAt(0).Name());
|
||||
VERIFY_IS_TRUE(settings->AllProfiles().GetAt(0).HasGuid());
|
||||
VERIFY_IS_FALSE(settings->AllProfiles().GetAt(0).Source().empty());
|
||||
|
||||
VERIFY_ARE_EQUAL(L"profile0", settings->AllProfiles().GetAt(1).Name());
|
||||
VERIFY_IS_TRUE(settings->AllProfiles().GetAt(1).HasGuid());
|
||||
VERIFY_IS_TRUE(settings->AllProfiles().GetAt(1).Source().empty());
|
||||
|
||||
VERIFY_ARE_NOT_EQUAL(settings->AllProfiles().GetAt(0).Guid(), settings->AllProfiles().GetAt(1).Guid());
|
||||
VERIFY_ARE_EQUAL(profile0Json, duplicatedJson);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "JsonTestClass.h"
|
||||
#include "TestUtils.h"
|
||||
#include <defaults.h>
|
||||
#include "../ut_app/TestDynamicProfileGenerator.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace WEX::Logging;
|
||||
|
@ -42,6 +43,13 @@ namespace SettingsModelLocalTests
|
|||
TEST_METHOD(CascadiaSettings);
|
||||
TEST_METHOD(LegacyFontSettings);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
InitializeJsonWriter();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Method Description:
|
||||
// - deserializes and reserializes a json string representing a settings object model of type T
|
||||
|
@ -53,7 +61,7 @@ namespace SettingsModelLocalTests
|
|||
// Return Value:
|
||||
// - the JsonObject representing this instance
|
||||
template<typename T>
|
||||
void RoundtripTest(const std::string_view& jsonString)
|
||||
void RoundtripTest(const std::string& jsonString)
|
||||
{
|
||||
const auto json{ VerifyParseSucceeded(jsonString) };
|
||||
const auto settings{ T::FromJson(json) };
|
||||
|
@ -69,7 +77,7 @@ namespace SettingsModelLocalTests
|
|||
|
||||
void SerializationTests::GlobalSettings()
|
||||
{
|
||||
static constexpr std::string_view globalsString{ R"(
|
||||
const std::string globalsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
|
||||
|
@ -97,7 +105,6 @@ namespace SettingsModelLocalTests
|
|||
"confirmCloseAllTabs": true,
|
||||
"largePasteWarning": true,
|
||||
"multiLinePasteWarning": true,
|
||||
"trimPaste": true,
|
||||
|
||||
"experimental.input.forceVT": false,
|
||||
"experimental.rendering.forceFullRepaint": false,
|
||||
|
@ -106,7 +113,7 @@ namespace SettingsModelLocalTests
|
|||
"actions": []
|
||||
})" };
|
||||
|
||||
static constexpr std::string_view smallGlobalsString{ R"(
|
||||
const std::string smallGlobalsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
"actions": []
|
||||
|
@ -118,7 +125,7 @@ namespace SettingsModelLocalTests
|
|||
|
||||
void SerializationTests::Profile()
|
||||
{
|
||||
static constexpr std::string_view profileString{ R"(
|
||||
const std::string profileString{ R"(
|
||||
{
|
||||
"name": "Windows PowerShell",
|
||||
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
|
@ -153,7 +160,7 @@ namespace SettingsModelLocalTests
|
|||
"selectionBackground": "#CCAABB",
|
||||
|
||||
"useAcrylic": false,
|
||||
"opacity": 50,
|
||||
"acrylicOpacity": 0.5,
|
||||
|
||||
"backgroundImage": "made_you_look.jpeg",
|
||||
"backgroundImageStretchMode": "uniformToFill",
|
||||
|
@ -168,7 +175,7 @@ namespace SettingsModelLocalTests
|
|||
"experimental.retroTerminalEffect": false
|
||||
})" };
|
||||
|
||||
static constexpr std::string_view smallProfileString{ R"(
|
||||
const std::string smallProfileString{ R"(
|
||||
{
|
||||
"name": "Custom Profile"
|
||||
})" };
|
||||
|
@ -176,7 +183,7 @@ namespace SettingsModelLocalTests
|
|||
// Setting "tabColor" to null tests two things:
|
||||
// - null should count as an explicit user-set value, not falling back to the parent's value
|
||||
// - null should be acceptable even though we're working with colors
|
||||
static constexpr std::string_view weirdProfileString{ R"(
|
||||
const std::string weirdProfileString{ R"(
|
||||
{
|
||||
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
|
||||
"name": "Weird Profile",
|
||||
|
@ -193,7 +200,7 @@ namespace SettingsModelLocalTests
|
|||
|
||||
void SerializationTests::ColorScheme()
|
||||
{
|
||||
static constexpr std::string_view schemeString{ R"({
|
||||
const std::string schemeString{ R"({
|
||||
"name": "Campbell",
|
||||
|
||||
"cursorColor": "#FFFFFF",
|
||||
|
@ -226,56 +233,56 @@ namespace SettingsModelLocalTests
|
|||
void SerializationTests::Actions()
|
||||
{
|
||||
// simple command
|
||||
static constexpr std::string_view actionsString1{ R"([
|
||||
const std::string actionsString1{ R"([
|
||||
{ "command": "paste" }
|
||||
])" };
|
||||
|
||||
// complex command
|
||||
static constexpr std::string_view actionsString2A{ R"([
|
||||
const std::string actionsString2A{ R"([
|
||||
{ "command": { "action": "setTabColor" } }
|
||||
])" };
|
||||
static constexpr std::string_view actionsString2B{ R"([
|
||||
const std::string actionsString2B{ R"([
|
||||
{ "command": { "action": "setTabColor", "color": "#112233" } }
|
||||
])" };
|
||||
static constexpr std::string_view actionsString2C{ R"([
|
||||
const std::string actionsString2C{ R"([
|
||||
{ "command": { "action": "copy" } },
|
||||
{ "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" } }
|
||||
])" };
|
||||
|
||||
// simple command with key chords
|
||||
static constexpr std::string_view actionsString3{ R"([
|
||||
const std::string actionsString3{ R"([
|
||||
{ "command": "toggleAlwaysOnTop", "keys": "ctrl+a" },
|
||||
{ "command": "toggleAlwaysOnTop", "keys": "ctrl+b" }
|
||||
])" };
|
||||
|
||||
// complex command with key chords
|
||||
static constexpr std::string_view actionsString4A{ R"([
|
||||
const std::string actionsString4A{ R"([
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" },
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
|
||||
])" };
|
||||
static constexpr std::string_view actionsString4B{ R"([
|
||||
const std::string actionsString4B{ R"([
|
||||
{ "command": { "action": "findMatch", "direction": "next" }, "keys": "ctrl+shift+s" },
|
||||
{ "command": { "action": "findMatch", "direction": "prev" }, "keys": "ctrl+shift+r" }
|
||||
])" };
|
||||
|
||||
// command with name and icon and multiple key chords
|
||||
static constexpr std::string_view actionsString5{ R"([
|
||||
const std::string actionsString5{ R"([
|
||||
{ "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "keys": "ctrl+e" },
|
||||
{ "command": "scrollToTop", "keys": "ctrl+f" }
|
||||
])" };
|
||||
|
||||
// complex command with new terminal args
|
||||
static constexpr std::string_view actionsString6{ R"([
|
||||
const std::string actionsString6{ R"([
|
||||
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+g" },
|
||||
])" };
|
||||
|
||||
// complex command with meaningful null arg
|
||||
static constexpr std::string_view actionsString7{ R"([
|
||||
const std::string actionsString7{ R"([
|
||||
{ "command": { "action": "renameWindow", "name": null }, "keys": "ctrl+h" }
|
||||
])" };
|
||||
|
||||
// nested command
|
||||
static constexpr std::string_view actionsString8{ R"([
|
||||
const std::string actionsString8{ R"([
|
||||
{
|
||||
"name": "Change font size...",
|
||||
"commands": [
|
||||
|
@ -287,7 +294,7 @@ namespace SettingsModelLocalTests
|
|||
])" };
|
||||
|
||||
// iterable command
|
||||
static constexpr std::string_view actionsString9A{ R"([
|
||||
const std::string actionsString9A{ R"([
|
||||
{
|
||||
"name": "New tab",
|
||||
"commands": [
|
||||
|
@ -300,12 +307,12 @@ namespace SettingsModelLocalTests
|
|||
]
|
||||
}
|
||||
])" };
|
||||
static constexpr std::string_view actionsString9B{ R"([
|
||||
const std::string actionsString9B{ R"([
|
||||
{
|
||||
"commands":
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"command":
|
||||
"command":
|
||||
{
|
||||
"action": "sendInput",
|
||||
"input": "${profile.name}"
|
||||
|
@ -316,15 +323,15 @@ namespace SettingsModelLocalTests
|
|||
"name": "Send Input ..."
|
||||
}
|
||||
])" };
|
||||
static constexpr std::string_view actionsString9C{ R""([
|
||||
const std::string actionsString9C{ R""([
|
||||
{
|
||||
"commands":
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"commands":
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"command":
|
||||
"command":
|
||||
{
|
||||
"action": "sendInput",
|
||||
"input": "${profile.name} ${scheme.name}"
|
||||
|
@ -339,9 +346,9 @@ namespace SettingsModelLocalTests
|
|||
"name": "Send Input (Evil) ..."
|
||||
}
|
||||
])"" };
|
||||
static constexpr std::string_view actionsString9D{ R""([
|
||||
const std::string actionsString9D{ R""([
|
||||
{
|
||||
"command":
|
||||
"command":
|
||||
{
|
||||
"action": "newTab",
|
||||
"profile": "${profile.name}"
|
||||
|
@ -353,7 +360,7 @@ namespace SettingsModelLocalTests
|
|||
])"" };
|
||||
|
||||
// unbound command
|
||||
static constexpr std::string_view actionsString10{ R"([
|
||||
const std::string actionsString10{ R"([
|
||||
{ "command": "unbound", "keys": "ctrl+c" }
|
||||
])" };
|
||||
|
||||
|
@ -396,77 +403,81 @@ namespace SettingsModelLocalTests
|
|||
|
||||
void SerializationTests::CascadiaSettings()
|
||||
{
|
||||
static constexpr std::string_view settingsString{ R"({
|
||||
"$help" : "https://aka.ms/terminal-documentation",
|
||||
"$schema" : "https://aka.ms/terminal-profiles-schema",
|
||||
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
|
||||
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"font": {
|
||||
"face": "Zamora Code"
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"font": { "face": "Cascadia Code" },
|
||||
"guid": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
|
||||
"name": "HowettShell"
|
||||
},
|
||||
{
|
||||
"hidden": true,
|
||||
"guid": "{c08b0496-e71c-5503-b84e-3af7a7a6d2a7}",
|
||||
"name": "BhojwaniShell"
|
||||
},
|
||||
{
|
||||
"antialiasingMode": "aliased",
|
||||
"guid": "{fe9df758-ac22-5c20-922d-c7766cdd13af}",
|
||||
"name": "NiksaShell"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemes": [
|
||||
{
|
||||
"name": "Cinnamon Roll",
|
||||
const std::string settingsString{ R"({
|
||||
"$schema": "https://aka.ms/terminal-profiles-schema",
|
||||
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
|
||||
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
|
||||
|
||||
"cursorColor": "#FFFFFD",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"font": {
|
||||
"face": "Zamora Code"
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"font": { "face": "Cascadia Code" },
|
||||
"guid": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
|
||||
"name": "HowettShell"
|
||||
},
|
||||
{
|
||||
"hidden": true,
|
||||
"name": "BhojwaniShell"
|
||||
},
|
||||
{
|
||||
"antialiasingMode": "aliased",
|
||||
"name": "NiksaShell"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemes": [
|
||||
{
|
||||
"name": "Cinnamon Roll",
|
||||
|
||||
"background": "#3C0315",
|
||||
"foreground": "#FFFFFD",
|
||||
"cursorColor": "#FFFFFD",
|
||||
"selectionBackground": "#FFFFFF",
|
||||
|
||||
"black": "#282A2E",
|
||||
"blue": "#0170C5",
|
||||
"cyan": "#3F8D83",
|
||||
"green": "#76AB23",
|
||||
"purple": "#7D498F",
|
||||
"red": "#BD0940",
|
||||
"white": "#FFFFFD",
|
||||
"yellow": "#E0DE48",
|
||||
"brightBlack": "#676E7A",
|
||||
"brightBlue": "#5C98C5",
|
||||
"brightCyan": "#8ABEB7",
|
||||
"brightGreen": "#B5D680",
|
||||
"brightPurple": "#AC79BB",
|
||||
"brightRed": "#BD6D85",
|
||||
"brightWhite": "#FFFFFD",
|
||||
"brightYellow": "#FFFD76"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{ "command": { "action": "sendInput", "input": "VT Griese Mode" }, "keys": "ctrl+k" }
|
||||
]
|
||||
})" };
|
||||
"background": "#3C0315",
|
||||
"foreground": "#FFFFFD",
|
||||
|
||||
const auto settings{ winrt::make_self<implementation::CascadiaSettings>(settingsString) };
|
||||
"black": "#282A2E",
|
||||
"blue": "#0170C5",
|
||||
"cyan": "#3F8D83",
|
||||
"green": "#76AB23",
|
||||
"purple": "#7D498F",
|
||||
"red": "#BD0940",
|
||||
"white": "#FFFFFD",
|
||||
"yellow": "#E0DE48",
|
||||
"brightBlack": "#676E7A",
|
||||
"brightBlue": "#5C98C5",
|
||||
"brightCyan": "#8ABEB7",
|
||||
"brightGreen": "#B5D680",
|
||||
"brightPurple": "#AC79BB",
|
||||
"brightRed": "#BD6D85",
|
||||
"brightWhite": "#FFFFFD",
|
||||
"brightYellow": "#FFFD76"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{ "command": { "action": "renameTab", "title": "Liang Tab" }, "keys": "ctrl+t" },
|
||||
{ "command": { "action": "sendInput", "input": "VT Griese Mode" }, "keys": "ctrl+k" },
|
||||
{ "command": { "action": "renameWindow", "name": "Hecker Window" }, "keys": "ctrl+l" }
|
||||
]
|
||||
})" };
|
||||
|
||||
auto settings{ winrt::make_self<implementation::CascadiaSettings>(false) };
|
||||
settings->_ParseJsonString(settingsString, false);
|
||||
settings->_ApplyDefaultsFromUserSettings();
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
const auto result{ settings->ToJson() };
|
||||
VERIFY_ARE_EQUAL(toString(VerifyParseSucceeded(settingsString)), toString(result));
|
||||
VERIFY_ARE_EQUAL(toString(settings->_userSettings), toString(result));
|
||||
}
|
||||
|
||||
void SerializationTests::LegacyFontSettings()
|
||||
{
|
||||
static constexpr std::string_view profileString{ R"(
|
||||
const std::string profileString{ R"(
|
||||
{
|
||||
"name": "Profile with legacy font settings",
|
||||
|
||||
|
@ -475,7 +486,7 @@ namespace SettingsModelLocalTests
|
|||
"fontWeight": "normal"
|
||||
})" };
|
||||
|
||||
static constexpr std::string_view expectedOutput{ R"(
|
||||
const std::string expectedOutput{ R"(
|
||||
{
|
||||
"name": "Profile with legacy font settings",
|
||||
|
||||
|
|
|
@ -98,10 +98,10 @@
|
|||
<!-- 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.2.7.0-prerelease.210913003\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\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.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
#include "pch.h"
|
||||
|
||||
#include <til/rand.h>
|
||||
|
||||
#include "../TerminalSettingsModel/CascadiaSettings.h"
|
||||
#include "../TerminalSettingsModel/TerminalSettings.h"
|
||||
#include "TestUtils.h"
|
||||
|
@ -36,13 +34,13 @@ namespace SettingsModelLocalTests
|
|||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(TryCreateWinRTType);
|
||||
|
||||
TEST_METHOD(TestTerminalArgsForBinding);
|
||||
TEST_METHOD(CommandLineToArgvW);
|
||||
TEST_METHOD(GetProfileForArgsWithCommandline);
|
||||
TEST_METHOD(MakeSettingsForProfile);
|
||||
|
||||
TEST_METHOD(MakeSettingsForProfileThatDoesntExist);
|
||||
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
|
||||
|
||||
TEST_METHOD(TestLayerProfileOnColorScheme);
|
||||
TEST_METHOD(TestCommandlineToTitlePromotion);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
|
@ -60,145 +58,12 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize);
|
||||
}
|
||||
|
||||
// CascadiaSettings::_normalizeCommandLine abuses some aspects from CommandLineToArgvW
|
||||
// to simplify the implementation. It assumes that all arguments returned by
|
||||
// CommandLineToArgvW are returned back to back in memory as "arg1\0arg2\0arg3\0...".
|
||||
// This test ensures CommandLineToArgvW doesn't change just to be sure.
|
||||
void TerminalSettingsTests::CommandLineToArgvW()
|
||||
{
|
||||
pcg_engines::oneseq_dxsm_64_32 rng{ til::gen_random<uint64_t>() };
|
||||
|
||||
const auto expectedArgc = static_cast<int>(rng(16) + 1);
|
||||
std::wstring expectedArgv;
|
||||
std::wstring input;
|
||||
|
||||
// We generate up to 16 arguments. Each argument is up to 64 chars long, is quoted
|
||||
// (2 chars, only applies to the input) and separated by a whitespace (1 char).
|
||||
expectedArgv.reserve(expectedArgc * 65);
|
||||
input.reserve(expectedArgc * 67);
|
||||
|
||||
for (int i = 0; i < expectedArgc; ++i)
|
||||
{
|
||||
const bool useQuotes = static_cast<bool>(rng(2));
|
||||
const auto count = static_cast<size_t>(rng(64));
|
||||
const auto ch = static_cast<wchar_t>(rng('z' - 'a' + 1) + 'a');
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
expectedArgv.push_back(L'\0');
|
||||
input.push_back(L' ');
|
||||
}
|
||||
|
||||
if (useQuotes)
|
||||
{
|
||||
input.push_back(L'"');
|
||||
}
|
||||
|
||||
expectedArgv.append(count, ch);
|
||||
input.append(count, ch);
|
||||
|
||||
if (useQuotes)
|
||||
{
|
||||
input.push_back(L'"');
|
||||
}
|
||||
}
|
||||
|
||||
int argc;
|
||||
wil::unique_hlocal_ptr<PWSTR[]> argv{ ::CommandLineToArgvW(input.c_str(), &argc) };
|
||||
VERIFY_ARE_EQUAL(expectedArgc, argc);
|
||||
VERIFY_IS_NOT_NULL(argv);
|
||||
|
||||
const auto lastArg = argv[argc - 1];
|
||||
const auto beg = argv[0];
|
||||
const auto end = lastArg + wcslen(lastArg);
|
||||
VERIFY_IS_GREATER_THAN(end, beg);
|
||||
VERIFY_ARE_EQUAL(expectedArgv.size(), static_cast<size_t>(end - beg));
|
||||
VERIFY_ARE_EQUAL(0, memcmp(beg, expectedArgv.data(), expectedArgv.size()));
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::GetProfileForArgsWithCommandline()
|
||||
{
|
||||
// I'm exclusively using cmd.exe as I know exactly where it resides at.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"historySize": 123
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"commandline": "%SystemRoot%\\System32\\cmd.exe"
|
||||
},
|
||||
{
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"commandline": "cmd.exe /A"
|
||||
},
|
||||
{
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"commandline": "cmd.exe /A /B"
|
||||
},
|
||||
{
|
||||
"guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
|
||||
"commandline": "cmd.exe /A /C",
|
||||
"connectionType": "{9a9977a7-1fe0-49c0-b6c0-13a0cd1c98a1}"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
|
||||
struct TestCase
|
||||
{
|
||||
std::wstring_view input;
|
||||
int expected;
|
||||
};
|
||||
|
||||
static constexpr std::array testCases{
|
||||
// Base test.
|
||||
TestCase{ L"cmd.exe", 0 },
|
||||
// SearchPathW() normalization + case insensitive matching.
|
||||
TestCase{ L"cmd.exe /a", 1 },
|
||||
TestCase{ L"C:\\Windows\\System32\\cmd.exe /A", 1 },
|
||||
// Test that we don't pick the equally long but different "/A /B" variant.
|
||||
TestCase{ L"C:\\Windows\\System32\\cmd.exe /A /C", 1 },
|
||||
// Test that we don't pick the shorter "/A" variant,
|
||||
// but do pick the shorter "/A /B" variant for longer inputs.
|
||||
TestCase{ L"cmd.exe /A /B", 2 },
|
||||
TestCase{ L"cmd.exe /A /B /C", 2 },
|
||||
// Ignore profiles with a connection type, like the Azure cloud shell.
|
||||
// Instead it should pick any other prefix.
|
||||
TestCase{ L"C:\\Windows\\System32\\cmd.exe /A /C", 1 },
|
||||
// Return base layer profile for missing profiles.
|
||||
TestCase{ L"C:\\Windows\\regedit.exe", -1 },
|
||||
};
|
||||
|
||||
for (const auto& testCase : testCases)
|
||||
{
|
||||
NewTerminalArgs args;
|
||||
args.Commandline(testCase.input);
|
||||
|
||||
const auto profile = settings->GetProfileForArgs(args);
|
||||
VERIFY_IS_NOT_NULL(profile);
|
||||
|
||||
if (testCase.expected < 0)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(123, profile.HistorySize());
|
||||
}
|
||||
else
|
||||
{
|
||||
GUID expectedGUID{ 0x6239a42c, static_cast<uint16_t>(0x1111 * testCase.expected), 0x49a3, { 0x80, 0xbd, 0xe8, 0xfd, 0xd0, 0x45, 0x18, 0x5c } };
|
||||
VERIFY_ARE_EQUAL(expectedGUID, static_cast<const GUID&>(profile.Guid()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::TestTerminalArgsForBinding()
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": { "list": [
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
|
@ -217,9 +82,6 @@ namespace SettingsModelLocalTests
|
|||
"commandline": "wsl.exe"
|
||||
}
|
||||
],
|
||||
"defaults": {
|
||||
"historySize": 29
|
||||
} },
|
||||
"keybindings": [
|
||||
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "vertical", "profile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" } },
|
||||
|
@ -239,46 +101,46 @@ namespace SettingsModelLocalTests
|
|||
const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") };
|
||||
const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
auto actionMap = settings->GlobalSettings().ActionMap();
|
||||
VERIFY_ARE_EQUAL(3u, settings->ActiveProfiles().Size());
|
||||
auto actionMap = settings.GlobalSettings().ActionMap();
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
const auto profile2Guid = settings->ActiveProfiles().GetAt(2).Guid();
|
||||
const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid();
|
||||
VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid);
|
||||
|
||||
const auto& actionMapImpl{ winrt::get_self<implementation::ActionMap>(actionMap) };
|
||||
VERIFY_ARE_EQUAL(12u, actionMapImpl->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('A'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('B'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('B') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -286,21 +148,21 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid1, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -308,21 +170,21 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid1, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -330,21 +192,21 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -352,30 +214,21 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
if constexpr (Feature_ShowProfileDefaultsInSettings::IsEnabled())
|
||||
{
|
||||
// This action specified a command but no profile; it gets reassigned to the base profile
|
||||
VERIFY_ARE_EQUAL(settings->ProfileDefaults(), profile);
|
||||
VERIFY_ARE_EQUAL(29, termSettings.HistorySize());
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL(guid0, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -384,15 +237,15 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid1, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
|
@ -404,15 +257,15 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
|
@ -425,16 +278,16 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('I'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
|
@ -448,16 +301,16 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('J'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('J') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
|
@ -470,16 +323,16 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('K'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('K') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
|
@ -493,16 +346,16 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('L'), 0 };
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('L') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
|
@ -518,10 +371,10 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid1, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
|
@ -529,10 +382,10 @@ namespace SettingsModelLocalTests
|
|||
}
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::MakeSettingsForProfile()
|
||||
void TerminalSettingsTests::MakeSettingsForProfileThatDoesntExist()
|
||||
{
|
||||
// Test that making settings generally works.
|
||||
static constexpr std::string_view settingsString{ R"(
|
||||
// Test that making settings throws when the GUID doesn't exist
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -548,39 +401,39 @@ namespace SettingsModelLocalTests
|
|||
}
|
||||
]
|
||||
})" };
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsString);
|
||||
CascadiaSettings settings{ til::u8u16(settingsString) };
|
||||
|
||||
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
const auto profile1 = settings->FindProfile(guid1);
|
||||
const auto profile2 = settings->FindProfile(guid2);
|
||||
const auto guid3 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings{ TerminalSettings::CreateWithProfile(*settings, profile1, nullptr) };
|
||||
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid1, nullptr) };
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(1, terminalSettings.DefaultSettings().HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to CreateWithProfile should succeed");
|
||||
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings{ TerminalSettings::CreateWithProfile(*settings, profile2, nullptr) };
|
||||
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid2, nullptr) };
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(2, terminalSettings.DefaultSettings().HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to CreateWithProfile should succeed");
|
||||
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed");
|
||||
}
|
||||
|
||||
VERIFY_THROWS(auto terminalSettings = TerminalSettings::CreateWithProfileByID(settings, guid3, nullptr), wil::ResultException, L"This call to constructor should fail");
|
||||
|
||||
try
|
||||
{
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(*settings, nullptr, nullptr) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) };
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.DefaultSettings().HistorySize());
|
||||
}
|
||||
|
@ -596,7 +449,7 @@ namespace SettingsModelLocalTests
|
|||
// defaultProfile that's not in the list, we validate the settings, and
|
||||
// then call MakeSettings(nullopt). The validation should ensure that
|
||||
// the default profile is something reasonable
|
||||
static constexpr std::string_view settingsString{ R"(
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -612,14 +465,14 @@ namespace SettingsModelLocalTests
|
|||
}
|
||||
]
|
||||
})" };
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsString);
|
||||
CascadiaSettings settings{ til::u8u16(settingsString) };
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, settings->Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings->ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(settings->GlobalSettings().DefaultProfile(), settings->ActiveProfiles().GetAt(0).Guid());
|
||||
VERIFY_ARE_EQUAL(2u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings.ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(settings.GlobalSettings().DefaultProfile(), settings.ActiveProfiles().GetAt(0).Guid());
|
||||
try
|
||||
{
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(*settings, nullptr, nullptr) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) };
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.DefaultSettings().HistorySize());
|
||||
}
|
||||
|
@ -634,7 +487,7 @@ namespace SettingsModelLocalTests
|
|||
Log::Comment(NoThrowString().Format(
|
||||
L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly."));
|
||||
|
||||
static constexpr std::string_view settings0String{ R"(
|
||||
const std::string settings0String{ R"(
|
||||
{
|
||||
"defaultProfile": "profile5",
|
||||
"profiles": [
|
||||
|
@ -667,50 +520,18 @@ namespace SettingsModelLocalTests
|
|||
"schemes": [
|
||||
{
|
||||
"name": "schemeWithCursorColor",
|
||||
"cursorColor": "#123456",
|
||||
"black": "#121314",
|
||||
"red": "#121314",
|
||||
"green": "#121314",
|
||||
"yellow": "#121314",
|
||||
"blue": "#121314",
|
||||
"purple": "#121314",
|
||||
"cyan": "#121314",
|
||||
"white": "#121314",
|
||||
"brightBlack": "#121314",
|
||||
"brightRed": "#121314",
|
||||
"brightGreen": "#121314",
|
||||
"brightYellow": "#121314",
|
||||
"brightBlue": "#121314",
|
||||
"brightPurple": "#121314",
|
||||
"brightCyan": "#121314",
|
||||
"brightWhite": "#121314"
|
||||
"cursorColor": "#123456"
|
||||
},
|
||||
{
|
||||
"name": "schemeWithoutCursorColor",
|
||||
"black": "#121314",
|
||||
"red": "#121314",
|
||||
"green": "#121314",
|
||||
"yellow": "#121314",
|
||||
"blue": "#121314",
|
||||
"purple": "#121314",
|
||||
"cyan": "#121314",
|
||||
"white": "#121314",
|
||||
"brightBlack": "#121314",
|
||||
"brightRed": "#121314",
|
||||
"brightGreen": "#121314",
|
||||
"brightYellow": "#121314",
|
||||
"brightBlue": "#121314",
|
||||
"brightPurple": "#121314",
|
||||
"brightCyan": "#121314",
|
||||
"brightWhite": "#121314"
|
||||
"name": "schemeWithoutCursorColor"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settings0String);
|
||||
CascadiaSettings settings{ til::u8u16(settings0String) };
|
||||
|
||||
VERIFY_ARE_EQUAL(6u, settings->ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings->GlobalSettings().ColorSchemes().Size());
|
||||
VERIFY_ARE_EQUAL(6u, settings.ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings.GlobalSettings().ColorSchemes().Size());
|
||||
|
||||
auto createTerminalSettings = [&](const auto& profile, const auto& schemes) {
|
||||
auto terminalSettings{ winrt::make_self<implementation::TerminalSettings>() };
|
||||
|
@ -719,14 +540,12 @@ namespace SettingsModelLocalTests
|
|||
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);
|
||||
auto terminalSettings0 = createTerminalSettings(settings.ActiveProfiles().GetAt(0), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings1 = createTerminalSettings(settings.ActiveProfiles().GetAt(1), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings2 = createTerminalSettings(settings.ActiveProfiles().GetAt(2), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings3 = createTerminalSettings(settings.ActiveProfiles().GetAt(3), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings4 = createTerminalSettings(settings.ActiveProfiles().GetAt(4), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings5 = createTerminalSettings(settings.ActiveProfiles().GetAt(5), settings.GlobalSettings().ColorSchemes());
|
||||
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme
|
||||
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default
|
||||
|
@ -735,83 +554,4 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4->CursorColor()); // from profile (no color scheme)
|
||||
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5->CursorColor()); // default
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::TestCommandlineToTitlePromotion()
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": { "list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
},
|
||||
],
|
||||
"defaults": {
|
||||
"historySize": 29
|
||||
} }
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
|
||||
{ // just a profile (profile wins)
|
||||
NewTerminalArgs args{};
|
||||
args.Profile(L"profile0");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"profile0", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{ // profile and command line -> no promotion (profile wins)
|
||||
NewTerminalArgs args{};
|
||||
args.Profile(L"profile0");
|
||||
args.Commandline(L"foo.exe");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"profile0", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{ // just a title -> it is propagated
|
||||
NewTerminalArgs args{};
|
||||
args.TabTitle(L"Analog Kid");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"Analog Kid", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{ // title and command line -> no promotion
|
||||
NewTerminalArgs args{};
|
||||
args.TabTitle(L"Digital Man");
|
||||
args.Commandline(L"foo.exe");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"Digital Man", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{ // just a commandline -> promotion
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L"foo.exe");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
// various typesof commandline follow
|
||||
{
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L"foo.exe bar");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L"\"foo exe.exe\" bar");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"foo exe.exe", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L"\"\" grand designs");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L" imagine a man");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,6 @@ namespace TerminalAppLocalTests
|
|||
TEST_METHOD(ParseComboCommandlineIntoArgs);
|
||||
TEST_METHOD(ParseFocusTabArgs);
|
||||
TEST_METHOD(ParseMoveFocusArgs);
|
||||
TEST_METHOD(ParseSwapPaneArgs);
|
||||
TEST_METHOD(ParseArgumentsWithParsingTerminators);
|
||||
TEST_METHOD(ParseFocusPaneArgs);
|
||||
|
||||
|
@ -715,7 +714,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
@ -735,7 +734,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
@ -757,7 +756,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
@ -799,7 +798,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -828,7 +827,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, myArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -857,7 +856,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -1031,11 +1030,9 @@ namespace TerminalAppLocalTests
|
|||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
const auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NextTab, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
const auto myArgs = actionAndArgs.Args().as<NextTabArgs>();
|
||||
VERIFY_ARE_EQUAL(TabSwitcherMode::Disabled, myArgs.SwitcherMode().Value());
|
||||
VERIFY_IS_NULL(actionAndArgs.Args());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
|
@ -1049,9 +1046,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::PrevTab, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
const auto myArgs = actionAndArgs.Args().as<PrevTabArgs>();
|
||||
VERIFY_ARE_EQUAL(TabSwitcherMode::Disabled, myArgs.SwitcherMode().Value());
|
||||
VERIFY_IS_NULL(actionAndArgs.Args());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
|
@ -1212,119 +1207,6 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::ParseSwapPaneArgs()
|
||||
{
|
||||
const wchar_t* subcommand = L"swap-pane";
|
||||
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Just the subcommand, without a direction, should fail."));
|
||||
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"right" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"up" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Up, myArgs.Direction());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"down" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Down, myArgs.Direction());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"badDirection" };
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"move-pane with an invalid direction should fail."));
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left", L";", subcommand, L"right" };
|
||||
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
|
||||
|
||||
actionAndArgs = appArgs._startupActions.at(2);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::ParseFocusPaneArgs()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
|
@ -1783,7 +1665,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
@ -1803,7 +1685,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
@ -1824,7 +1706,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
@ -1834,7 +1716,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
@ -1856,7 +1738,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
@ -1866,7 +1748,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.7f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
|
|
|
@ -16,31 +16,6 @@ using namespace winrt::Microsoft::Terminal::Control;
|
|||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
static constexpr std::wstring_view inboxSettings{ LR"({
|
||||
"schemes": [{
|
||||
"name": "Campbell",
|
||||
"foreground": "#CCCCCC",
|
||||
"background": "#0C0C0C",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"black": "#0C0C0C",
|
||||
"red": "#C50F1F",
|
||||
"green": "#13A10E",
|
||||
"yellow": "#C19C00",
|
||||
"blue": "#0037DA",
|
||||
"purple": "#881798",
|
||||
"cyan": "#3A96DD",
|
||||
"white": "#CCCCCC",
|
||||
"brightBlack": "#767676",
|
||||
"brightRed": "#E74856",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightWhite": "#F2F2F2"
|
||||
}]
|
||||
})" };
|
||||
|
||||
// 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
|
||||
|
@ -104,7 +79,7 @@ namespace TerminalAppLocalTests
|
|||
// containing a ${profile.name} to replace. When we expand it, it should
|
||||
// have created one command for each profile.
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -132,10 +107,14 @@ namespace TerminalAppLocalTests
|
|||
"iterateOn": "profiles",
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}" }
|
||||
},
|
||||
]
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, inboxSettings };
|
||||
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
|
||||
|
@ -153,7 +132,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -177,7 +156,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -195,7 +174,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -213,7 +192,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -228,7 +207,7 @@ namespace TerminalAppLocalTests
|
|||
// For this test, put an iterable command without a given `name` to
|
||||
// replace. When we expand it, it should still work.
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -255,10 +234,14 @@ namespace TerminalAppLocalTests
|
|||
"iterateOn": "profiles",
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}" }
|
||||
},
|
||||
]
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, inboxSettings };
|
||||
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
|
||||
|
@ -276,7 +259,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -300,7 +283,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -318,7 +301,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -336,7 +319,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -352,7 +335,7 @@ namespace TerminalAppLocalTests
|
|||
// cause bad json to be filled in. Something like a profile with a name
|
||||
// of "Foo\"", so the trailing '"' might break the json parsing.
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -380,10 +363,14 @@ namespace TerminalAppLocalTests
|
|||
"iterateOn": "profiles",
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}" }
|
||||
},
|
||||
]
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, inboxSettings };
|
||||
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
|
||||
|
@ -401,7 +388,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -425,7 +412,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -443,7 +430,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -461,7 +448,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -481,7 +468,7 @@ namespace TerminalAppLocalTests
|
|||
// ├─ first.com
|
||||
// └─ second.com
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -517,10 +504,11 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, inboxSettings };
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
@ -570,7 +558,7 @@ namespace TerminalAppLocalTests
|
|||
// ├─ child1
|
||||
// └─ child2
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -611,10 +599,11 @@ namespace TerminalAppLocalTests
|
|||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, inboxSettings };
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
@ -691,18 +680,18 @@ namespace TerminalAppLocalTests
|
|||
// <Command Palette>
|
||||
// ├─ profile0...
|
||||
// | ├─ Split pane, profile: profile0
|
||||
// | ├─ Split pane, direction: right, profile: profile0
|
||||
// | └─ Split pane, direction: down, profile: profile0
|
||||
// | ├─ Split pane, direction: vertical, profile: profile0
|
||||
// | └─ Split pane, direction: horizontal, profile: profile0
|
||||
// ├─ profile1...
|
||||
// | ├─Split pane, profile: profile1
|
||||
// | ├─Split pane, direction: right, profile: profile1
|
||||
// | └─Split pane, direction: down, profile: profile1
|
||||
// | ├─Split pane, direction: vertical, profile: profile1
|
||||
// | └─Split pane, direction: horizontal, profile: profile1
|
||||
// └─ profile2...
|
||||
// ├─ Split pane, profile: profile2
|
||||
// ├─ Split pane, direction: right, profile: profile2
|
||||
// └─ Split pane, direction: down, profile: profile2
|
||||
// ├─ Split pane, direction: vertical, profile: profile2
|
||||
// └─ Split pane, direction: horizontal, profile: profile2
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -730,14 +719,15 @@ namespace TerminalAppLocalTests
|
|||
"name": "${profile.name}...",
|
||||
"commands": [
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "auto" } },
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "right" } },
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "down" } }
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" } },
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, inboxSettings };
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
@ -772,7 +762,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = childActionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -783,7 +773,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_FALSE(childCommand.HasNestedCommands());
|
||||
}
|
||||
{
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: down, profile: {}", name) };
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: horizontal, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
|
@ -793,7 +783,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = childActionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -804,7 +794,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_FALSE(childCommand.HasNestedCommands());
|
||||
}
|
||||
{
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: right, profile: {}", name) };
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: vertical, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
|
@ -814,7 +804,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = childActionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -838,7 +828,7 @@ namespace TerminalAppLocalTests
|
|||
// ├─ Profile 2
|
||||
// └─ Profile 3
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -870,10 +860,11 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, inboxSettings };
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
@ -924,18 +915,18 @@ namespace TerminalAppLocalTests
|
|||
// └─ New Pane...
|
||||
// ├─ profile0...
|
||||
// | ├─ Split automatically
|
||||
// | ├─ Split right
|
||||
// | └─ Split down
|
||||
// | ├─ Split vertically
|
||||
// | └─ Split horizontally
|
||||
// ├─ profile1...
|
||||
// | ├─ Split automatically
|
||||
// | ├─ Split right
|
||||
// | └─ Split down
|
||||
// | ├─ Split vertically
|
||||
// | └─ Split horizontally
|
||||
// └─ profile2...
|
||||
// ├─ Split automatically
|
||||
// ├─ Split right
|
||||
// └─ Split down
|
||||
// ├─ Split vertically
|
||||
// └─ Split horizontally
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -966,16 +957,17 @@ namespace TerminalAppLocalTests
|
|||
"name": "${profile.name}...",
|
||||
"commands": [
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "auto" } },
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "right" } },
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "down" } }
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" } },
|
||||
{ "command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, inboxSettings };
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
@ -1018,7 +1010,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = childActionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -1029,7 +1021,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_FALSE(childCommand.HasNestedCommands());
|
||||
}
|
||||
{
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: down, profile: {}", name) };
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: horizontal, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
|
@ -1039,7 +1031,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = childActionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -1050,7 +1042,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_FALSE(childCommand.HasNestedCommands());
|
||||
}
|
||||
{
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: right, profile: {}", name) };
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: vertical, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
|
@ -1060,7 +1052,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = childActionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -1079,7 +1071,7 @@ namespace TerminalAppLocalTests
|
|||
// containing a ${profile.name} to replace. When we expand it, it should
|
||||
// have created one command for each profile.
|
||||
|
||||
static constexpr std::wstring_view settingsJson{ LR"(
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -1102,72 +1094,9 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
],
|
||||
"schemes": [
|
||||
{
|
||||
"name": "Campbell",
|
||||
"foreground": "#CCCCCC",
|
||||
"background": "#0C0C0C",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"black": "#0C0C0C",
|
||||
"red": "#C50F1F",
|
||||
"green": "#13A10E",
|
||||
"yellow": "#C19C00",
|
||||
"blue": "#0037DA",
|
||||
"purple": "#881798",
|
||||
"cyan": "#3A96DD",
|
||||
"white": "#CCCCCC",
|
||||
"brightBlack": "#767676",
|
||||
"brightRed": "#E74856",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightWhite": "#F2F2F2"
|
||||
},
|
||||
{
|
||||
"name": "Campbell PowerShell",
|
||||
"foreground": "#CCCCCC",
|
||||
"background": "#012456",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"black": "#0C0C0C",
|
||||
"red": "#C50F1F",
|
||||
"green": "#13A10E",
|
||||
"yellow": "#C19C00",
|
||||
"blue": "#0037DA",
|
||||
"purple": "#881798",
|
||||
"cyan": "#3A96DD",
|
||||
"white": "#CCCCCC",
|
||||
"brightBlack": "#767676",
|
||||
"brightRed": "#E74856",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightWhite": "#F2F2F2"
|
||||
},
|
||||
{
|
||||
"name": "Vintage",
|
||||
"foreground": "#C0C0C0",
|
||||
"background": "#000000",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"black": "#000000",
|
||||
"red": "#800000",
|
||||
"green": "#008000",
|
||||
"yellow": "#808000",
|
||||
"blue": "#000080",
|
||||
"purple": "#800080",
|
||||
"cyan": "#008080",
|
||||
"white": "#C0C0C0",
|
||||
"brightBlack": "#808080",
|
||||
"brightRed": "#FF0000",
|
||||
"brightGreen": "#00FF00",
|
||||
"brightYellow": "#FFFF00",
|
||||
"brightBlue": "#0000FF",
|
||||
"brightPurple": "#FF00FF",
|
||||
"brightCyan": "#00FFFF",
|
||||
"brightWhite": "#FFFFFF"
|
||||
}
|
||||
{ "name": "scheme_0" },
|
||||
{ "name": "scheme_1" },
|
||||
{ "name": "scheme_2" },
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
|
@ -1178,7 +1107,11 @@ namespace TerminalAppLocalTests
|
|||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ settingsJson, {} };
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
// Since at least one profile does not reference a color scheme,
|
||||
// we add a warning saying "the color scheme is unknown"
|
||||
VERIFY_ARE_EQUAL(1u, settings.Warnings().Size());
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
|
@ -1194,7 +1127,7 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
|
@ -1206,6 +1139,8 @@ namespace TerminalAppLocalTests
|
|||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
// This is the same warning as above
|
||||
VERIFY_ARE_EQUAL(1u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
// Yes, this test is testing splitPane with profiles named after each
|
||||
|
@ -1213,7 +1148,7 @@ namespace TerminalAppLocalTests
|
|||
// just easy tests to write.
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Campbell");
|
||||
auto command = expandedCommands.Lookup(L"iterable command scheme_0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -1221,17 +1156,17 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", realArgs.TerminalArgs().Profile());
|
||||
VERIFY_ARE_EQUAL(L"scheme_0", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Campbell PowerShell");
|
||||
auto command = expandedCommands.Lookup(L"iterable command scheme_1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -1239,17 +1174,17 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"Campbell PowerShell", realArgs.TerminalArgs().Profile());
|
||||
VERIFY_ARE_EQUAL(L"scheme_1", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Vintage");
|
||||
auto command = expandedCommands.Lookup(L"iterable command scheme_2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -1257,13 +1192,13 @@ namespace TerminalAppLocalTests
|
|||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection());
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"Vintage", realArgs.TerminalArgs().Profile());
|
||||
VERIFY_ARE_EQUAL(L"scheme_2", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,8 +82,6 @@ namespace TerminalAppLocalTests
|
|||
TEST_METHOD(MoveFocusFromZoomedPane);
|
||||
TEST_METHOD(CloseZoomedPane);
|
||||
|
||||
TEST_METHOD(SwapPanes);
|
||||
|
||||
TEST_METHOD(NextMRUTab);
|
||||
TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder);
|
||||
|
||||
|
@ -95,8 +93,6 @@ namespace TerminalAppLocalTests
|
|||
TEST_METHOD(TestPreviewDismissScheme);
|
||||
TEST_METHOD(TestPreviewSchemeWhilePreviewing);
|
||||
|
||||
TEST_METHOD(TestClampSwitchToTab);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
return true;
|
||||
|
@ -311,7 +307,7 @@ namespace TerminalAppLocalTests
|
|||
// TerminalPage and not only create them successfully, but also create a
|
||||
// tab using those settings successfully.
|
||||
|
||||
static constexpr std::wstring_view settingsJson0{ LR"(
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -328,7 +324,7 @@ namespace TerminalAppLocalTests
|
|||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings0{ settingsJson0, {} };
|
||||
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
|
||||
// This is super wacky, but we can't just initialize the
|
||||
|
@ -357,7 +353,7 @@ namespace TerminalAppLocalTests
|
|||
//
|
||||
// Created to test GH#2455
|
||||
|
||||
static constexpr std::wstring_view settingsJson0{ LR"(
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -374,7 +370,7 @@ namespace TerminalAppLocalTests
|
|||
]
|
||||
})" };
|
||||
|
||||
static constexpr std::wstring_view settingsJson1{ LR"(
|
||||
const std::string settingsJson1{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -386,10 +382,10 @@ namespace TerminalAppLocalTests
|
|||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings0{ settingsJson0, {} };
|
||||
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
|
||||
CascadiaSettings settings1{ settingsJson1, {} };
|
||||
CascadiaSettings settings1{ til::u8u16(settingsJson1) };
|
||||
VERIFY_IS_NOT_NULL(settings1);
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
@ -430,7 +426,7 @@ namespace TerminalAppLocalTests
|
|||
Log::Comment(L"Duplicate the tab, and don't crash");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateFocusedTab();
|
||||
VERIFY_ARE_EQUAL(3u, page->_tabs.Size(), L"We should successfully duplicate a tab hosting a deleted profile.");
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
@ -444,7 +440,7 @@ namespace TerminalAppLocalTests
|
|||
//
|
||||
// Created to test GH#2455
|
||||
|
||||
static constexpr std::wstring_view settingsJson0{ LR"(
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -461,7 +457,7 @@ namespace TerminalAppLocalTests
|
|||
]
|
||||
})" };
|
||||
|
||||
static constexpr std::wstring_view settingsJson1{ LR"(
|
||||
const std::string settingsJson1{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
|
@ -473,10 +469,10 @@ namespace TerminalAppLocalTests
|
|||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings0{ settingsJson0, {} };
|
||||
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
|
||||
CascadiaSettings settings1{ settingsJson1, {} };
|
||||
CascadiaSettings settings1{ til::u8u16(settingsJson1) };
|
||||
VERIFY_IS_NOT_NULL(settings1);
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
@ -508,7 +504,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitDirection::Automatic, 0.5f, page->_MakePane(nullptr, true, nullptr));
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, 0.5f, nullptr);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
|
@ -526,13 +522,13 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitDirection::Automatic, 0.5f, page->_MakePane(nullptr, true, nullptr));
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, 0.5f, nullptr);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(3,
|
||||
VERIFY_ARE_EQUAL(2,
|
||||
tab->GetLeafPaneCount(),
|
||||
L"We should successfully duplicate a pane hosting a deleted profile.");
|
||||
L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
|
@ -558,7 +554,7 @@ namespace TerminalAppLocalTests
|
|||
// - The initialized TerminalPage, ready to use.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> TabTests::_commonSetup()
|
||||
{
|
||||
static constexpr std::wstring_view settingsJson0{ LR"(
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"showTabsInTitlebar": false,
|
||||
|
@ -659,7 +655,7 @@ namespace TerminalAppLocalTests
|
|||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings0{ settingsJson0, {} };
|
||||
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
@ -751,7 +747,7 @@ namespace TerminalAppLocalTests
|
|||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Move focus. We should still be zoomed.");
|
||||
Log::Comment(L"Move focus. This will cause us to un-zoom.");
|
||||
result = RunOnUIThread([&page]() {
|
||||
// Set up action
|
||||
MoveFocusArgs args{ FocusDirection::Left };
|
||||
|
@ -761,7 +757,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_TRUE(firstTab->IsZoomed());
|
||||
VERIFY_IS_FALSE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
@ -821,212 +817,6 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::SwapPanes()
|
||||
{
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Setup 4 panes.");
|
||||
// Create the following layout
|
||||
// -------------------
|
||||
// | 1 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 3 | 4 |
|
||||
// | | |
|
||||
// -------------------
|
||||
uint32_t firstId = 0, secondId = 0, thirdId = 0, fourthId = 0;
|
||||
TestOnUIThread([&]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
firstId = tab->_activePane->Id().value();
|
||||
// We start with 1 tab, split vertically to get
|
||||
// -------------------
|
||||
// | 1 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
page->_SplitPane(SplitDirection::Right, 0.5f, page->_MakePane(nullptr, true, nullptr));
|
||||
secondId = tab->_activePane->Id().value();
|
||||
});
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
// After this the `2` pane is focused, go back to `1` being focused
|
||||
page->_MoveFocus(FocusDirection::Left);
|
||||
});
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
// Split again to make the 3rd tab
|
||||
// -------------------
|
||||
// | 1 | |
|
||||
// | | |
|
||||
// ---------| 2 |
|
||||
// | 3 | |
|
||||
// | | |
|
||||
// -------------------
|
||||
page->_SplitPane(SplitDirection::Down, 0.5f, page->_MakePane(nullptr, true, nullptr));
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
// Split again to make the 3rd tab
|
||||
thirdId = tab->_activePane->Id().value();
|
||||
});
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
// After this the `3` pane is focused, go back to `2` being focused
|
||||
page->_MoveFocus(FocusDirection::Right);
|
||||
});
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
// Split to create the final pane
|
||||
// -------------------
|
||||
// | 1 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 3 | 4 |
|
||||
// | | |
|
||||
// -------------------
|
||||
page->_SplitPane(SplitDirection::Down, 0.5f, page->_MakePane(nullptr, true, nullptr));
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
fourthId = tab->_activePane->Id().value();
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// just to be complete, make sure we actually have 4 different ids
|
||||
VERIFY_ARE_NOT_EQUAL(firstId, fourthId);
|
||||
VERIFY_ARE_NOT_EQUAL(secondId, fourthId);
|
||||
VERIFY_ARE_NOT_EQUAL(thirdId, fourthId);
|
||||
VERIFY_ARE_NOT_EQUAL(firstId, thirdId);
|
||||
VERIFY_ARE_NOT_EQUAL(secondId, thirdId);
|
||||
VERIFY_ARE_NOT_EQUAL(firstId, secondId);
|
||||
});
|
||||
|
||||
// Gratuitous use of sleep to make sure that the UI has updated properly
|
||||
// after each operation.
|
||||
Sleep(250);
|
||||
// Now try to move the pane through the tree
|
||||
Log::Comment(L"Move pane to the left. This should swap panes 3 and 4");
|
||||
// -------------------
|
||||
// | 1 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 4 | 3 |
|
||||
// | | |
|
||||
// -------------------
|
||||
TestOnUIThread([&]() {
|
||||
// Set up action
|
||||
SwapPaneArgs args{ FocusDirection::Left };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleSwapPane(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// Our currently focused pane should be `4`
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
|
||||
|
||||
// Inspect the tree to make sure we swapped
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_secondChild->Id().value());
|
||||
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_secondChild->Id().value());
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
Log::Comment(L"Move pane to up. This should swap panes 1 and 4");
|
||||
// -------------------
|
||||
// | 4 | 2 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 1 | 3 |
|
||||
// | | |
|
||||
// -------------------
|
||||
TestOnUIThread([&]() {
|
||||
// Set up action
|
||||
SwapPaneArgs args{ FocusDirection::Up };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleSwapPane(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// Our currently focused pane should be `4`
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
|
||||
|
||||
// Inspect the tree to make sure we swapped
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_firstChild->Id().value());
|
||||
VERIFY_ARE_EQUAL(firstId, tab->_rootPane->_firstChild->_secondChild->Id().value());
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
Log::Comment(L"Move pane to the right. This should swap panes 2 and 4");
|
||||
// -------------------
|
||||
// | 2 | 4 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 1 | 3 |
|
||||
// | | |
|
||||
// -------------------
|
||||
TestOnUIThread([&]() {
|
||||
// Set up action
|
||||
SwapPaneArgs args{ FocusDirection::Right };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleSwapPane(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// Our currently focused pane should be `4`
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
|
||||
|
||||
// Inspect the tree to make sure we swapped
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_firstChild->Id().value());
|
||||
VERIFY_ARE_EQUAL(secondId, tab->_rootPane->_firstChild->_firstChild->Id().value());
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
Log::Comment(L"Move pane down. This should swap panes 3 and 4");
|
||||
// -------------------
|
||||
// | 2 | 3 |
|
||||
// | | |
|
||||
// -------------------
|
||||
// | 1 | 4 |
|
||||
// | | |
|
||||
// -------------------
|
||||
TestOnUIThread([&]() {
|
||||
// Set up action
|
||||
SwapPaneArgs args{ FocusDirection::Down };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleSwapPane(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Sleep(250);
|
||||
|
||||
TestOnUIThread([&]() {
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
|
||||
// Our currently focused pane should be `4`
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
|
||||
|
||||
// Inspect the tree to make sure we swapped
|
||||
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_secondChild->Id().value());
|
||||
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_firstChild->Id().value());
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::NextMRUTab()
|
||||
{
|
||||
// This is a test for GH#8025 - we want to make sure that we can do both
|
||||
|
@ -1357,8 +1147,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(L"Color should be changed to the preview");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
|
||||
// And we should have stored a function to revert the change.
|
||||
VERIFY_ARE_EQUAL(1u, page->_restorePreviewFuncs.size());
|
||||
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
|
@ -1384,8 +1173,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(L"Color should be changed");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
|
||||
// After preview there should be no more restore functions to execute.
|
||||
VERIFY_ARE_EQUAL(0u, page->_restorePreviewFuncs.size());
|
||||
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1430,6 +1218,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(L"Color should be changed to the preview");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
|
@ -1452,6 +1241,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(L"Color should be the same as it originally was");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1498,6 +1288,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(L"Color should be changed to the preview");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
|
@ -1521,6 +1312,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(L"Color should be changed to the preview");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
|
@ -1546,57 +1338,7 @@ namespace TerminalAppLocalTests
|
|||
|
||||
Log::Comment(L"Color should be changed");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::TestClampSwitchToTab()
|
||||
{
|
||||
Log::Comment(L"Test that switching to a tab index higher than the number of tabs just clamps to the last tab.");
|
||||
|
||||
auto page = _commonSetup();
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
|
||||
Log::Comment(L"Create a second tab");
|
||||
TestOnUIThread([&page]() {
|
||||
NewTerminalArgs newTerminalArgs{ 1 };
|
||||
page->_OpenNewTab(newTerminalArgs);
|
||||
});
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
|
||||
Log::Comment(L"Create a third tab");
|
||||
TestOnUIThread([&page]() {
|
||||
NewTerminalArgs newTerminalArgs{ 2 };
|
||||
page->_OpenNewTab(newTerminalArgs);
|
||||
});
|
||||
VERIFY_ARE_EQUAL(3u, page->_tabs.Size());
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
|
||||
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
|
||||
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Switch to the first tab");
|
||||
page->_SelectTab(0);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
|
||||
|
||||
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
|
||||
VERIFY_ARE_EQUAL(0u, focusedTabIndexOpt.value());
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Switch to the tab 6, which is greater than number of tabs. This should switch to the third tab");
|
||||
page->_SelectTab(6);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
|
||||
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
|
||||
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
|
||||
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22000.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug" MinVersion="14.0.27023.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug.UWPDesktop" MinVersion="14.0.27027.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
</Dependencies>
|
||||
|
|
|
@ -92,11 +92,11 @@
|
|||
<!-- 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.2.7.0-prerelease.210913003\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\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.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<uap:SupportedUsers>multiple</uap:SupportedUsers>
|
||||
</Properties>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22000.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
<Resources>
|
||||
<Resource Language="x-generate" />
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
|
||||
<Import Project="$(OpenConsoleDir)\src\common.build.post.props" />
|
||||
|
||||
|
|
|
@ -172,6 +172,7 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
|
|||
_desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8 },
|
||||
_actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
|
||||
_uiaProvider{ nullptr },
|
||||
_uiaProviderInitialized{ false },
|
||||
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
|
||||
_pfnWriteCallback{ nullptr },
|
||||
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
|
||||
|
@ -239,8 +240,8 @@ HRESULT HwndTerminal::Initialize()
|
|||
_terminal->SetBackgroundCallback([](auto) {});
|
||||
|
||||
_terminal->Create(COORD{ 80, 25 }, 1000, *_renderer);
|
||||
_terminal->SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, RGB(12, 12, 12));
|
||||
_terminal->SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, RGB(204, 204, 204));
|
||||
_terminal->SetDefaultBackground(RGB(12, 12, 12));
|
||||
_terminal->SetDefaultForeground(RGB(204, 204, 204));
|
||||
_terminal->SetWriteInputCallback([=](std::wstring& input) noexcept { _WriteTextToConnection(input); });
|
||||
localPointerToThread->EnablePainting();
|
||||
|
||||
|
@ -323,22 +324,26 @@ void HwndTerminal::_UpdateFont(int newDpi)
|
|||
|
||||
IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
|
||||
{
|
||||
// If TermControlUiaProvider throws during construction,
|
||||
// we don't want to try constructing an instance again and again.
|
||||
// _uiaProviderInitialized helps us prevent this.
|
||||
if (!_uiaProviderInitialized)
|
||||
if (nullptr == _uiaProvider && !_uiaProviderInitialized)
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock;
|
||||
try
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
#pragma warning(suppress : 26441) // The lock is named, this appears to be a false positive
|
||||
lock = _terminal->LockForWriting();
|
||||
if (_uiaProviderInitialized)
|
||||
{
|
||||
return _uiaProvider.Get();
|
||||
}
|
||||
|
||||
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, this->GetUiaData(), this));
|
||||
_uiaProviderInitialized = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
_uiaProvider = nullptr;
|
||||
}
|
||||
_uiaProviderInitialized = true;
|
||||
}
|
||||
|
||||
return _uiaProvider.Get();
|
||||
|
@ -549,11 +554,11 @@ try
|
|||
|
||||
if (multiClickMapper == 3)
|
||||
{
|
||||
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansion::Line);
|
||||
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansionMode::Line);
|
||||
}
|
||||
else if (multiClickMapper == 2)
|
||||
{
|
||||
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansion::Word);
|
||||
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansionMode::Word);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -781,8 +786,8 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
|
|||
{
|
||||
auto lock = publicTerminal->_terminal->LockForWriting();
|
||||
|
||||
publicTerminal->_terminal->SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, theme.DefaultForeground);
|
||||
publicTerminal->_terminal->SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, theme.DefaultBackground);
|
||||
publicTerminal->_terminal->SetDefaultForeground(theme.DefaultForeground);
|
||||
publicTerminal->_terminal->SetDefaultBackground(theme.DefaultBackground);
|
||||
publicTerminal->_renderEngine->SetSelectionBackground(theme.DefaultSelectionBackground, theme.SelectionBackgroundAlpha);
|
||||
|
||||
// Set the font colors
|
||||
|
@ -977,7 +982,7 @@ void HwndTerminal::_StringPaste(const wchar_t* const pData) noexcept
|
|||
CATCH_LOG();
|
||||
}
|
||||
|
||||
COORD HwndTerminal::GetFontSize() const noexcept
|
||||
COORD HwndTerminal::GetFontSize() const
|
||||
{
|
||||
return _actualFont.GetSize();
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ private:
|
|||
FontInfoDesired _desiredFont;
|
||||
FontInfo _actualFont;
|
||||
int _currentDpi;
|
||||
bool _uiaProviderInitialized;
|
||||
std::function<void(wchar_t*)> _pfnWriteCallback;
|
||||
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
|
||||
|
||||
|
@ -82,7 +83,6 @@ private:
|
|||
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
|
||||
|
||||
bool _focused{ false };
|
||||
bool _uiaProviderInitialized{ false };
|
||||
|
||||
std::chrono::milliseconds _multiClickTime;
|
||||
unsigned int _multiClickCounter{};
|
||||
|
@ -128,7 +128,7 @@ private:
|
|||
void _SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept;
|
||||
|
||||
// Inherited via IControlAccessibilityInfo
|
||||
COORD GetFontSize() const noexcept override;
|
||||
COORD GetFontSize() const override;
|
||||
RECT GetBounds() const noexcept override;
|
||||
double GetScaleFactor() const noexcept override;
|
||||
void ChangeViewport(const SMALL_RECT NewWindow) override;
|
||||
|
|
|
@ -29,9 +29,6 @@
|
|||
<ProjectReference Include="$(SolutionDir)src\types\lib\types.vcxproj">
|
||||
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)src\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)src\renderer\base\lib\base.vcxproj">
|
||||
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
|
||||
</ProjectReference>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "GetWindowLayoutArgs.h"
|
||||
#include "GetWindowLayoutArgs.g.cpp"
|
|
@ -1,32 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- GetWindowLayoutArgs.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for getting the window layout from a peasant.
|
||||
Depending on if we are running on the monarch or on a peasant we might need
|
||||
to switch what thread we are executing on. This gives us the option of
|
||||
either returning the json result synchronously, or as a promise.
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GetWindowLayoutArgs.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct GetWindowLayoutArgs : public GetWindowLayoutArgsT<GetWindowLayoutArgs>
|
||||
{
|
||||
WINRT_PROPERTY(winrt::hstring, WindowLayoutJson, L"");
|
||||
WINRT_PROPERTY(winrt::Windows::Foundation::IAsyncOperation<winrt::hstring>, WindowLayoutJsonAsync, nullptr)
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(GetWindowLayoutArgs);
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
</PropertyGroup>
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Monarch.h">
|
||||
|
@ -35,12 +36,6 @@
|
|||
<ClInclude Include="WindowActivatedArgs.h">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GetWindowLayoutArgs.h">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="QuitAllRequestedArgs.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="MonarchFactory.h" />
|
||||
<ClInclude Include="Peasant.h">
|
||||
|
@ -76,12 +71,6 @@
|
|||
<ClCompile Include="WindowActivatedArgs.cpp">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GetWindowLayoutArgs.cpp">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QuitAllRequestedArgs.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
@ -139,5 +128,6 @@
|
|||
</ItemDefinitionGroup>
|
||||
<!-- ========================= Globals ======================== -->
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "Monarch.h"
|
||||
#include "CommandlineArgs.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
#include "QuitAllRequestedArgs.h"
|
||||
#include "ProposeCommandlineResult.h"
|
||||
|
||||
#include "Monarch.g.cpp"
|
||||
|
@ -29,10 +28,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// This constructor is intended to be used in unit tests,
|
||||
// but we need to make it public in order to use make_self
|
||||
// in the tests. It's not exposed through the idl though
|
||||
// so it's not _truly_ fully public which should be acceptable.
|
||||
// This is a private constructor to be used in unit tests, where we don't
|
||||
// want each Monarch to necessarily use the current PID.
|
||||
Monarch::Monarch(const uint64_t testPID) :
|
||||
_ourPID{ testPID }
|
||||
{
|
||||
|
@ -51,7 +48,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// - Add the given peasant to the list of peasants we're tracking. This
|
||||
// Peasant may have already been assigned an ID. If it hasn't, then give
|
||||
// it an ID.
|
||||
// - NB: this takes a unique_lock on _peasantsMutex.
|
||||
// Arguments:
|
||||
// - peasant: the new Peasant to track.
|
||||
// Return Value:
|
||||
|
@ -73,37 +69,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
{
|
||||
// Peasant already had an ID (from an older monarch). Leave that one
|
||||
// be. Make sure that the next peasant's ID is higher than it.
|
||||
// If multiple peasants are added concurrently we keep trying to update
|
||||
// until we get to set the new id.
|
||||
uint64_t current;
|
||||
do
|
||||
{
|
||||
current = _nextPeasantID.load(std::memory_order_relaxed);
|
||||
} while (current <= providedID && !_nextPeasantID.compare_exchange_weak(current, providedID + 1, std::memory_order_relaxed));
|
||||
_nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID;
|
||||
}
|
||||
|
||||
auto newPeasantsId = peasant.GetID();
|
||||
|
||||
// Keep track of which peasant we are
|
||||
// SAFETY: this is only true for one peasant, and each peasant
|
||||
// is only added to a monarch once, so we do not need synchronization here.
|
||||
if (peasant.GetPID() == _ourPID)
|
||||
{
|
||||
_ourPeasantId = newPeasantsId;
|
||||
}
|
||||
// Add an event listener to the peasant's WindowActivated event.
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows });
|
||||
peasant.RenameRequested({ this, &Monarch::_renameRequested });
|
||||
|
||||
peasant.ShowNotificationIconRequested([this](auto&&, auto&&) { _ShowNotificationIconRequestedHandlers(*this, nullptr); });
|
||||
peasant.HideNotificationIconRequested([this](auto&&, auto&&) { _HideNotificationIconRequestedHandlers(*this, nullptr); });
|
||||
peasant.QuitAllRequested({ this, &Monarch::_handleQuitAll });
|
||||
|
||||
{
|
||||
std::unique_lock lock{ _peasantsMutex };
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
}
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_AddPeasant",
|
||||
|
@ -111,8 +86,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
TraceLoggingUInt64(newPeasantsId, "peasantID", "the ID of the new peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_WindowCreatedHandlers(nullptr, nullptr);
|
||||
return newPeasantsId;
|
||||
}
|
||||
catch (...)
|
||||
|
@ -129,100 +102,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gives the host process an opportunity to run any pre-close logic then
|
||||
// requests all peasants to close.
|
||||
// Arguments:
|
||||
// - <none> used
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget Monarch::_handleQuitAll(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
// Let the process hosting the monarch run any needed logic before
|
||||
// closing all windows.
|
||||
auto args = winrt::make_self<implementation::QuitAllRequestedArgs>();
|
||||
_QuitAllRequestedHandlers(*this, *args);
|
||||
|
||||
if (const auto action = args->BeforeQuitAllAction())
|
||||
{
|
||||
co_await action;
|
||||
}
|
||||
|
||||
_quitting.store(true);
|
||||
// Tell all peasants to exit.
|
||||
const auto callback = [&](const auto& id, const auto& p) {
|
||||
// We want to tell our peasant to quit last, so that we don't try
|
||||
// to perform a bunch of elections on quit.
|
||||
if (id != _ourPeasantId)
|
||||
{
|
||||
p.Quit();
|
||||
}
|
||||
};
|
||||
const auto onError = [&](const auto& id) {
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_handleQuitAll_Failed",
|
||||
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not close"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
};
|
||||
|
||||
_forEachPeasant(callback, onError);
|
||||
|
||||
{
|
||||
std::shared_lock lock{ _peasantsMutex };
|
||||
const auto peasantSearch = _peasants.find(_ourPeasantId);
|
||||
if (peasantSearch != _peasants.end())
|
||||
{
|
||||
peasantSearch->second.Quit();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Somehow we don't have our own peasant, this should never happen.
|
||||
// We are trying to quit anyways so just fail here.
|
||||
assert(peasantSearch != _peasants.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Tells the monarch that a peasant is being closed.
|
||||
// - NB: this (separately) takes unique locks on _peasantsMutex and
|
||||
// _mruPeasantsMutex.
|
||||
// Arguments:
|
||||
// - peasantId: the id of the peasant
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::SignalClose(const uint64_t peasantId)
|
||||
{
|
||||
// If we are quitting we don't care about maintaining our list of
|
||||
// peasants anymore, and don't need to notify the host that something
|
||||
// changed.
|
||||
if (_quitting.load(std::memory_order_acquire))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_clearOldMruEntries({ peasantId });
|
||||
{
|
||||
std::unique_lock lock{ _peasantsMutex };
|
||||
_peasants.erase(peasantId);
|
||||
}
|
||||
_WindowClosedHandlers(nullptr, nullptr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Counts the number of living peasants.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the number of active peasants.
|
||||
uint64_t Monarch::GetNumberOfPeasants()
|
||||
{
|
||||
std::shared_lock lock{ _peasantsMutex };
|
||||
return _peasants.size();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for the Peasant::WindowActivated event. Used as an
|
||||
// opportunity for us to update our internal stack of the "most recent
|
||||
|
@ -241,25 +120,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// Method Description:
|
||||
// - Lookup a peasant by its ID. If the peasant has died, this will also
|
||||
// remove the peasant from our list of peasants.
|
||||
// - NB: this (separately) takes unique locks on _peasantsMutex and
|
||||
// _mruPeasantsMutex.
|
||||
// Arguments:
|
||||
// - peasantID: The ID Of the peasant to find
|
||||
// - clearMruPeasantOnFailure: When true this function will handle clearing
|
||||
// from _mruPeasants if a peasant was not found, otherwise the caller is
|
||||
// expected to handle that cleanup themselves.
|
||||
// Return Value:
|
||||
// - the peasant if it exists in our map, otherwise null
|
||||
Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID, bool clearMruPeasantOnFailure)
|
||||
Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPeasant maybeThePeasant = nullptr;
|
||||
{
|
||||
std::shared_lock lock{ _peasantsMutex };
|
||||
const auto peasantSearch = _peasants.find(peasantID);
|
||||
maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
}
|
||||
const auto peasantSearch = _peasants.find(peasantID);
|
||||
auto maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
// Ask the peasant for their PID. This will validate that they're
|
||||
// actually still alive.
|
||||
if (maybeThePeasant)
|
||||
|
@ -271,19 +141,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
|
||||
// Remove the peasant from the list of peasants
|
||||
{
|
||||
std::unique_lock lock{ _peasantsMutex };
|
||||
_peasants.erase(peasantID);
|
||||
}
|
||||
_peasants.erase(peasantID);
|
||||
|
||||
if (clearMruPeasantOnFailure)
|
||||
{
|
||||
// Remove the peasant from the list of MRU windows. They're dead.
|
||||
// They can't be the MRU anymore.
|
||||
_clearOldMruEntries({ peasantID });
|
||||
}
|
||||
// Remove the peasant from the list of MRU windows. They're dead.
|
||||
// They can't be the MRU anymore.
|
||||
_clearOldMruEntries(peasantID);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -304,34 +167,40 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
return 0;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> peasantsToErase{};
|
||||
uint64_t result = 0;
|
||||
|
||||
const auto callback = [&](const auto& id, const auto& p) {
|
||||
auto otherName = p.WindowName();
|
||||
if (otherName == name)
|
||||
for (const auto& [id, p] : _peasants)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = id;
|
||||
return false;
|
||||
auto otherName = p.WindowName();
|
||||
if (otherName == name)
|
||||
{
|
||||
result = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
// Normally, we'd just erase the peasant here. However, we can't
|
||||
// erase from the map while we're iterating over it like this.
|
||||
// Instead, pull a good ole Java and collect this id for removal
|
||||
// later.
|
||||
peasantsToErase.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
const auto onError = [&](const auto& id) {
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_lookupPeasantIdForName_Failed",
|
||||
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not get the name of"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
};
|
||||
// Remove the dead peasants we came across while iterating.
|
||||
for (const auto& id : peasantsToErase)
|
||||
{
|
||||
// Remove the peasant from the list of peasants
|
||||
_peasants.erase(id);
|
||||
// Remove the peasant from the list of MRU windows. They're dead.
|
||||
// They can't be the MRU anymore.
|
||||
_clearOldMruEntries(id);
|
||||
}
|
||||
|
||||
_forEachPeasant(callback, onError);
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_lookupPeasantIdForName",
|
||||
TraceLoggingWideString(std::wstring{ name }.c_str(), "name", "the name we're looking for"),
|
||||
TraceLoggingUInt64(result, "peasantID", "the ID of the peasant with that name"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -376,42 +245,33 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// - Helper for removing a peasant from the list of MRU peasants. We want to
|
||||
// do this both when the peasant dies, and also when the peasant is newly
|
||||
// activated (so that we don't leave an old entry for it in the list).
|
||||
// - NB: This takes a unique lock on _mruPeasantsMutex.
|
||||
// Arguments:
|
||||
// - peasantIds: The list of peasant IDs to remove from the MRU list
|
||||
// - peasantID: The ID of the peasant to remove from the MRU list
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::_clearOldMruEntries(const std::unordered_set<uint64_t>& peasantIds)
|
||||
void Monarch::_clearOldMruEntries(const uint64_t peasantID)
|
||||
{
|
||||
if (peasantIds.size() == 0)
|
||||
auto result = std::find_if(_mruPeasants.begin(),
|
||||
_mruPeasants.end(),
|
||||
[peasantID](auto&& other) {
|
||||
return peasantID == other.PeasantID();
|
||||
});
|
||||
|
||||
if (result != std::end(_mruPeasants))
|
||||
{
|
||||
return;
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_RemovedPeasantFromDesktop",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "The ID of the peasant"),
|
||||
TraceLoggingGuid(result->DesktopID(), "desktopGuid", "The GUID of the previous desktop the window was on"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_mruPeasants.erase(result);
|
||||
}
|
||||
|
||||
std::unique_lock lock{ _mruPeasantsMutex };
|
||||
auto partition = std::remove_if(_mruPeasants.begin(), _mruPeasants.end(), [&](const auto& p) {
|
||||
const auto id = p.PeasantID();
|
||||
// remove the element if it was found in the list to erase.
|
||||
if (peasantIds.count(id) == 1)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_RemovedPeasantFromDesktop",
|
||||
TraceLoggingUInt64(id, "peasantID", "The ID of the peasant"),
|
||||
TraceLoggingGuid(p.DesktopID(), "desktopGuid", "The GUID of the previous desktop the window was on"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Remove everything that was in the list
|
||||
_mruPeasants.erase(partition, _mruPeasants.end());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Actually handle inserting the WindowActivatedArgs into our list of MRU windows.
|
||||
// - NB: this takes a unique_lock on _mruPeasantsMutex.
|
||||
// Arguments:
|
||||
// - localArgs: an in-proc WindowActivatedArgs that we should add to our list of MRU windows.
|
||||
// Return Value:
|
||||
|
@ -422,22 +282,19 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
|
||||
// * Check all the current lists to look for this peasant.
|
||||
// remove it from any where it exists.
|
||||
_clearOldMruEntries({ localArgs->PeasantID() });
|
||||
_clearOldMruEntries(localArgs->PeasantID());
|
||||
|
||||
// * If the current desktop doesn't have a vector, add one.
|
||||
const auto desktopGuid{ localArgs->DesktopID() };
|
||||
|
||||
{
|
||||
std::unique_lock lock{ _mruPeasantsMutex };
|
||||
// * Add this args list. By using lower_bound with insert, we can get it
|
||||
// into exactly the right spot, without having to re-sort the whole
|
||||
// array.
|
||||
_mruPeasants.insert(std::lower_bound(_mruPeasants.begin(),
|
||||
_mruPeasants.end(),
|
||||
*localArgs,
|
||||
[](const auto& first, const auto& second) { return first.ActivatedTime() > second.ActivatedTime(); }),
|
||||
*localArgs);
|
||||
}
|
||||
// * Add this args list. By using lower_bound with insert, we can get it
|
||||
// into exactly the right spot, without having to re-sort the whole
|
||||
// array.
|
||||
_mruPeasants.insert(std::lower_bound(_mruPeasants.begin(),
|
||||
_mruPeasants.end(),
|
||||
*localArgs,
|
||||
[](const auto& first, const auto& second) { return first.ActivatedTime() > second.ActivatedTime(); }),
|
||||
*localArgs);
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SetMostRecentPeasant",
|
||||
|
@ -451,9 +308,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// Method Description:
|
||||
// - Retrieves the ID of the MRU peasant window. If requested, will limit
|
||||
// the search to windows that are on the current desktop.
|
||||
// - NB: This method will hold a shared lock on _mruPeasantsMutex and
|
||||
// potentially a unique_lock on _peasantsMutex at the same time.
|
||||
// Separately it might hold a unique_lock on _mruPeasantsMutex.
|
||||
// Arguments:
|
||||
// - limitToCurrentDesktop: if true, only return the MRU peasant that's
|
||||
// actually on the current desktop.
|
||||
|
@ -466,13 +320,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// - the ID of the most recent peasant, otherwise 0 if we could not find one.
|
||||
uint64_t Monarch::_getMostRecentPeasantID(const bool limitToCurrentDesktop, const bool ignoreQuakeWindow)
|
||||
{
|
||||
std::shared_lock lock{ _mruPeasantsMutex };
|
||||
if (_mruPeasants.empty())
|
||||
{
|
||||
// unlock the mruPeasants mutex to make sure we can't deadlock here.
|
||||
lock.unlock();
|
||||
// Only need a shared lock for read
|
||||
std::shared_lock peasantsLock{ _peasantsMutex };
|
||||
// We haven't yet been told the MRU peasant. Just use the first one.
|
||||
// This is just gonna be a random one, but really shouldn't happen
|
||||
// in practice. The WindowManager should set the MRU peasant
|
||||
|
@ -513,17 +362,15 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// - If it isn't on the current desktop, we'll loop again, on the
|
||||
// following peasant.
|
||||
// * If we don't care, then we'll just return that one.
|
||||
uint64_t result = 0;
|
||||
std::unordered_set<uint64_t> peasantsToErase{};
|
||||
for (const auto& mruWindowArgs : _mruPeasants)
|
||||
//
|
||||
// We're not just using an iterator because the contents of the list
|
||||
// might change while we're iterating here (if the peasant is dead we'll
|
||||
// remove it from the list).
|
||||
int positionInList = 0;
|
||||
while (_mruPeasants.cbegin() + positionInList < _mruPeasants.cend())
|
||||
{
|
||||
// Try to get the peasant, but do not have _getPeasant clean up old
|
||||
// _mruPeasants because we are iterating here.
|
||||
// SAFETY: _getPeasant can take a unique_lock on _peasantsMutex if
|
||||
// it detects a peasant is dead. Currently _getMostRecentPeasantId
|
||||
// is the only method that holds a lock on both _mruPeasantsMutex and
|
||||
// _peasantsMutex at the same time so there cannot be a deadlock here.
|
||||
const auto peasant{ _getPeasant(mruWindowArgs.PeasantID(), false) };
|
||||
const auto mruWindowArgs{ *(_mruPeasants.begin() + positionInList) };
|
||||
const auto peasant{ _getPeasant(mruWindowArgs.PeasantID()) };
|
||||
if (!peasant)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
|
@ -537,7 +384,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// We'll go through the loop again. We removed the current one
|
||||
// at positionInList, so the next one in positionInList will be
|
||||
// a new, different peasant.
|
||||
peasantsToErase.emplace(mruWindowArgs.PeasantID());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -575,8 +421,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
"true if this window was in fact on the current desktop"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
result = mruWindowArgs.PeasantID();
|
||||
break;
|
||||
return mruWindowArgs.PeasantID();
|
||||
}
|
||||
// If this window wasn't on the current desktop, another one
|
||||
// might be. We'll increment positionInList below, and try
|
||||
|
@ -590,30 +435,20 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
result = mruWindowArgs.PeasantID();
|
||||
break;
|
||||
return mruWindowArgs.PeasantID();
|
||||
}
|
||||
positionInList++;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
// Here, we've checked all the windows, and none of them was both alive
|
||||
// and the most recent (on this desktop). Just return 0 - the caller
|
||||
// will use this to create a new window.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_getMostRecentPeasantID_NotFound",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
if (peasantsToErase.size() > 0)
|
||||
{
|
||||
_clearOldMruEntries(peasantsToErase);
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
// Here, we've checked all the windows, and none of them was both alive
|
||||
// and the most recent (on this desktop). Just return 0 - the caller
|
||||
// will use this to create a new window.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_getMostRecentPeasantID_NotFound",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
return result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -767,6 +602,39 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
return *result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper for doing something on each and every peasant, with no regard
|
||||
// for if the peasant is living or dead.
|
||||
// - We'll try calling callback on every peasant.
|
||||
// - If any single peasant is dead, then we'll call errorCallback, and move on.
|
||||
// - We're taking an errorCallback here, because the thing we usually want
|
||||
// to do is TraceLog a message, but TraceLoggingWrite is actually a macro
|
||||
// that _requires_ the second arg to be a string literal. It can't just be
|
||||
// a variable.
|
||||
// Arguments:
|
||||
// - callback: The function to call on each peasant
|
||||
// - errorCallback: The function to call if a peasant is dead.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::_forAllPeasantsIgnoringTheDead(std::function<void(const Remoting::IPeasant&, const uint64_t)> callback,
|
||||
std::function<void(const uint64_t)> errorCallback)
|
||||
{
|
||||
for (const auto& [id, p] : _peasants)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(p, id);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
// If this fails, we don't _really_ care. Just move on to the
|
||||
// next one. Someone else will clean up the dead peasant.
|
||||
errorCallback(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is an event handler for the IdentifyWindowsRequested event. A
|
||||
// Peasant may raise that event if they want _all_ windows to identify
|
||||
|
@ -781,18 +649,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
// Notify all the peasants to display their ID.
|
||||
const auto callback = [&](const auto& /*id*/, const auto& p) {
|
||||
auto callback = [](auto&& p, auto&& /*id*/) {
|
||||
p.DisplayWindowId();
|
||||
};
|
||||
const auto onError = [&](const auto& id) {
|
||||
auto onError = [](auto&& id) {
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_identifyWindows_Failed",
|
||||
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not identify"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
};
|
||||
|
||||
_forEachPeasant(callback, onError);
|
||||
_forAllPeasantsIgnoringTheDead(callback, onError);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -865,55 +732,24 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
try
|
||||
{
|
||||
args.FoundMatch(false);
|
||||
|
||||
// If a WindowID is provided from the args, use that first.
|
||||
uint64_t windowId = 0;
|
||||
if (args.WindowID())
|
||||
// If no name was provided, then just summon the MRU window.
|
||||
if (searchedForName.empty())
|
||||
{
|
||||
windowId = args.WindowID().Value();
|
||||
// Use the value of the `desktop` arg to determine if we should
|
||||
// limit to the current desktop (desktop:onCurrent) or not
|
||||
// (desktop:any or desktop:toCurrent)
|
||||
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no name was provided, then just summon the MRU window.
|
||||
if (searchedForName.empty())
|
||||
{
|
||||
// Use the value of the `desktop` arg to determine if we should
|
||||
// limit to the current desktop (desktop:onCurrent) or not
|
||||
// (desktop:any or desktop:toCurrent)
|
||||
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to find a peasant that currently has this name
|
||||
windowId = _lookupPeasantIdForName(searchedForName);
|
||||
}
|
||||
// Try to find a peasant that currently has this name
|
||||
windowId = _lookupPeasantIdForName(searchedForName);
|
||||
}
|
||||
|
||||
if (auto targetPeasant{ _getPeasant(windowId) })
|
||||
{
|
||||
targetPeasant.Summon(args.SummonBehavior());
|
||||
args.FoundMatch(true);
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SummonWindow_Success",
|
||||
TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
|
||||
TraceLoggingUInt64(windowId, "peasantID", "The id of the window we tried to summon"),
|
||||
TraceLoggingBoolean(args.OnCurrentDesktop(), "OnCurrentDesktop", "true iff the window needs to be on the current virtual desktop"),
|
||||
TraceLoggingBoolean(args.SummonBehavior().MoveToCurrentDesktop(), "MoveToCurrentDesktop", "if true, move the window to the current virtual desktop"),
|
||||
TraceLoggingBoolean(args.SummonBehavior().ToggleVisibility(), "ToggleVisibility", "true if we should toggle the visibility of the window"),
|
||||
TraceLoggingUInt32(args.SummonBehavior().DropdownDuration(), "DropdownDuration", "the duration to dropdown the window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SummonWindow_NoPeasant",
|
||||
TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
|
||||
TraceLoggingUInt64(windowId, "peasantID", "The id of the window we tried to summon"),
|
||||
TraceLoggingBoolean(args.OnCurrentDesktop(), "OnCurrentDesktop", "true iff the window needs to be on the current virtual desktop"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
|
@ -926,103 +762,4 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method creates a map of peasant IDs to peasant names
|
||||
// while removing dead peasants.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - A map of peasant IDs to their names.
|
||||
Windows::Foundation::Collections::IVectorView<PeasantInfo> Monarch::GetPeasantInfos()
|
||||
{
|
||||
std::vector<PeasantInfo> names;
|
||||
{
|
||||
std::shared_lock lock{ _peasantsMutex };
|
||||
names.reserve(_peasants.size());
|
||||
}
|
||||
|
||||
const auto func = [&](const auto& id, const auto& p) -> void {
|
||||
names.push_back({ id, p.WindowName(), p.ActiveTabTitle() });
|
||||
};
|
||||
|
||||
const auto onError = [&](const auto& id) {
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_identifyWindows_Failed",
|
||||
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not identify"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
};
|
||||
|
||||
_forEachPeasant(func, onError);
|
||||
|
||||
return winrt::single_threaded_vector<PeasantInfo>(std::move(names)).GetView();
|
||||
}
|
||||
|
||||
bool Monarch::DoesQuakeWindowExist()
|
||||
{
|
||||
bool result = false;
|
||||
const auto func = [&](const auto& /*id*/, const auto& p) {
|
||||
if (p.WindowName() == QuakeWindowName)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
// continue if we didn't get a positive result
|
||||
return !result;
|
||||
};
|
||||
|
||||
const auto onError = [&](const auto& id) {
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_DoesQuakeWindowExist_Failed",
|
||||
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not ask for its name"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
};
|
||||
|
||||
_forEachPeasant(func, onError);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Monarch::SummonAllWindows()
|
||||
{
|
||||
const auto func = [&](const auto& /*id*/, const auto& p) {
|
||||
SummonWindowBehavior args{};
|
||||
args.ToggleVisibility(false);
|
||||
p.Summon(args);
|
||||
};
|
||||
|
||||
const auto onError = [&](const auto& id) {
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SummonAll_Failed",
|
||||
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
};
|
||||
|
||||
_forEachPeasant(func, onError);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Ask all peasants to return their window layout as json
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The collection of window layouts from each peasant.
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> Monarch::GetAllWindowLayouts()
|
||||
{
|
||||
std::vector<winrt::hstring> vec;
|
||||
auto callback = [&](const auto& /*id*/, const auto& p) {
|
||||
vec.emplace_back(p.GetWindowLayout());
|
||||
};
|
||||
auto onError = [](auto&& id) {
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_GetAllWindowLayouts_Failed",
|
||||
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not get a window layout from"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
};
|
||||
_forEachPeasant(callback, onError);
|
||||
|
||||
return winrt::single_threaded_vector(std::move(vec));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "Peasant.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "WindowActivatedArgs.h"
|
||||
#include <atomic>
|
||||
|
||||
// We sure different GUIDs here depending on whether we're running a Release,
|
||||
// Preview, or Dev build. This ensures that different installs don't
|
||||
|
@ -42,58 +41,39 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
struct Monarch : public MonarchT<Monarch>
|
||||
{
|
||||
Monarch();
|
||||
Monarch(const uint64_t testPID);
|
||||
~Monarch();
|
||||
|
||||
uint64_t GetPID();
|
||||
|
||||
uint64_t AddPeasant(winrt::Microsoft::Terminal::Remoting::IPeasant peasant);
|
||||
void SignalClose(const uint64_t peasantId);
|
||||
|
||||
uint64_t GetNumberOfPeasants();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
|
||||
|
||||
void SummonAllWindows();
|
||||
bool DoesQuakeWindowExist();
|
||||
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs);
|
||||
|
||||
private:
|
||||
Monarch(const uint64_t testPID);
|
||||
uint64_t _ourPID;
|
||||
|
||||
std::atomic<uint64_t> _nextPeasantID{ 1 };
|
||||
uint64_t _ourPeasantId{ 0 };
|
||||
|
||||
// When we're quitting we do not care as much about handling some events that we know will be triggered
|
||||
std::atomic<bool> _quitting{ false };
|
||||
uint64_t _nextPeasantID{ 1 };
|
||||
uint64_t _thisPeasantID{ 0 };
|
||||
|
||||
winrt::com_ptr<IVirtualDesktopManager> _desktopManager{ nullptr };
|
||||
|
||||
std::unordered_map<uint64_t, winrt::Microsoft::Terminal::Remoting::IPeasant> _peasants;
|
||||
std::vector<Remoting::WindowActivatedArgs> _mruPeasants;
|
||||
// These should not be locked at the same time to prevent deadlocks
|
||||
// unless they are both shared_locks.
|
||||
std::shared_mutex _peasantsMutex{};
|
||||
std::shared_mutex _mruPeasantsMutex{};
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _getPeasant(uint64_t peasantID, bool clearMruPeasantOnFailure = true);
|
||||
std::vector<Remoting::WindowActivatedArgs> _mruPeasants;
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _getPeasant(uint64_t peasantID);
|
||||
uint64_t _getMostRecentPeasantID(bool limitToCurrentDesktop, const bool ignoreQuakeWindow);
|
||||
uint64_t _lookupPeasantIdForName(std::wstring_view name);
|
||||
|
||||
void _peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
void _doHandleActivatePeasant(const winrt::com_ptr<winrt::Microsoft::Terminal::Remoting::implementation::WindowActivatedArgs>& args);
|
||||
void _clearOldMruEntries(const std::unordered_set<uint64_t>& peasantIds);
|
||||
void _clearOldMruEntries(const uint64_t peasantID);
|
||||
|
||||
void _forAllPeasantsIgnoringTheDead(std::function<void(const winrt::Microsoft::Terminal::Remoting::IPeasant&, const uint64_t)> callback,
|
||||
std::function<void(const uint64_t)> errorCallback);
|
||||
|
@ -104,87 +84,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
void _renameRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
|
||||
|
||||
winrt::fire_and_forget _handleQuitAll(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
|
||||
// Method Description:
|
||||
// - Helper for doing something on each and every peasant.
|
||||
// - We'll try calling func on every peasant.
|
||||
// - If the return type of func is void, it will perform it for all peasants.
|
||||
// - If the return type is a boolean, we'll break out of the loop once func
|
||||
// returns false.
|
||||
// - If any single peasant is dead, then we'll call onError and then add it to a
|
||||
// list of peasants to clean up once the loop ends.
|
||||
// - NB: this (separately) takes unique locks on _peasantsMutex and
|
||||
// _mruPeasantsMutex.
|
||||
// Arguments:
|
||||
// - func: The function to call on each peasant
|
||||
// - onError: The function to call if a peasant is dead.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
template<typename F, typename T>
|
||||
void _forEachPeasant(F&& func, T&& onError)
|
||||
{
|
||||
using Map = decltype(_peasants);
|
||||
using R = std::invoke_result_t<F, Map::key_type, Map::mapped_type>;
|
||||
static constexpr auto IsVoid = std::is_void_v<R>;
|
||||
|
||||
std::unordered_set<uint64_t> peasantsToErase;
|
||||
{
|
||||
std::shared_lock lock{ _peasantsMutex };
|
||||
|
||||
for (const auto& [id, p] : _peasants)
|
||||
{
|
||||
try
|
||||
{
|
||||
if constexpr (IsVoid)
|
||||
{
|
||||
func(id, p);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!func(id, p))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const winrt::hresult_error& exception)
|
||||
{
|
||||
onError(id);
|
||||
|
||||
if (exception.code() == 0x800706ba) // The RPC server is unavailable.
|
||||
{
|
||||
peasantsToErase.emplace(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (peasantsToErase.size() > 0)
|
||||
{
|
||||
// Don't hold a lock on _peasants and _mruPeasants at the same
|
||||
// time to avoid deadlocks.
|
||||
{
|
||||
std::unique_lock lock{ _peasantsMutex };
|
||||
for (const auto& id : peasantsToErase)
|
||||
{
|
||||
_peasants.erase(id);
|
||||
}
|
||||
}
|
||||
_clearOldMruEntries(peasantsToErase);
|
||||
|
||||
// A peasant died, let the app host know that the number of
|
||||
// windows has changed.
|
||||
_WindowClosedHandlers(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
friend class RemotingUnitTests::RemotingTests;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -28,43 +28,18 @@ namespace Microsoft.Terminal.Remoting
|
|||
|
||||
Boolean FoundMatch;
|
||||
SummonWindowBehavior SummonBehavior;
|
||||
Windows.Foundation.IReference<UInt64> WindowID;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass QuitAllRequestedArgs
|
||||
{
|
||||
QuitAllRequestedArgs();
|
||||
Windows.Foundation.IAsyncAction BeforeQuitAllAction;
|
||||
}
|
||||
|
||||
struct PeasantInfo
|
||||
{
|
||||
UInt64 Id;
|
||||
String Name;
|
||||
String TabTitle;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Monarch {
|
||||
Monarch();
|
||||
|
||||
UInt64 GetPID();
|
||||
UInt64 AddPeasant(IPeasant peasant);
|
||||
UInt64 GetNumberOfPeasants();
|
||||
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args);
|
||||
void HandleActivatePeasant(WindowActivatedArgs args);
|
||||
void SummonWindow(SummonWindowSelectionArgs args);
|
||||
void SignalClose(UInt64 peasantId);
|
||||
|
||||
void SummonAllWindows();
|
||||
Boolean DoesQuakeWindowExist();
|
||||
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos { get; };
|
||||
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
|
||||
event Windows.Foundation.TypedEventHandler<Object, QuitAllRequestedArgs> QuitAllRequested;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "Peasant.h"
|
||||
#include "CommandlineArgs.h"
|
||||
#include "SummonWindowBehavior.h"
|
||||
#include "GetWindowLayoutArgs.h"
|
||||
#include "Peasant.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
|
@ -21,10 +20,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
{
|
||||
}
|
||||
|
||||
// This constructor is intended to be used in unit tests,
|
||||
// but we need to make it public in order to use make_self
|
||||
// in the tests. It's not exposed through the idl though
|
||||
// so it's not _truly_ fully public which should be acceptable.
|
||||
// This is a private constructor to be used in unit tests, where we don't
|
||||
// want each Peasant to necessarily use the current PID.
|
||||
Peasant::Peasant(const uint64_t testPID) :
|
||||
_ourPID{ testPID }
|
||||
{
|
||||
|
@ -34,7 +31,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
uint64_t Peasant::GetID()
|
||||
{
|
||||
return _id;
|
||||
|
@ -226,88 +222,4 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
void Peasant::RequestShowNotificationIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ShowNotificationIconRequestedHandlers(*this, nullptr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_RequestShowNotificationIcon",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
void Peasant::RequestHideNotificationIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
_HideNotificationIconRequestedHandlers(*this, nullptr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_RequestHideNotificationIcon",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
void Peasant::RequestQuitAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
_QuitAllRequestedHandlers(*this, nullptr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_RequestQuit",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
void Peasant::Quit()
|
||||
{
|
||||
try
|
||||
{
|
||||
_QuitRequestedHandlers(*this, nullptr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_Quit",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Request and return the window layout from the current TerminalPage
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the window layout as a json string
|
||||
hstring Peasant::GetWindowLayout()
|
||||
{
|
||||
auto args = winrt::make_self<implementation::GetWindowLayoutArgs>();
|
||||
_GetWindowLayoutRequestedHandlers(nullptr, *args);
|
||||
if (const auto op = args->WindowLayoutJsonAsync())
|
||||
{
|
||||
// This will fail if called on the UI thread, so the monarch should
|
||||
// never set WindowLayoutJsonAsync.
|
||||
auto str = op.get();
|
||||
return str;
|
||||
}
|
||||
return args->WindowLayoutJson();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,19 +28,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
void RequestIdentifyWindows();
|
||||
void DisplayWindowId();
|
||||
void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
|
||||
void RequestShowNotificationIcon();
|
||||
void RequestHideNotificationIcon();
|
||||
void RequestQuitAll();
|
||||
void Quit();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
|
||||
|
||||
winrt::hstring GetWindowLayout();
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, WindowName);
|
||||
WINRT_PROPERTY(winrt::hstring, ActiveTabTitle);
|
||||
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs);
|
||||
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs);
|
||||
|
@ -48,11 +40,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs);
|
||||
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior);
|
||||
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(QuitRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs);
|
||||
|
||||
private:
|
||||
Peasant(const uint64_t testPID);
|
||||
|
|
|
@ -30,11 +30,6 @@ namespace Microsoft.Terminal.Remoting
|
|||
Windows.Foundation.DateTime ActivatedTime { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass GetWindowLayoutArgs {
|
||||
GetWindowLayoutArgs();
|
||||
String WindowLayoutJson;
|
||||
Windows.Foundation.IAsyncOperation<String> WindowLayoutJsonAsync;
|
||||
}
|
||||
|
||||
enum MonitorBehavior
|
||||
{
|
||||
|
@ -66,15 +61,9 @@ namespace Microsoft.Terminal.Remoting
|
|||
void DisplayWindowId(); // Tells us to display its own ID (which causes a DisplayWindowIdRequested to be raised)
|
||||
|
||||
String WindowName { get; };
|
||||
String ActiveTabTitle { get; };
|
||||
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
|
||||
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
|
||||
void Summon(SummonWindowBehavior behavior);
|
||||
void RequestShowNotificationIcon();
|
||||
void RequestHideNotificationIcon();
|
||||
void RequestQuitAll();
|
||||
void Quit();
|
||||
String GetWindowLayout();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
|
||||
|
@ -82,11 +71,6 @@ namespace Microsoft.Terminal.Remoting
|
|||
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, GetWindowLayoutArgs> GetWindowLayoutRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> QuitAllRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Peasant : IPeasant
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue