Merged PR 5097423: Migrate OSS up to 0488c532
Dustin L. Howett * Clear the last error before calling Mb2Wc in ConvertToW (GH-7391) * Update clang-format to 10.0 (GH-7389) * Add til::static_map, a constexpr key-value store (GH-7323) James Holderness * Refactor VT control sequence identification (CC-7304) Mike Griese * Compensate for VS 16.7, part 2 (GH-7383) * Add support for iterable, nested commands (GH-6856) Michael Niksa * Helix Testing (GH-6992) * Compensate for new warnings and STL changes in VS 16.7 (GH-7319) nathpete-msft * Fix environment block creation (GH-7401) Chester Liu * Add initial support for VT DCS sequences (CC-6328) Related work items: #28791050
This commit is contained in:
commit
f357e379fc
|
@ -1,17 +1,19 @@
|
|||
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
#AllowAllArgumentsOnNextLine: false
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
#AllowAllConstructorInitializersOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
#AllowShortLambdasOnASingleLine: Inline
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
|
@ -20,6 +22,7 @@ AlwaysBreakTemplateDeclarations: Yes
|
|||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
|
@ -47,6 +50,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
|||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Regroup
|
||||
|
@ -73,7 +77,7 @@ ReflowComments: false
|
|||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
#SpaceAfterLogicalNot: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
|
@ -88,6 +92,6 @@ SpacesInCStyleCastParentheses: false
|
|||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
Standard: Latest
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
|
|
|
@ -9,6 +9,7 @@ EXPCMDFLAGS
|
|||
EXPCMDSTATE
|
||||
fullkbd
|
||||
futex
|
||||
Hashtable
|
||||
href
|
||||
IAsync
|
||||
IBind
|
||||
|
@ -41,6 +42,7 @@ rfind
|
|||
roundf
|
||||
RSHIFT
|
||||
rx
|
||||
serializer
|
||||
SIZENS
|
||||
spsc
|
||||
STDCPP
|
||||
|
@ -48,3 +50,5 @@ syscall
|
|||
tmp
|
||||
tx
|
||||
userenv
|
||||
XDocument
|
||||
XElement
|
||||
|
|
|
@ -99401,6 +99401,8 @@ DCP
|
|||
DCPR
|
||||
DCPSK
|
||||
DCS
|
||||
Dcs
|
||||
dcs
|
||||
DCT
|
||||
DCTN
|
||||
DCTS
|
||||
|
|
|
@ -1,18 +1,37 @@
|
|||
ACLs
|
||||
altform
|
||||
appendwttlogging
|
||||
backplating
|
||||
DACL
|
||||
DACLs
|
||||
dotnetfeed
|
||||
DWINRT
|
||||
enablewttlogging
|
||||
LKG
|
||||
mfcribbon
|
||||
microsoft
|
||||
microsoftonline
|
||||
netcore
|
||||
osgvsowi
|
||||
pgc
|
||||
pgo
|
||||
pgosweep
|
||||
powerrename
|
||||
powershell
|
||||
pscustomobject
|
||||
robocopy
|
||||
SACLs
|
||||
Shobjidl
|
||||
Skype
|
||||
sysnative
|
||||
systemroot
|
||||
taskkill
|
||||
tasklist
|
||||
tdbuildteamid
|
||||
vcruntime
|
||||
visualstudio
|
||||
wlk
|
||||
wslpath
|
||||
wtl
|
||||
wtt
|
||||
wttlog
|
||||
|
|
|
@ -25,6 +25,7 @@ Kourosh
|
|||
kowalczyk
|
||||
leonmsft
|
||||
Lepilleur
|
||||
lukesampson
|
||||
Manandhar
|
||||
mbadolato
|
||||
Mehrain
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
abcd
|
||||
dst
|
||||
EFG
|
||||
EFGh
|
||||
EMPTYBOX
|
||||
GFEh
|
||||
nrcs
|
||||
Remoting
|
||||
Scs
|
||||
Shobjidl
|
|
@ -1,3 +1,4 @@
|
|||
abcd
|
||||
abcde
|
||||
abcdef
|
||||
ABCDEFG
|
||||
|
@ -10,6 +11,8 @@ abcdefghijklmnopqrstuvwxyz
|
|||
ABE
|
||||
BBGGRR
|
||||
BBBBBBBBBBBBBBDDDD
|
||||
EFG
|
||||
EFGh
|
||||
QQQQQQQQQQABCDEFGHIJ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
|
||||
|
|
37
.github/actions/spell-check/expect/expect.txt
vendored
37
.github/actions/spell-check/expect/expect.txt
vendored
|
@ -7,6 +7,7 @@ ABCDEFGHIJKLMNO
|
|||
ABCG
|
||||
abf
|
||||
abi
|
||||
ACCESSTOKEN
|
||||
acec
|
||||
acf
|
||||
acidev
|
||||
|
@ -23,6 +24,7 @@ addressof
|
|||
ADDSTRING
|
||||
ADDTOOL
|
||||
AEnd
|
||||
aef
|
||||
AFew
|
||||
AFill
|
||||
AFX
|
||||
|
@ -71,6 +73,7 @@ apps
|
|||
APPWINDOW
|
||||
appx
|
||||
appxbundle
|
||||
appxerror
|
||||
appxmanifest
|
||||
APrep
|
||||
apsect
|
||||
|
@ -188,6 +191,7 @@ buffersize
|
|||
buflen
|
||||
bugfix
|
||||
buildtransitive
|
||||
BUILDURI
|
||||
burriter
|
||||
BValue
|
||||
byref
|
||||
|
@ -293,6 +297,7 @@ codepage
|
|||
codepoint
|
||||
codeproject
|
||||
COINIT
|
||||
COLLECTIONURI
|
||||
colorizing
|
||||
colororacle
|
||||
colorref
|
||||
|
@ -507,6 +512,7 @@ dealloc
|
|||
debian
|
||||
debolden
|
||||
debounce
|
||||
debugbreak
|
||||
DECALN
|
||||
DECANM
|
||||
DECAUPSS
|
||||
|
@ -542,6 +548,7 @@ DECSCUSR
|
|||
DECSED
|
||||
DECSEL
|
||||
DECSET
|
||||
DECSLPP
|
||||
DECSLRM
|
||||
DECSMBV
|
||||
DECSMKR
|
||||
|
@ -594,6 +601,7 @@ df
|
|||
DFactory
|
||||
DFMT
|
||||
dh
|
||||
dhandler
|
||||
dialogbox
|
||||
diffing
|
||||
DINLINE
|
||||
|
@ -616,6 +624,8 @@ dllmain
|
|||
DLLVERSIONINFO
|
||||
DLOAD
|
||||
DLOOK
|
||||
dmp
|
||||
dnceng
|
||||
DOCTYPE
|
||||
docx
|
||||
DONTCARE
|
||||
|
@ -638,6 +648,7 @@ DROPDOWNLIST
|
|||
DROPFILES
|
||||
drv
|
||||
dsm
|
||||
dst
|
||||
DSwap
|
||||
DTest
|
||||
dtor
|
||||
|
@ -680,6 +691,7 @@ Elems
|
|||
elif
|
||||
elseif
|
||||
emacs
|
||||
EMPTYBOX
|
||||
enabledelayedexpansion
|
||||
endian
|
||||
endif
|
||||
|
@ -703,6 +715,7 @@ errno
|
|||
errorlevel
|
||||
esa
|
||||
ETB
|
||||
etcoreapp
|
||||
ETW
|
||||
ETX
|
||||
EUDC
|
||||
|
@ -885,6 +898,7 @@ GETWHEELSCROLLCHARACTERS
|
|||
GETWHEELSCROLLCHARS
|
||||
GETWHEELSCROLLLINES
|
||||
getwriter
|
||||
GFEh
|
||||
Gfun
|
||||
gfx
|
||||
gh
|
||||
|
@ -1150,6 +1164,7 @@ iwch
|
|||
IWin
|
||||
IWindow
|
||||
IXaml
|
||||
IXMP
|
||||
jconcpp
|
||||
JOBOBJECT
|
||||
JOBOBJECTINFOCLASS
|
||||
|
@ -1232,6 +1247,7 @@ linputfile
|
|||
Linq
|
||||
linux
|
||||
listbox
|
||||
listproperties
|
||||
listptr
|
||||
listptrsize
|
||||
lk
|
||||
|
@ -1252,6 +1268,7 @@ locstudio
|
|||
Loewen
|
||||
LOGFONT
|
||||
LOGFONTW
|
||||
logissue
|
||||
Loremipsumdolorsitamet
|
||||
lowercased
|
||||
loword
|
||||
|
@ -1376,6 +1393,7 @@ mimetype
|
|||
mincore
|
||||
mindbogglingly
|
||||
mingw
|
||||
minimizeall
|
||||
minkernel
|
||||
minwin
|
||||
minwindef
|
||||
|
@ -1537,6 +1555,7 @@ NOYIELD
|
|||
NOZORDER
|
||||
NPM
|
||||
npos
|
||||
nrcs
|
||||
NSTATUS
|
||||
ntapi
|
||||
ntcon
|
||||
|
@ -1779,6 +1798,7 @@ prect
|
|||
prefast
|
||||
prefilled
|
||||
prefs
|
||||
preinstalled
|
||||
PRELOAD
|
||||
PREMULTIPLIED
|
||||
prepopulated
|
||||
|
@ -1924,6 +1944,7 @@ REGSTR
|
|||
reingest
|
||||
Relayout
|
||||
RELBINPATH
|
||||
Remoting
|
||||
renderengine
|
||||
rendersize
|
||||
reparent
|
||||
|
@ -1939,6 +1960,7 @@ resheader
|
|||
resizable
|
||||
resmimetype
|
||||
restrictedcapabilities
|
||||
restrictederrorinfo
|
||||
resw
|
||||
resx
|
||||
retval
|
||||
|
@ -1979,6 +2001,7 @@ roundtrip
|
|||
rparen
|
||||
RRF
|
||||
RRRGGGBB
|
||||
rsas
|
||||
rtcore
|
||||
RTEXT
|
||||
rtf
|
||||
|
@ -1995,6 +2018,7 @@ runformat
|
|||
runft
|
||||
RUNFULLSCREEN
|
||||
runsettings
|
||||
runtests
|
||||
runtimeclass
|
||||
runuia
|
||||
runut
|
||||
|
@ -2037,6 +2061,7 @@ SCROLLSCALE
|
|||
SCROLLSCREENBUFFER
|
||||
Scrollup
|
||||
Scrolluppage
|
||||
Scs
|
||||
scursor
|
||||
sddl
|
||||
sdeleted
|
||||
|
@ -2142,6 +2167,7 @@ SND
|
|||
SOLIDBOX
|
||||
Solutiondir
|
||||
somefile
|
||||
SOURCEBRANCH
|
||||
sourced
|
||||
SOURCESDIRECTORY
|
||||
SPACEBAR
|
||||
|
@ -2215,6 +2241,7 @@ subspan
|
|||
substr
|
||||
subsystemconsole
|
||||
subsystemwindows
|
||||
suiteless
|
||||
svg
|
||||
swapchain
|
||||
swapchainpanel
|
||||
|
@ -2266,6 +2293,7 @@ tcommandline
|
|||
tcommands
|
||||
tcon
|
||||
TDP
|
||||
TEAMPROJECT
|
||||
tearoff
|
||||
Teb
|
||||
techcommunity
|
||||
|
@ -2280,6 +2308,7 @@ TERMINALSCROLLING
|
|||
terminfo
|
||||
TEs
|
||||
testapp
|
||||
testbuildplatform
|
||||
testcon
|
||||
testd
|
||||
testdlls
|
||||
|
@ -2288,11 +2317,15 @@ testlab
|
|||
testlist
|
||||
testmd
|
||||
testmddefinition
|
||||
testmode
|
||||
testname
|
||||
testnameprefix
|
||||
TESTNULL
|
||||
testpass
|
||||
testpasses
|
||||
testtestabc
|
||||
testtesttesttesttest
|
||||
testtimeout
|
||||
TEXCOORD
|
||||
texel
|
||||
TExpected
|
||||
|
@ -2449,6 +2482,7 @@ unpause
|
|||
Unregister
|
||||
Unregistering
|
||||
unte
|
||||
untests
|
||||
untextured
|
||||
untimes
|
||||
UPDATEDISPLAY
|
||||
|
@ -2546,6 +2580,7 @@ vstudio
|
|||
vswhere
|
||||
vtapp
|
||||
VTE
|
||||
VTID
|
||||
vtio
|
||||
vtmode
|
||||
vtpipeterm
|
||||
|
@ -2682,6 +2717,7 @@ WNull
|
|||
workarea
|
||||
workaround
|
||||
workflow
|
||||
workitem
|
||||
wostream
|
||||
WOutside
|
||||
WOWARM
|
||||
|
@ -2770,6 +2806,7 @@ XSubstantial
|
|||
xtended
|
||||
xterm
|
||||
XTest
|
||||
xunit
|
||||
xutr
|
||||
xvalue
|
||||
XVIRTUALSCREEN
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
|
||||
<!-- Use our own NuGet Feed -->
|
||||
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/ms/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" />
|
||||
|
||||
<!-- Temporarily? use the feeds from our friends in MUX for Helix test stuff -->
|
||||
<add key="dotnetfeed" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
|
||||
<add key="dnceng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
|
||||
<add key="MUX-Dependencies" value="https://pkgs.dev.azure.com/ms/microsoft-ui-xaml/_packaging/MUX-Dependencies/nuget/v3/index.json" />
|
||||
|
||||
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
|
||||
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />
|
||||
|
|
|
@ -177,7 +177,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminal", "src\casc
|
|||
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia\TerminalApp\TerminalApp.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia\TerminalApp\dll\TerminalApp.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746}
|
||||
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
|
||||
|
@ -227,7 +227,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_TerminalApp", "sr
|
|||
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj", "{CA5CAD1A-9A12-429C-B551-8562EC954746}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\cascadia\TerminalApp\TerminalAppLib.vcxproj", "{CA5CAD1A-9A12-429C-B551-8562EC954746}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
|
||||
EndProjectSection
|
||||
|
|
17
README.md
17
README.md
|
@ -60,6 +60,22 @@ choco upgrade microsoft-windows-terminal
|
|||
|
||||
If you have any issues when installing/upgrading the package please go to the [Windows Terminal package page](https://chocolatey.org/packages/microsoft-windows-terminal) and follow the [Chocolatey triage process](https://chocolatey.org/docs/package-triage-process)
|
||||
|
||||
#### Via Scoop (unofficial)
|
||||
|
||||
[Scoop](https://scoop.sh) users can download and install the latest Terminal release by installing the `windows-terminal` package:
|
||||
|
||||
```powershell
|
||||
scoop install windows-terminal
|
||||
```
|
||||
|
||||
To update Windows Terminal using Scoop, run the following:
|
||||
|
||||
```powershell
|
||||
scoop update windows-terminal
|
||||
```
|
||||
|
||||
If you have any issues when installing/updating the package, please search for or report the same on the [issues page](https://github.com/lukesampson/scoop-extras/issues) of Scoop Extras bucket repository.
|
||||
|
||||
---
|
||||
|
||||
## Windows Terminal 2.0 Roadmap
|
||||
|
@ -163,6 +179,7 @@ If you would like to ask a question that you feel doesn't warrant an issue (yet)
|
|||
* Mike Griese, Developer: [@zadjii](https://twitter.com/zadjii)
|
||||
* Carlos Zamora, Developer: [@cazamor_msft](https://twitter.com/cazamor_msft)
|
||||
* Leon Liang, Developer: [@leonmsft](https://twitter.com/leonmsft)
|
||||
* Pankaj Bhojwani, Developer
|
||||
|
||||
## Developer Guidance
|
||||
|
||||
|
|
32
build/Helix/AzurePipelinesHelperScripts.ps1
Normal file
32
build/Helix/AzurePipelinesHelperScripts.ps1
Normal file
|
@ -0,0 +1,32 @@
|
|||
function GetAzureDevOpsBaseUri
|
||||
{
|
||||
Param(
|
||||
[string]$CollectionUri,
|
||||
[string]$TeamProject
|
||||
)
|
||||
|
||||
return $CollectionUri + $TeamProject
|
||||
}
|
||||
|
||||
function GetQueryTestRunsUri
|
||||
{
|
||||
Param(
|
||||
[string]$CollectionUri,
|
||||
[string]$TeamProject,
|
||||
[string]$BuildUri,
|
||||
[switch]$IncludeRunDetails
|
||||
)
|
||||
|
||||
if ($IncludeRunDetails)
|
||||
{
|
||||
$includeRunDetailsParameter = "&includeRunDetails=true"
|
||||
}
|
||||
else
|
||||
{
|
||||
$includeRunDetailsParameter = ""
|
||||
}
|
||||
|
||||
$baseUri = GetAzureDevOpsBaseUri -CollectionUri $CollectionUri -TeamProject $TeamProject
|
||||
$queryUri = "$baseUri/_apis/test/runs?buildUri=$BuildUri$includeRunDetailsParameter&api-version=5.0"
|
||||
return $queryUri
|
||||
}
|
28
build/Helix/ConvertWttLogToXUnit.ps1
Normal file
28
build/Helix/ConvertWttLogToXUnit.ps1
Normal file
|
@ -0,0 +1,28 @@
|
|||
Param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WttInputPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WttSingleRerunInputPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WttMultipleRerunInputPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$XUnitOutputPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TestNamePrefix
|
||||
)
|
||||
|
||||
# Ideally these would be passed as parameters to the script. However ps makes it difficult to deal with string literals containing '&', so we just
|
||||
# read the values directly from the environment variables
|
||||
$helixResultsContainerUri = $Env:HELIX_RESULTS_CONTAINER_URI
|
||||
$helixResultsContainerRsas = $Env:HELIX_RESULTS_CONTAINER_RSAS
|
||||
|
||||
$rerunPassesRequiredToAvoidFailure = $env:rerunPassesRequiredToAvoidFailure
|
||||
|
||||
Add-Type -Language CSharp -ReferencedAssemblies System.Xml,System.Xml.Linq,System.Runtime.Serialization,System.Runtime.Serialization.Json (Get-Content $PSScriptRoot\HelixTestHelpers.cs -Raw)
|
||||
|
||||
$testResultParser = [HelixTestHelpers.TestResultParser]::new($TestNamePrefix, $helixResultsContainerUri, $helixResultsContainerRsas)
|
||||
$testResultParser.ConvertWttLogToXUnitLog($WttInputPath, $WttSingleRerunInputPath, $WttMultipleRerunInputPath, $XUnitOutputPath, $rerunPassesRequiredToAvoidFailure)
|
112
build/Helix/EnsureMachineState.ps1
Normal file
112
build/Helix/EnsureMachineState.ps1
Normal file
|
@ -0,0 +1,112 @@
|
|||
$scriptDirectory = $script:MyInvocation.MyCommand.Path | Split-Path -Parent
|
||||
|
||||
# List all processes to aid debugging:
|
||||
Write-Host "All processes running:"
|
||||
Get-Process
|
||||
|
||||
tasklist /svc
|
||||
|
||||
# Add this test directory as an exclusion for Windows Defender
|
||||
Write-Host "Add $scriptDirectory as Exclusion Path"
|
||||
Add-MpPreference -ExclusionPath $scriptDirectory
|
||||
Write-Host "Add $($env:HELIX_CORRELATION_PAYLOAD) as Exclusion Path"
|
||||
Add-MpPreference -ExclusionPath $env:HELIX_CORRELATION_PAYLOAD
|
||||
Get-MpPreference
|
||||
Get-MpComputerStatus
|
||||
|
||||
|
||||
# Minimize all windows:
|
||||
$shell = New-Object -ComObject "Shell.Application"
|
||||
$shell.minimizeall()
|
||||
|
||||
# Kill any instances of Windows Security Alert:
|
||||
$windowTitleToMatch = "*Windows Security Alert*"
|
||||
$procs = Get-Process | Where {$_.MainWindowTitle -like "*Windows Security Alert*"}
|
||||
foreach ($proc in $procs)
|
||||
{
|
||||
Write-Host "Found process with '$windowTitleToMatch' title: $proc"
|
||||
$proc.Kill();
|
||||
}
|
||||
|
||||
# Kill processes by name that are known to interfere with our tests:
|
||||
$processNamesToStop = @("Microsoft.Photos", "WinStore.App", "SkypeApp", "SkypeBackgroundHost", "OneDriveSetup", "OneDrive")
|
||||
foreach($procName in $processNamesToStop)
|
||||
{
|
||||
Write-Host "Attempting to kill $procName if it is running"
|
||||
Stop-Process -ProcessName $procName -Verbose -ErrorAction Ignore
|
||||
}
|
||||
Write-Host "All processes running after attempting to kill unwanted processes:"
|
||||
Get-Process
|
||||
|
||||
tasklist /svc
|
||||
|
||||
$platform = $env:testbuildplatform
|
||||
if(!$platform)
|
||||
{
|
||||
$platform = "x86"
|
||||
}
|
||||
|
||||
function UninstallApps {
|
||||
Param([string[]]$appsToUninstall)
|
||||
|
||||
foreach($pkgName in $appsToUninstall)
|
||||
{
|
||||
foreach($pkg in (Get-AppxPackage $pkgName).PackageFullName)
|
||||
{
|
||||
Write-Output "Removing: $pkg"
|
||||
Remove-AppxPackage $pkg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function UninstallTestApps {
|
||||
Param([string[]]$appsToUninstall)
|
||||
|
||||
foreach($pkgName in $appsToUninstall)
|
||||
{
|
||||
foreach($pkg in (Get-AppxPackage $pkgName).PackageFullName)
|
||||
{
|
||||
Write-Output "Removing: $pkg"
|
||||
Remove-AppxPackage $pkg
|
||||
}
|
||||
|
||||
# Sometimes an app can get into a state where it is no longer returned by Get-AppxPackage, but it is still present
|
||||
# which prevents other versions of the app from being installed.
|
||||
# To handle this, we can directly call Remove-AppxPackage against the full name of the package. However, without
|
||||
# Get-AppxPackage to find the PackageFullName, we just have to manually construct the name.
|
||||
$packageFullName = "$($pkgName)_1.0.0.0_$($platform)__8wekyb3d8bbwe"
|
||||
Write-Host "Removing $packageFullName if installed"
|
||||
Remove-AppPackage $packageFullName -ErrorVariable appxerror -ErrorAction SilentlyContinue
|
||||
if($appxerror)
|
||||
{
|
||||
foreach($error in $appxerror)
|
||||
{
|
||||
# In most cases, Remove-AppPackage will fail due to the package not being found. Don't treat this as an error.
|
||||
if(!($error.Exception.Message -match "0x80073CF1"))
|
||||
{
|
||||
Write-Error $error
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "Successfully removed $packageFullName"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Uninstall AppX packages that are known to cause issues with our tests"
|
||||
UninstallApps("*Skype*", "*Windows.Photos*")
|
||||
|
||||
Write-Host "Uninstall any of our test apps that may have been left over from previous test runs"
|
||||
UninstallTestApps("NugetPackageTestApp", "NugetPackageTestAppCX", "IXMPTestApp", "MUXControlsTestApp")
|
||||
|
||||
Write-Host "Uninstall MUX Framework package that may have been left over from previous test runs"
|
||||
# We don't want to uninstall all versions of the MUX Framework package, as there may be other apps preinstalled on the system
|
||||
# that depend on it. We only uninstall the Framework package that corresponds to the version of MUX that we are testing.
|
||||
[xml]$versionData = (Get-Content "version.props")
|
||||
$versionMajor = $versionData.GetElementsByTagName("MUXVersionMajor").'#text'
|
||||
$versionMinor = $versionData.GetElementsByTagName("MUXVersionMinor").'#text'
|
||||
UninstallApps("Microsoft.UI.Xaml.$versionMajor.$versionMinor")
|
||||
|
||||
Get-Process
|
336
build/Helix/GenerateTestProjFile.ps1
Normal file
336
build/Helix/GenerateTestProjFile.ps1
Normal file
|
@ -0,0 +1,336 @@
|
|||
[CmdLetBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TestFile,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$OutputProjFile,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$JobTestSuiteName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TaefPath,
|
||||
|
||||
[string]$TaefQuery
|
||||
)
|
||||
|
||||
Class TestCollection
|
||||
{
|
||||
[string]$Name
|
||||
[string]$SetupMethodName
|
||||
[string]$TeardownMethodName
|
||||
[System.Collections.Generic.Dictionary[string, string]]$Properties
|
||||
|
||||
TestCollection()
|
||||
{
|
||||
if ($this.GetType() -eq [TestCollection])
|
||||
{
|
||||
throw "This class should never be instantiated directly; it should only be derived from."
|
||||
}
|
||||
}
|
||||
|
||||
TestCollection([string]$name)
|
||||
{
|
||||
$this.Init($name)
|
||||
}
|
||||
|
||||
hidden Init([string]$name)
|
||||
{
|
||||
$this.Name = $name
|
||||
$this.Properties = @{}
|
||||
}
|
||||
}
|
||||
|
||||
Class Test : TestCollection
|
||||
{
|
||||
Test([string]$name)
|
||||
{
|
||||
$this.Init($name)
|
||||
}
|
||||
}
|
||||
|
||||
Class TestClass : TestCollection
|
||||
{
|
||||
[System.Collections.Generic.List[Test]]$Tests
|
||||
|
||||
TestClass([string]$name)
|
||||
{
|
||||
$this.Init($name)
|
||||
$this.Tests = @{}
|
||||
}
|
||||
}
|
||||
|
||||
Class TestModule : TestCollection
|
||||
{
|
||||
[System.Collections.Generic.List[TestClass]]$TestClasses
|
||||
|
||||
TestModule([string]$name)
|
||||
{
|
||||
$this.Init($name)
|
||||
$this.TestClasses = @{}
|
||||
}
|
||||
}
|
||||
|
||||
function Parse-TestInfo([string]$taefOutput)
|
||||
{
|
||||
enum LineType
|
||||
{
|
||||
None
|
||||
TestModule
|
||||
TestClass
|
||||
Test
|
||||
Setup
|
||||
Teardown
|
||||
Property
|
||||
}
|
||||
|
||||
[string]$testModuleIndentation = " "
|
||||
[string]$testClassIndentation = " "
|
||||
[string]$testIndentation = " "
|
||||
[string]$setupBeginning = "Setup: "
|
||||
[string]$teardownBeginning = "Teardown: "
|
||||
[string]$propertyBeginning = "Property["
|
||||
|
||||
function Get-LineType([string]$line)
|
||||
{
|
||||
if ($line.Contains($setupBeginning))
|
||||
{
|
||||
return [LineType]::Setup;
|
||||
}
|
||||
elseif ($line.Contains($teardownBeginning))
|
||||
{
|
||||
return [LineType]::Teardown;
|
||||
}
|
||||
elseif ($line.Contains($propertyBeginning))
|
||||
{
|
||||
return [LineType]::Property;
|
||||
}
|
||||
elseif ($line.StartsWith($testModuleIndentation) -and -not $line.StartsWith("$testModuleIndentation "))
|
||||
{
|
||||
return [LineType]::TestModule;
|
||||
}
|
||||
elseif ($line.StartsWith($testClassIndentation) -and -not $line.StartsWith("$testClassIndentation "))
|
||||
{
|
||||
return [LineType]::TestClass;
|
||||
}
|
||||
elseif ($line.StartsWith($testIndentation) -and -not $line.StartsWith("$testIndentation "))
|
||||
{
|
||||
return [LineType]::Test;
|
||||
}
|
||||
else
|
||||
{
|
||||
return [LineType]::None;
|
||||
}
|
||||
}
|
||||
|
||||
[string[]]$lines = $taefOutput.Split(@([Environment]::NewLine, "`n"), [StringSplitOptions]::RemoveEmptyEntries)
|
||||
[System.Collections.Generic.List[TestModule]]$testModules = @()
|
||||
|
||||
[TestModule]$currentTestModule = $null
|
||||
[TestClass]$currentTestClass = $null
|
||||
[Test]$currentTest = $null
|
||||
|
||||
[TestCollection]$lastTestCollection = $null
|
||||
|
||||
foreach ($rawLine in $lines)
|
||||
{
|
||||
[LineType]$lineType = (Get-LineType $rawLine)
|
||||
|
||||
# We don't need the whitespace around the line anymore, so we'll discard it to make things easier.
|
||||
[string]$line = $rawLine.Trim()
|
||||
|
||||
if ($lineType -eq [LineType]::TestModule)
|
||||
{
|
||||
if ($currentTest -ne $null -and $currentTestClass -ne $null)
|
||||
{
|
||||
$currentTestClass.Tests.Add($currentTest)
|
||||
}
|
||||
|
||||
if ($currentTestClass -ne $null -and $currentTestModule -ne $null)
|
||||
{
|
||||
$currentTestModule.TestClasses.Add($currentTestClass)
|
||||
}
|
||||
|
||||
if ($currentTestModule -ne $null)
|
||||
{
|
||||
$testModules.Add($currentTestModule)
|
||||
}
|
||||
|
||||
$currentTestModule = [TestModule]::new($line)
|
||||
$currentTestClass = $null
|
||||
$currentTest = $null
|
||||
$lastTestCollection = $currentTestModule
|
||||
}
|
||||
elseif ($lineType -eq [LineType]::TestClass)
|
||||
{
|
||||
if ($currentTest -ne $null -and $currentTestClass -ne $null)
|
||||
{
|
||||
$currentTestClass.Tests.Add($currentTest)
|
||||
}
|
||||
|
||||
if ($currentTestClass -ne $null -and $currentTestModule -ne $null)
|
||||
{
|
||||
$currentTestModule.TestClasses.Add($currentTestClass)
|
||||
}
|
||||
|
||||
$currentTestClass = [TestClass]::new($line)
|
||||
$currentTest = $null
|
||||
$lastTestCollection = $currentTestClass
|
||||
}
|
||||
elseif ($lineType -eq [LineType]::Test)
|
||||
{
|
||||
if ($currentTest -ne $null -and $currentTestClass -ne $null)
|
||||
{
|
||||
$currentTestClass.Tests.Add($currentTest)
|
||||
}
|
||||
|
||||
$currentTest = [Test]::new($line)
|
||||
$lastTestCollection = $currentTest
|
||||
}
|
||||
elseif ($lineType -eq [LineType]::Setup)
|
||||
{
|
||||
if ($lastTestCollection -ne $null)
|
||||
{
|
||||
$lastTestCollection.SetupMethodName = $line.Replace($setupBeginning, "")
|
||||
}
|
||||
}
|
||||
elseif ($lineType -eq [LineType]::Teardown)
|
||||
{
|
||||
if ($lastTestCollection -ne $null)
|
||||
{
|
||||
$lastTestCollection.TeardownMethodName = $line.Replace($teardownBeginning, "")
|
||||
}
|
||||
}
|
||||
elseif ($lineType -eq [LineType]::Property)
|
||||
{
|
||||
if ($lastTestCollection -ne $null)
|
||||
{
|
||||
foreach ($match in [Regex]::Matches($line, "Property\[(.*)\]\s+=\s+(.*)"))
|
||||
{
|
||||
[string]$propertyKey = $match.Groups[1].Value;
|
||||
[string]$propertyValue = $match.Groups[2].Value;
|
||||
$lastTestCollection.Properties.Add($propertyKey, $propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($currentTest -ne $null -and $currentTestClass -ne $null)
|
||||
{
|
||||
$currentTestClass.Tests.Add($currentTest)
|
||||
}
|
||||
|
||||
if ($currentTestClass -ne $null -and $currentTestModule -ne $null)
|
||||
{
|
||||
$currentTestModule.TestClasses.Add($currentTestClass)
|
||||
}
|
||||
|
||||
if ($currentTestModule -ne $null)
|
||||
{
|
||||
$testModules.Add($currentTestModule)
|
||||
}
|
||||
|
||||
return $testModules
|
||||
}
|
||||
|
||||
Write-Verbose "TaefQuery = $TaefQuery"
|
||||
|
||||
$TaefSelectQuery = ""
|
||||
$TaefQueryToAppend = ""
|
||||
if($TaefQuery)
|
||||
{
|
||||
$TaefSelectQuery = "/select:`"$TaefQuery`""
|
||||
$TaefQueryToAppend = " and $TaefQuery"
|
||||
}
|
||||
Write-Verbose "TaefSelectQuery = $TaefSelectQuery"
|
||||
|
||||
|
||||
$taefExe = "$TaefPath\te.exe"
|
||||
[string]$taefOutput = & "$taefExe" /listproperties $TaefSelectQuery $TestFile | Out-String
|
||||
|
||||
[System.Collections.Generic.List[TestModule]]$testModules = (Parse-TestInfo $taefOutput)
|
||||
|
||||
$projFileContent = @"
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
"@
|
||||
|
||||
foreach ($testModule in $testModules)
|
||||
{
|
||||
foreach ($testClass in $testModules.TestClasses)
|
||||
{
|
||||
Write-Host "Generating Helix work item for test class $($testClass.Name)..."
|
||||
[System.Collections.Generic.List[string]]$testSuiteNames = @()
|
||||
|
||||
$testSuiteExists = $false
|
||||
$suitelessTestExists = $false
|
||||
|
||||
foreach ($test in $testClass.Tests)
|
||||
{
|
||||
# A test method inherits its 'TestSuite' property from its TestClass
|
||||
if (!$test.Properties.ContainsKey("TestSuite") -and $testClass.Properties.ContainsKey("TestSuite"))
|
||||
{
|
||||
$test.Properties["TestSuite"] = $testClass.Properties["TestSuite"]
|
||||
}
|
||||
|
||||
if ($test.Properties.ContainsKey("TestSuite"))
|
||||
{
|
||||
[string]$testSuite = $test.Properties["TestSuite"]
|
||||
|
||||
if (-not $testSuiteNames.Contains($testSuite))
|
||||
{
|
||||
Write-Host " Found test suite $testSuite. Generating Helix work item for it as well."
|
||||
$testSuiteNames.Add($testSuite)
|
||||
}
|
||||
|
||||
$testSuiteExists = $true
|
||||
}
|
||||
else
|
||||
{
|
||||
$suitelessTestExists = $true
|
||||
}
|
||||
}
|
||||
|
||||
$testClassSelectPattern = "$($testClass.Name).*"
|
||||
if($testClass.Name.Contains("::"))
|
||||
{
|
||||
$testClassSelectPattern = "$($testClass.Name)::*"
|
||||
}
|
||||
$testNameQuery= "(@Name='$testClassSelectPattern')"
|
||||
|
||||
$workItemName = $testClass.Name
|
||||
# Native tests use '::' as a separator, which is not valid for workItem names.
|
||||
$workItemName = $workItemName -replace "::", "-"
|
||||
|
||||
if ($suitelessTestExists)
|
||||
{
|
||||
$projFileContent += @"
|
||||
|
||||
<HelixWorkItem Include="$($workItemName)" Condition="'`$(TestSuite)'=='$($JobTestSuiteName)'">
|
||||
<Timeout>00:30:00</Timeout>
|
||||
<Command>call %HELIX_CORRELATION_PAYLOAD%\runtests.cmd /select:"(@Name='$($testClass.Name)*'$(if ($testSuiteExists) { "and not @TestSuite='*'" }))$($TaefQueryToAppend)"</Command>
|
||||
</HelixWorkItem>
|
||||
"@
|
||||
}
|
||||
|
||||
foreach ($testSuiteName in $testSuiteNames)
|
||||
{
|
||||
$projFileContent += @"
|
||||
|
||||
<HelixWorkItem Include="$($workItemName)-$testSuiteName" Condition="'`$(TestSuite)'=='$($JobTestSuiteName)'">
|
||||
<Timeout>00:30:00</Timeout>
|
||||
<Command>call %HELIX_CORRELATION_PAYLOAD%\runtests.cmd /select:"(@Name='$($testClass.Name)*' and @TestSuite='$testSuiteName')$($TaefQueryToAppend)"</Command>
|
||||
</HelixWorkItem>
|
||||
"@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$projFileContent += @"
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
"@
|
||||
|
||||
Set-Content $OutputProjFile $projFileContent -NoNewline -Encoding UTF8
|
669
build/Helix/HelixTestHelpers.cs
Normal file
669
build/Helix/HelixTestHelpers.cs
Normal file
|
@ -0,0 +1,669 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace HelixTestHelpers
|
||||
{
|
||||
public class TestResult
|
||||
{
|
||||
public TestResult()
|
||||
{
|
||||
Screenshots = new List<string>();
|
||||
RerunResults = new List<TestResult>();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string SourceWttFile { get; set; }
|
||||
public bool Passed { get; set; }
|
||||
public bool CleanupPassed { get; set; }
|
||||
public TimeSpan ExecutionTime { get; set; }
|
||||
public string Details { get; set; }
|
||||
|
||||
public List<string> Screenshots { get; private set; }
|
||||
public List<TestResult> RerunResults { get; private set; }
|
||||
|
||||
// Returns true if the test pass rate is sufficient to avoid being counted as a failure.
|
||||
public bool PassedOrUnreliable(int requiredNumberOfPasses)
|
||||
{
|
||||
if(Passed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(RerunResults.Count == 1)
|
||||
{
|
||||
return RerunResults[0].Passed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return RerunResults.Where(r => r.Passed).Count() >= requiredNumberOfPasses;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Azure DevOps doesn't currently provide a way to directly report sub-results for tests that failed at least once
|
||||
// that were run multiple times. To get around that limitation, we'll mark the test as "Skip" since
|
||||
// that's the only non-pass/fail result we can return, and will then report the information about the
|
||||
// runs in the "reason" category for the skipped test. In order to save space, we'll make the following
|
||||
// optimizations for size:
|
||||
//
|
||||
// 1. Serialize as JSON, which is more compact than XML;
|
||||
// 2. Don't serialize values that we don't need;
|
||||
// 3. Store the URL prefix and suffix for the blob storage URL only once instead of
|
||||
// storing every log and screenshot URL in its entirety; and
|
||||
// 4. Store a list of unique error messages and then index into that instead of
|
||||
// storing every error message in its entirety.
|
||||
//
|
||||
// #4 is motivated by the fact that if a test fails multiple times, it probably failed for the same reason
|
||||
// each time, in which case we'd just be repeating ourselves if we stored every error message each time.
|
||||
//
|
||||
// TODO (https://github.com/dotnet/arcade/issues/2773): Once we're able to directly report things in a
|
||||
// more granular fashion than just a binary pass/fail result, we should do that.
|
||||
//
|
||||
[DataContract]
|
||||
internal class JsonSerializableTestResults
|
||||
{
|
||||
[DataMember]
|
||||
internal string blobPrefix;
|
||||
|
||||
[DataMember]
|
||||
internal string blobSuffix;
|
||||
|
||||
[DataMember]
|
||||
internal string[] errors;
|
||||
|
||||
[DataMember]
|
||||
internal JsonSerializableTestResult[] results;
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
internal class JsonSerializableTestResult
|
||||
{
|
||||
[DataMember]
|
||||
internal string outcome;
|
||||
|
||||
[DataMember]
|
||||
internal int duration;
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
internal string log;
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
internal string[] screenshots;
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
internal int errorIndex;
|
||||
}
|
||||
|
||||
public class TestPass
|
||||
{
|
||||
public TimeSpan TestPassExecutionTime { get; set; }
|
||||
public List<TestResult> TestResults { get; set; }
|
||||
|
||||
public static TestPass ParseTestWttFile(string fileName, bool cleanupFailuresAreRegressions, bool truncateTestNames)
|
||||
{
|
||||
using (var stream = File.OpenRead(fileName))
|
||||
{
|
||||
var doc = XDocument.Load(stream);
|
||||
var testResults = new List<TestResult>();
|
||||
var testExecutionTimeMap = new Dictionary<string, List<double>>();
|
||||
|
||||
TestResult currentResult = null;
|
||||
long frequency = 0;
|
||||
long startTime = 0;
|
||||
long stopTime = 0;
|
||||
bool inTestCleanup = false;
|
||||
|
||||
bool shouldLogToTestDetails = false;
|
||||
|
||||
long testPassStartTime = 0;
|
||||
long testPassStopTime = 0;
|
||||
|
||||
Func<XElement, bool> isScopeData = (elt) =>
|
||||
{
|
||||
return
|
||||
elt.Element("Data") != null &&
|
||||
elt.Element("Data").Element("WexContext") != null &&
|
||||
(
|
||||
elt.Element("Data").Element("WexContext").Value == "Cleanup" ||
|
||||
elt.Element("Data").Element("WexContext").Value == "TestScope" ||
|
||||
elt.Element("Data").Element("WexContext").Value == "TestScope" ||
|
||||
elt.Element("Data").Element("WexContext").Value == "ClassScope" ||
|
||||
elt.Element("Data").Element("WexContext").Value == "ModuleScope"
|
||||
);
|
||||
};
|
||||
|
||||
Func<XElement, bool> isModuleOrClassScopeStart = (elt) =>
|
||||
{
|
||||
return
|
||||
elt.Name == "Msg" &&
|
||||
elt.Element("Data") != null &&
|
||||
elt.Element("Data").Element("StartGroup") != null &&
|
||||
elt.Element("Data").Element("WexContext") != null &&
|
||||
(elt.Element("Data").Element("WexContext").Value == "ClassScope" ||
|
||||
elt.Element("Data").Element("WexContext").Value == "ModuleScope");
|
||||
};
|
||||
|
||||
Func<XElement, bool> isModuleScopeEnd = (elt) =>
|
||||
{
|
||||
return
|
||||
elt.Name == "Msg" &&
|
||||
elt.Element("Data") != null &&
|
||||
elt.Element("Data").Element("EndGroup") != null &&
|
||||
elt.Element("Data").Element("WexContext") != null &&
|
||||
elt.Element("Data").Element("WexContext").Value == "ModuleScope";
|
||||
};
|
||||
|
||||
Func<XElement, bool> isClassScopeEnd = (elt) =>
|
||||
{
|
||||
return
|
||||
elt.Name == "Msg" &&
|
||||
elt.Element("Data") != null &&
|
||||
elt.Element("Data").Element("EndGroup") != null &&
|
||||
elt.Element("Data").Element("WexContext") != null &&
|
||||
elt.Element("Data").Element("WexContext").Value == "ClassScope";
|
||||
};
|
||||
|
||||
int testsExecuting = 0;
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
// Capturing the frequency data to record accurate
|
||||
// timing data.
|
||||
if (element.Name == "RTI")
|
||||
{
|
||||
frequency = Int64.Parse(element.Attribute("Frequency").Value);
|
||||
}
|
||||
|
||||
// It's possible for a test to launch another test. If that happens, we won't modify the
|
||||
// current result. Instead, we'll continue operating like normal and expect that we get two
|
||||
// EndTests nodes before our next StartTests. We'll check that we've actually got a stop time
|
||||
// before creating a new result. This will result in the two results being squashed
|
||||
// into one result of the outer test that ran the inner one.
|
||||
if (element.Name == "StartTest")
|
||||
{
|
||||
testsExecuting++;
|
||||
if (testsExecuting == 1)
|
||||
{
|
||||
string testName = element.Attribute("Title").Value;
|
||||
|
||||
if (truncateTestNames)
|
||||
{
|
||||
const string xamlNativePrefix = "Windows::UI::Xaml::Tests::";
|
||||
const string xamlManagedPrefix = "Windows.UI.Xaml.Tests.";
|
||||
if (testName.StartsWith(xamlNativePrefix))
|
||||
{
|
||||
testName = testName.Substring(xamlNativePrefix.Length);
|
||||
}
|
||||
else if (testName.StartsWith(xamlManagedPrefix))
|
||||
{
|
||||
testName = testName.Substring(xamlManagedPrefix.Length);
|
||||
}
|
||||
}
|
||||
|
||||
currentResult = new TestResult() { Name = testName, SourceWttFile = fileName, Passed = true, CleanupPassed = true };
|
||||
testResults.Add(currentResult);
|
||||
startTime = Int64.Parse(element.Descendants("WexTraceInfo").First().Attribute("TimeStamp").Value);
|
||||
inTestCleanup = false;
|
||||
shouldLogToTestDetails = true;
|
||||
stopTime = 0;
|
||||
}
|
||||
}
|
||||
else if (currentResult != null && element.Name == "EndTest")
|
||||
{
|
||||
testsExecuting--;
|
||||
|
||||
// If any inner test fails, we'll still fail the outer
|
||||
currentResult.Passed &= element.Attribute("Result").Value == "Pass";
|
||||
|
||||
// Only gather execution data if this is the outer test we ran initially
|
||||
if (testsExecuting == 0)
|
||||
{
|
||||
stopTime = Int64.Parse(element.Descendants("WexTraceInfo").First().Attribute("TimeStamp").Value);
|
||||
if (!testExecutionTimeMap.Keys.Contains(currentResult.Name))
|
||||
testExecutionTimeMap[currentResult.Name] = new List<double>();
|
||||
testExecutionTimeMap[currentResult.Name].Add((double)(stopTime - startTime) / frequency);
|
||||
currentResult.ExecutionTime = TimeSpan.FromSeconds(testExecutionTimeMap[currentResult.Name].Average());
|
||||
|
||||
startTime = 0;
|
||||
inTestCleanup = true;
|
||||
}
|
||||
}
|
||||
else if (currentResult != null &&
|
||||
(isModuleOrClassScopeStart(element) || isModuleScopeEnd(element) || isClassScopeEnd(element)))
|
||||
{
|
||||
shouldLogToTestDetails = false;
|
||||
inTestCleanup = false;
|
||||
}
|
||||
|
||||
// Log-appending methods.
|
||||
if (currentResult != null && element.Name == "Error")
|
||||
{
|
||||
if (shouldLogToTestDetails)
|
||||
{
|
||||
currentResult.Details += "\r\n[Error]: " + element.Attribute("UserText").Value;
|
||||
if (element.Attribute("File") != null && element.Attribute("File").Value != "")
|
||||
{
|
||||
currentResult.Details += (" [File " + element.Attribute("File").Value);
|
||||
if (element.Attribute("Line") != null)
|
||||
currentResult.Details += " Line: " + element.Attribute("Line").Value;
|
||||
currentResult.Details += "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The test cleanup errors will often come after the test claimed to have
|
||||
// 'passed'. We treat them as errors as well.
|
||||
if (inTestCleanup)
|
||||
{
|
||||
currentResult.CleanupPassed = false;
|
||||
currentResult.Passed = false;
|
||||
// In stress mode runs, this test will run n times before cleanup is run. If the cleanup
|
||||
// fails, we want to fail every test.
|
||||
if (cleanupFailuresAreRegressions)
|
||||
{
|
||||
foreach (var result in testResults.Where(res => res.Name == currentResult.Name))
|
||||
{
|
||||
result.Passed = false;
|
||||
result.CleanupPassed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentResult != null && element.Name == "Warn")
|
||||
{
|
||||
if (shouldLogToTestDetails)
|
||||
{
|
||||
currentResult.Details += "\r\n[Warn]: " + element.Attribute("UserText").Value;
|
||||
}
|
||||
|
||||
if (element.Attribute("File") != null && element.Attribute("File").Value != "")
|
||||
{
|
||||
currentResult.Details += (" [File " + element.Attribute("File").Value);
|
||||
if (element.Attribute("Line") != null)
|
||||
currentResult.Details += " Line: " + element.Attribute("Line").Value;
|
||||
currentResult.Details += "]";
|
||||
}
|
||||
}
|
||||
|
||||
if (currentResult != null && element.Name == "Msg")
|
||||
{
|
||||
var dataElement = element.Element("Data");
|
||||
if (dataElement != null)
|
||||
{
|
||||
var supportingInfo = dataElement.Element("SupportingInfo");
|
||||
if (supportingInfo != null)
|
||||
{
|
||||
var screenshots = supportingInfo.Elements("Item")
|
||||
.Where(item => GetAttributeValue(item, "Name") == "Screenshot")
|
||||
.Select(item => GetAttributeValue(item, "Value"));
|
||||
|
||||
foreach(var screenshot in screenshots)
|
||||
{
|
||||
string fileNameSuffix = string.Empty;
|
||||
|
||||
if (fileName.Contains("_rerun_multiple"))
|
||||
{
|
||||
fileNameSuffix = "_rerun_multiple";
|
||||
}
|
||||
else if (fileName.Contains("_rerun"))
|
||||
{
|
||||
fileNameSuffix = "_rerun";
|
||||
}
|
||||
|
||||
currentResult.Screenshots.Add(screenshot.Replace(".jpg", fileNameSuffix + ".jpg"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testPassStartTime = Int64.Parse(doc.Root.Descendants("WexTraceInfo").First().Attribute("TimeStamp").Value);
|
||||
testPassStopTime = Int64.Parse(doc.Root.Descendants("WexTraceInfo").Last().Attribute("TimeStamp").Value);
|
||||
|
||||
var testPassTime = TimeSpan.FromSeconds((double)(testPassStopTime - testPassStartTime) / frequency);
|
||||
|
||||
foreach (TestResult testResult in testResults)
|
||||
{
|
||||
if (testResult.Details != null)
|
||||
{
|
||||
testResult.Details = testResult.Details.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
var testpass = new TestPass
|
||||
{
|
||||
TestPassExecutionTime = testPassTime,
|
||||
TestResults = testResults
|
||||
};
|
||||
|
||||
return testpass;
|
||||
}
|
||||
}
|
||||
|
||||
public static TestPass ParseTestWttFileWithReruns(string fileName, string singleRerunFileName, string multipleRerunFileName, bool cleanupFailuresAreRegressions, bool truncateTestNames)
|
||||
{
|
||||
TestPass testPass = ParseTestWttFile(fileName, cleanupFailuresAreRegressions, truncateTestNames);
|
||||
TestPass singleRerunTestPass = File.Exists(singleRerunFileName) ? ParseTestWttFile(singleRerunFileName, cleanupFailuresAreRegressions, truncateTestNames) : null;
|
||||
TestPass multipleRerunTestPass = File.Exists(multipleRerunFileName) ? ParseTestWttFile(multipleRerunFileName, cleanupFailuresAreRegressions, truncateTestNames) : null;
|
||||
|
||||
List<TestResult> rerunTestResults = new List<TestResult>();
|
||||
|
||||
if (singleRerunTestPass != null)
|
||||
{
|
||||
rerunTestResults.AddRange(singleRerunTestPass.TestResults);
|
||||
}
|
||||
|
||||
if (multipleRerunTestPass != null)
|
||||
{
|
||||
rerunTestResults.AddRange(multipleRerunTestPass.TestResults);
|
||||
}
|
||||
|
||||
// For each failed test result, we'll check to see whether the test passed at least once upon rerun.
|
||||
// If so, we'll set PassedOnRerun to true to flag the fact that this is an unreliable test
|
||||
// rather than a genuine test failure.
|
||||
foreach (TestResult failedTestResult in testPass.TestResults.Where(r => !r.Passed))
|
||||
{
|
||||
failedTestResult.RerunResults.AddRange(rerunTestResults.Where(r => r.Name == failedTestResult.Name));
|
||||
}
|
||||
|
||||
return testPass;
|
||||
}
|
||||
|
||||
private static string GetAttributeValue(XElement element, string attributeName)
|
||||
{
|
||||
if(element.Attribute(attributeName) != null)
|
||||
{
|
||||
return element.Attribute(attributeName).Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FailedTestDetector
|
||||
{
|
||||
public static void OutputFailedTestQuery(string wttInputPath)
|
||||
{
|
||||
var testPass = TestPass.ParseTestWttFile(wttInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false);
|
||||
|
||||
List<string> failedTestNames = new List<string>();
|
||||
|
||||
foreach (var result in testPass.TestResults)
|
||||
{
|
||||
if (!result.Passed)
|
||||
{
|
||||
failedTestNames.Add(result.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedTestNames.Count > 0)
|
||||
{
|
||||
string failedTestSelectQuery = "(@Name='";
|
||||
|
||||
for (int i = 0; i < failedTestNames.Count; i++)
|
||||
{
|
||||
failedTestSelectQuery += failedTestNames[i];
|
||||
|
||||
if (i < failedTestNames.Count - 1)
|
||||
{
|
||||
failedTestSelectQuery += "' or @Name='";
|
||||
}
|
||||
}
|
||||
|
||||
failedTestSelectQuery += "')";
|
||||
|
||||
Console.WriteLine(failedTestSelectQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TestResultParser
|
||||
{
|
||||
private string testNamePrefix;
|
||||
private string helixResultsContainerUri;
|
||||
private string helixResultsContainerRsas;
|
||||
|
||||
public TestResultParser(string testNamePrefix, string helixResultsContainerUri, string helixResultsContainerRsas)
|
||||
{
|
||||
this.testNamePrefix = testNamePrefix;
|
||||
this.helixResultsContainerUri = helixResultsContainerUri;
|
||||
this.helixResultsContainerRsas = helixResultsContainerRsas;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetSubResultsJsonByMethodName(string wttInputPath, string wttSingleRerunInputPath, string wttMultipleRerunInputPath)
|
||||
{
|
||||
Dictionary<string, string> subResultsJsonByMethod = new Dictionary<string, string>();
|
||||
TestPass testPass = TestPass.ParseTestWttFileWithReruns(wttInputPath, wttSingleRerunInputPath, wttMultipleRerunInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false);
|
||||
|
||||
foreach (var result in testPass.TestResults)
|
||||
{
|
||||
var methodName = result.Name.Substring(result.Name.LastIndexOf('.') + 1);
|
||||
|
||||
if (!result.Passed)
|
||||
{
|
||||
// If a test failed, we'll have rerun it multiple times. We'll record the results of each run
|
||||
// formatted as JSON.
|
||||
JsonSerializableTestResults serializableResults = new JsonSerializableTestResults();
|
||||
serializableResults.blobPrefix = helixResultsContainerUri;
|
||||
serializableResults.blobSuffix = helixResultsContainerRsas;
|
||||
|
||||
List<string> errorList = new List<string>();
|
||||
errorList.Add(result.Details);
|
||||
|
||||
foreach (TestResult rerunResult in result.RerunResults)
|
||||
{
|
||||
errorList.Add(rerunResult.Details);
|
||||
}
|
||||
|
||||
serializableResults.errors = errorList.Distinct().Where(s => s != null).ToArray();
|
||||
|
||||
var reason = new XElement("reason");
|
||||
List<JsonSerializableTestResult> serializableResultList = new List<JsonSerializableTestResult>();
|
||||
serializableResultList.Add(ConvertToSerializableResult(result, serializableResults.errors));
|
||||
|
||||
foreach (TestResult rerunResult in result.RerunResults)
|
||||
{
|
||||
serializableResultList.Add(ConvertToSerializableResult(rerunResult, serializableResults.errors));
|
||||
}
|
||||
|
||||
serializableResults.results = serializableResultList.ToArray();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(JsonSerializableTestResults));
|
||||
serializer.WriteObject(stream, serializableResults);
|
||||
stream.Position = 0;
|
||||
|
||||
using (StreamReader streamReader = new StreamReader(stream))
|
||||
{
|
||||
subResultsJsonByMethod.Add(methodName, streamReader.ReadToEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return subResultsJsonByMethod;
|
||||
}
|
||||
|
||||
public void ConvertWttLogToXUnitLog(string wttInputPath, string wttSingleRerunInputPath, string wttMultipleRerunInputPath, string xunitOutputPath, int requiredPassRateThreshold)
|
||||
{
|
||||
TestPass testPass = TestPass.ParseTestWttFileWithReruns(wttInputPath, wttSingleRerunInputPath, wttMultipleRerunInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false);
|
||||
var results = testPass.TestResults;
|
||||
|
||||
int resultCount = results.Count;
|
||||
int passedCount = results.Where(r => r.Passed).Count();
|
||||
|
||||
// Since we re-run tests on failure, we'll mark every test that failed at least once as "skipped" rather than "failed".
|
||||
// If the test failed sufficiently often enough for it to count as a failed test (determined by a property on the
|
||||
// Azure DevOps job), we'll later mark it as failed during test results processing.
|
||||
|
||||
int failedCount = results.Where(r => !r.PassedOrUnreliable(requiredPassRateThreshold)).Count();
|
||||
int skippedCount = results.Where(r => !r.Passed && r.PassedOrUnreliable(requiredPassRateThreshold)).Count();
|
||||
|
||||
var root = new XElement("assemblies");
|
||||
|
||||
var assembly = new XElement("assembly");
|
||||
assembly.SetAttributeValue("name", "MUXControls.Test.dll");
|
||||
assembly.SetAttributeValue("test-framework", "TAEF");
|
||||
assembly.SetAttributeValue("run-date", DateTime.Now.ToString("yyyy-MM-dd"));
|
||||
|
||||
// This doesn't need to be completely accurate since it's not exposed anywhere.
|
||||
// If we need accurate an start time we can probably calculate it from the te.wtl file, but for
|
||||
// now this is fine.
|
||||
assembly.SetAttributeValue("run-time", (DateTime.Now - testPass.TestPassExecutionTime).ToString("hh:mm:ss"));
|
||||
|
||||
assembly.SetAttributeValue("total", resultCount);
|
||||
assembly.SetAttributeValue("passed", passedCount);
|
||||
assembly.SetAttributeValue("failed", failedCount);
|
||||
assembly.SetAttributeValue("skipped", skippedCount);
|
||||
|
||||
assembly.SetAttributeValue("time", (int)testPass.TestPassExecutionTime.TotalSeconds);
|
||||
assembly.SetAttributeValue("errors", 0);
|
||||
root.Add(assembly);
|
||||
|
||||
var collection = new XElement("collection");
|
||||
collection.SetAttributeValue("total", resultCount);
|
||||
collection.SetAttributeValue("passed", passedCount);
|
||||
collection.SetAttributeValue("failed", failedCount);
|
||||
collection.SetAttributeValue("skipped", skippedCount);
|
||||
collection.SetAttributeValue("name", "Test collection");
|
||||
collection.SetAttributeValue("time", (int)testPass.TestPassExecutionTime.TotalSeconds);
|
||||
assembly.Add(collection);
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
var test = new XElement("test");
|
||||
test.SetAttributeValue("name", testNamePrefix + "." + result.Name);
|
||||
|
||||
var className = GetTestClassName(result.Name);
|
||||
var methodName = GetTestMethodName(result.Name);
|
||||
test.SetAttributeValue("type", className);
|
||||
test.SetAttributeValue("method", methodName);
|
||||
|
||||
test.SetAttributeValue("time", result.ExecutionTime.TotalSeconds);
|
||||
|
||||
string resultString = string.Empty;
|
||||
|
||||
if (result.Passed)
|
||||
{
|
||||
resultString = "Pass";
|
||||
}
|
||||
else if(result.PassedOrUnreliable(requiredPassRateThreshold))
|
||||
{
|
||||
resultString = "Skip";
|
||||
}
|
||||
else
|
||||
{
|
||||
resultString = "Fail";
|
||||
}
|
||||
|
||||
|
||||
test.SetAttributeValue("result", resultString);
|
||||
|
||||
if (!result.Passed)
|
||||
{
|
||||
// If a test failed, we'll have rerun it multiple times.
|
||||
// We'll save the subresults to a JSON text file that we'll upload to the helix results container -
|
||||
// this allows it to be as long as we want, whereas the reason field in Azure DevOps has a 4000 character limit.
|
||||
string subResultsFileName = methodName + "_subresults.json";
|
||||
string subResultsFilePath = Path.Combine(Path.GetDirectoryName(wttInputPath), subResultsFileName);
|
||||
|
||||
if (result.PassedOrUnreliable(requiredPassRateThreshold))
|
||||
{
|
||||
var reason = new XElement("reason");
|
||||
reason.Add(new XCData(GetUploadedFileUrl(subResultsFileName, helixResultsContainerUri, helixResultsContainerRsas)));
|
||||
test.Add(reason);
|
||||
}
|
||||
else
|
||||
{
|
||||
var failure = new XElement("failure");
|
||||
var message = new XElement("message");
|
||||
message.Add(new XCData(GetUploadedFileUrl(subResultsFileName, helixResultsContainerUri, helixResultsContainerRsas)));
|
||||
failure.Add(message);
|
||||
test.Add(failure);
|
||||
}
|
||||
}
|
||||
collection.Add(test);
|
||||
}
|
||||
|
||||
File.WriteAllText(xunitOutputPath, root.ToString());
|
||||
}
|
||||
|
||||
private JsonSerializableTestResult ConvertToSerializableResult(TestResult rerunResult, string[] uniqueErrors)
|
||||
{
|
||||
var serializableResult = new JsonSerializableTestResult();
|
||||
|
||||
serializableResult.outcome = rerunResult.Passed ? "Passed" : "Failed";
|
||||
serializableResult.duration = (int)Math.Round(rerunResult.ExecutionTime.TotalMilliseconds);
|
||||
|
||||
if (!rerunResult.Passed)
|
||||
{
|
||||
serializableResult.log = Path.GetFileName(rerunResult.SourceWttFile);
|
||||
|
||||
if (rerunResult.Screenshots.Any())
|
||||
{
|
||||
List<string> screenshots = new List<string>();
|
||||
|
||||
foreach (var screenshot in rerunResult.Screenshots)
|
||||
{
|
||||
screenshots.Add(Path.GetFileName(screenshot));
|
||||
}
|
||||
|
||||
serializableResult.screenshots = screenshots.ToArray();
|
||||
}
|
||||
|
||||
// To conserve space, we'll log the index of the error to index in a list of unique errors rather than
|
||||
// jotting down every single error in its entirety. We'll add one to the result so we can avoid
|
||||
// serializing this property when it has the default value of 0.
|
||||
serializableResult.errorIndex = Array.IndexOf(uniqueErrors, rerunResult.Details) + 1;
|
||||
}
|
||||
|
||||
return serializableResult;
|
||||
}
|
||||
|
||||
private string GetUploadedFileUrl(string filePath, string helixResultsContainerUri, string helixResultsContainerRsas)
|
||||
{
|
||||
var filename = Path.GetFileName(filePath);
|
||||
return string.Format("{0}/{1}{2}", helixResultsContainerUri, filename, helixResultsContainerRsas);
|
||||
}
|
||||
|
||||
private string GetTestNameSeparator(string testname)
|
||||
{
|
||||
var separatorString = ".";
|
||||
if (!testname.Contains(separatorString))
|
||||
{
|
||||
separatorString = "::";
|
||||
}
|
||||
return separatorString;
|
||||
}
|
||||
|
||||
private string GetTestMethodName(string fullyQualifiedName)
|
||||
{
|
||||
var separatorString = GetTestNameSeparator(fullyQualifiedName);
|
||||
var methodName = fullyQualifiedName.Substring(fullyQualifiedName.LastIndexOf(separatorString) + separatorString.Length);
|
||||
|
||||
return methodName;
|
||||
}
|
||||
|
||||
private string GetTestClassName(string fullyQualifiedName)
|
||||
{
|
||||
var separatorString = GetTestNameSeparator(fullyQualifiedName);
|
||||
var className = fullyQualifiedName.Substring(0, fullyQualifiedName.LastIndexOf(separatorString));
|
||||
|
||||
return className;
|
||||
}
|
||||
}
|
||||
}
|
12
build/Helix/InstallTestAppDependencies.ps1
Normal file
12
build/Helix/InstallTestAppDependencies.ps1
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Displaying progress is unnecessary and is just distracting.
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
$dependencyFiles = Get-ChildItem -Filter "*Microsoft.VCLibs.*.appx"
|
||||
|
||||
foreach ($file in $dependencyFiles)
|
||||
{
|
||||
Write-Host "Adding dependency $($file)..."
|
||||
|
||||
Add-AppxPackage $file
|
||||
|
||||
}
|
8
build/Helix/OutputFailedTestQuery.ps1
Normal file
8
build/Helix/OutputFailedTestQuery.ps1
Normal file
|
@ -0,0 +1,8 @@
|
|||
Param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WttInputPath
|
||||
)
|
||||
|
||||
Add-Type -Language CSharp -ReferencedAssemblies System.Xml,System.Xml.Linq,System.Runtime.Serialization,System.Runtime.Serialization.Json (Get-Content $PSScriptRoot\HelixTestHelpers.cs -Raw)
|
||||
|
||||
[HelixTestHelpers.FailedTestDetector]::OutputFailedTestQuery($WttInputPath)
|
32
build/Helix/OutputSubResultsJsonFiles.ps1
Normal file
32
build/Helix/OutputSubResultsJsonFiles.ps1
Normal file
|
@ -0,0 +1,32 @@
|
|||
Param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WttInputPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WttSingleRerunInputPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WttMultipleRerunInputPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TestNamePrefix
|
||||
)
|
||||
|
||||
# Ideally these would be passed as parameters to the script. However ps makes it difficult to deal with string literals containing '&', so we just
|
||||
# read the values directly from the environment variables
|
||||
$helixResultsContainerUri = $Env:HELIX_RESULTS_CONTAINER_URI
|
||||
$helixResultsContainerRsas = $Env:HELIX_RESULTS_CONTAINER_RSAS
|
||||
|
||||
Add-Type -Language CSharp -ReferencedAssemblies System.Xml,System.Xml.Linq,System.Runtime.Serialization,System.Runtime.Serialization.Json (Get-Content $PSScriptRoot\HelixTestHelpers.cs -Raw)
|
||||
|
||||
$testResultParser = [HelixTestHelpers.TestResultParser]::new($TestNamePrefix, $helixResultsContainerUri, $helixResultsContainerRsas)
|
||||
[System.Collections.Generic.Dictionary[string, string]]$subResultsJsonByMethodName = $testResultParser.GetSubResultsJsonByMethodName($WttInputPath, $WttSingleRerunInputPath, $WttMultipleRerunInputPath)
|
||||
|
||||
$subResultsJsonDirectory = [System.IO.Path]::GetDirectoryName($WttInputPath)
|
||||
|
||||
foreach ($methodName in $subResultsJsonByMethodName.Keys)
|
||||
{
|
||||
$subResultsJson = $subResultsJsonByMethodName[$methodName]
|
||||
$subResultsJsonPath = [System.IO.Path]::Combine($subResultsJsonDirectory, $methodName + "_subresults.json")
|
||||
Out-File $subResultsJsonPath -Encoding utf8 -InputObject $subResultsJson
|
||||
}
|
131
build/Helix/OutputTestResults.ps1
Normal file
131
build/Helix/OutputTestResults.ps1
Normal file
|
@ -0,0 +1,131 @@
|
|||
Param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]$MinimumExpectedTestsExecutedCount,
|
||||
|
||||
[string]$AccessToken = $env:SYSTEM_ACCESSTOKEN,
|
||||
[string]$CollectionUri = $env:SYSTEM_COLLECTIONURI,
|
||||
[string]$TeamProject = $env:SYSTEM_TEAMPROJECT,
|
||||
[string]$BuildUri = $env:BUILD_BUILDURI,
|
||||
[bool]$CheckJobAttempt
|
||||
)
|
||||
|
||||
$azureDevOpsRestApiHeaders = @{
|
||||
"Accept"="application/json"
|
||||
"Authorization"="Basic $([System.Convert]::ToBase64String([System.Text.ASCIIEncoding]::ASCII.GetBytes(":$AccessToken")))"
|
||||
}
|
||||
|
||||
. "$PSScriptRoot/AzurePipelinesHelperScripts.ps1"
|
||||
|
||||
Write-Host "Checking test results..."
|
||||
|
||||
$queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri -IncludeRunDetails
|
||||
Write-Host "queryUri = $queryUri"
|
||||
|
||||
$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
[System.Collections.Generic.List[string]]$failingTests = @()
|
||||
[System.Collections.Generic.List[string]]$unreliableTests = @()
|
||||
[System.Collections.Generic.List[string]]$unexpectedResultTest = @()
|
||||
|
||||
[System.Collections.Generic.List[string]]$namesOfProcessedTestRuns = @()
|
||||
$totalTestsExecutedCount = 0
|
||||
|
||||
# We assume that we only have one testRun with a given name that we care about
|
||||
# We only process the last testRun with a given name (based on completedDate)
|
||||
# The name of a testRun is set to the Helix queue that it was run on (e.g. windows.10.amd64.client19h1.xaml)
|
||||
# If we have multiple test runs on the same queue that we care about, we will need to re-visit this logic
|
||||
foreach ($testRun in ($testRuns.value | Sort-Object -Property "completedDate" -Descending))
|
||||
{
|
||||
if ($CheckJobAttempt)
|
||||
{
|
||||
if ($namesOfProcessedTestRuns -contains $testRun.name)
|
||||
{
|
||||
Write-Host "Skipping test run '$($testRun.name)', since we have already processed a test run of that name."
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Processing results from test run '$($testRun.name)'"
|
||||
$namesOfProcessedTestRuns.Add($testRun.name)
|
||||
|
||||
$totalTestsExecutedCount += $testRun.totalTests
|
||||
|
||||
$testRunResultsUri = "$($testRun.url)/results?api-version=5.0"
|
||||
$testResults = Invoke-RestMethod -Uri "$($testRun.url)/results?api-version=5.0" -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
|
||||
foreach ($testResult in $testResults.value)
|
||||
{
|
||||
$shortTestCaseTitle = $testResult.testCaseTitle -replace "[a-zA-Z0-9]+.[a-zA-Z0-9]+.Windows.UI.Xaml.Tests.MUXControls.",""
|
||||
|
||||
if ($testResult.outcome -eq "Failed")
|
||||
{
|
||||
if (-not $failingTests.Contains($shortTestCaseTitle))
|
||||
{
|
||||
$failingTests.Add($shortTestCaseTitle)
|
||||
}
|
||||
}
|
||||
elseif ($testResult.outcome -eq "Warning")
|
||||
{
|
||||
if (-not $unreliableTests.Contains($shortTestCaseTitle))
|
||||
{
|
||||
$unreliableTests.Add($shortTestCaseTitle)
|
||||
}
|
||||
}
|
||||
elseif ($testResult.outcome -ne "Passed")
|
||||
{
|
||||
# We should only see tests with result "Passed", "Failed" or "Warning"
|
||||
if (-not $unexpectedResultTest.Contains($shortTestCaseTitle))
|
||||
{
|
||||
$unexpectedResultTest.Add($shortTestCaseTitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($unreliableTests.Count -gt 0)
|
||||
{
|
||||
Write-Host @"
|
||||
##vso[task.logissue type=warning;]Unreliable tests:
|
||||
##vso[task.logissue type=warning;]$($unreliableTests -join "$([Environment]::NewLine)##vso[task.logissue type=warning;]")
|
||||
|
||||
"@
|
||||
}
|
||||
|
||||
if ($failingTests.Count -gt 0)
|
||||
{
|
||||
Write-Host @"
|
||||
##vso[task.logissue type=error;]Failing tests:
|
||||
##vso[task.logissue type=error;]$($failingTests -join "$([Environment]::NewLine)##vso[task.logissue type=error;]")
|
||||
|
||||
"@
|
||||
}
|
||||
|
||||
if ($unexpectedResultTest.Count -gt 0)
|
||||
{
|
||||
Write-Host @"
|
||||
##vso[task.logissue type=error;]Tests with unexpected results:
|
||||
##vso[task.logissue type=error;]$($unexpectedResultTest -join "$([Environment]::NewLine)##vso[task.logissue type=error;]")
|
||||
|
||||
"@
|
||||
}
|
||||
|
||||
if($totalTestsExecutedCount -lt $MinimumExpectedTestsExecutedCount)
|
||||
{
|
||||
Write-Host "Expected at least $MinimumExpectedTestsExecutedCount tests to be executed."
|
||||
Write-Host "Actual executed test count is: $totalTestsExecutedCount"
|
||||
Write-Host "##vso[task.complete result=Failed;]"
|
||||
}
|
||||
elseif ($failingTests.Count -gt 0)
|
||||
{
|
||||
Write-Host "At least one test failed."
|
||||
Write-Host "##vso[task.complete result=Failed;]"
|
||||
}
|
||||
elseif ($unreliableTests.Count -gt 0)
|
||||
{
|
||||
Write-Host "All tests eventually passed, but some initially failed."
|
||||
Write-Host "##vso[task.complete result=Succeeded;]"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "All tests passed."
|
||||
Write-Host "##vso[task.complete result=Succeeded;]"
|
||||
}
|
54
build/Helix/PrepareHelixPayload.ps1
Normal file
54
build/Helix/PrepareHelixPayload.ps1
Normal file
|
@ -0,0 +1,54 @@
|
|||
[CmdLetBinding()]
|
||||
Param(
|
||||
[string]$Platform,
|
||||
[string]$Configuration,
|
||||
[string]$ArtifactName='drop'
|
||||
)
|
||||
|
||||
$payloadDir = "HelixPayload\$Configuration\$Platform"
|
||||
|
||||
$repoDirectory = Join-Path (Split-Path -Parent $script:MyInvocation.MyCommand.Path) "..\..\"
|
||||
$nugetPackagesDir = Join-Path (Split-Path -Parent $script:MyInvocation.MyCommand.Path) "packages"
|
||||
|
||||
# Create the payload directory. Remove it if it already exists.
|
||||
If(test-path $payloadDir)
|
||||
{
|
||||
Remove-Item $payloadDir -Recurse
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path $payloadDir
|
||||
|
||||
# Copy files from nuget packages
|
||||
Copy-Item "$nugetPackagesDir\microsoft.windows.apps.test.1.0.181203002\lib\netcoreapp2.1\*.dll" $payloadDir
|
||||
Copy-Item "$nugetPackagesDir\taef.redist.wlk.10.57.200731005-develop\build\Binaries\$Platform\*" $payloadDir
|
||||
Copy-Item "$nugetPackagesDir\taef.redist.wlk.10.57.200731005-develop\build\Binaries\$Platform\CoreClr\*" $payloadDir
|
||||
New-Item -ItemType Directory -Force -Path "$payloadDir\.NETCoreApp2.1\"
|
||||
Copy-Item "$nugetPackagesDir\runtime.win-$Platform.microsoft.netcore.app.2.1.0\runtimes\win-$Platform\lib\netcoreapp2.1\*" "$payloadDir\.NETCoreApp2.1\"
|
||||
Copy-Item "$nugetPackagesDir\runtime.win-$Platform.microsoft.netcore.app.2.1.0\runtimes\win-$Platform\native\*" "$payloadDir\.NETCoreApp2.1\"
|
||||
|
||||
function Copy-If-Exists
|
||||
{
|
||||
Param($source, $destinationDir)
|
||||
|
||||
if (Test-Path $source)
|
||||
{
|
||||
Write-Host "Copy from '$source' to '$destinationDir'"
|
||||
Copy-Item -Force $source $destinationDir
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "'$source' does not exist."
|
||||
}
|
||||
}
|
||||
|
||||
# Copy files from the 'drop' artifact dir
|
||||
Copy-Item "$repoDirectory\Artifacts\$ArtifactName\$Configuration\$Platform\Test\*" $payloadDir -Recurse
|
||||
|
||||
# Copy files from the repo
|
||||
New-Item -ItemType Directory -Force -Path "$payloadDir"
|
||||
Copy-Item "build\helix\ConvertWttLogToXUnit.ps1" "$payloadDir"
|
||||
Copy-Item "build\helix\OutputFailedTestQuery.ps1" "$payloadDir"
|
||||
Copy-Item "build\helix\OutputSubResultsJsonFiles.ps1" "$payloadDir"
|
||||
Copy-Item "build\helix\HelixTestHelpers.cs" "$payloadDir"
|
||||
Copy-Item "build\helix\runtests.cmd" $payloadDir
|
||||
Copy-Item "build\helix\InstallTestAppDependencies.ps1" "$payloadDir"
|
||||
Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir"
|
112
build/Helix/ProcessHelixFiles.ps1
Normal file
112
build/Helix/ProcessHelixFiles.ps1
Normal file
|
@ -0,0 +1,112 @@
|
|||
Param(
|
||||
[string]$AccessToken = $env:SYSTEM_ACCESSTOKEN,
|
||||
[string]$HelixAccessToken = $env:HelixAccessToken,
|
||||
[string]$CollectionUri = $env:SYSTEM_COLLECTIONURI,
|
||||
[string]$TeamProject = $env:SYSTEM_TEAMPROJECT,
|
||||
[string]$BuildUri = $env:BUILD_BUILDURI,
|
||||
[string]$OutputFolder = "HelixOutput"
|
||||
)
|
||||
|
||||
$helixLinkFile = "$OutputFolder\LinksToHelixTestFiles.html"
|
||||
|
||||
$accessTokenParam = ""
|
||||
if($HelixAccessToken)
|
||||
{
|
||||
$accessTokenParam = "?access_token=$HelixAccessToken"
|
||||
}
|
||||
|
||||
function Generate-File-Links
|
||||
{
|
||||
Param ([Array[]]$files,[string]$sectionName)
|
||||
if($files.Count -gt 0)
|
||||
{
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<div class=$sectionName>"
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<h4>$sectionName</h4>"
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<ul>"
|
||||
foreach($file in $files)
|
||||
{
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<li><a href=$($file.Link)>$($file.Name)</a></li>"
|
||||
}
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "</ul>"
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "</div>"
|
||||
}
|
||||
}
|
||||
|
||||
#Create output directory
|
||||
New-Item $OutputFolder -ItemType Directory
|
||||
|
||||
$azureDevOpsRestApiHeaders = @{
|
||||
"Accept"="application/json"
|
||||
"Authorization"="Basic $([System.Convert]::ToBase64String([System.Text.ASCIIEncoding]::ASCII.GetBytes(":$AccessToken")))"
|
||||
}
|
||||
|
||||
. "$PSScriptRoot/AzurePipelinesHelperScripts.ps1"
|
||||
|
||||
$queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri -IncludeRunDetails
|
||||
Write-Host "queryUri = $queryUri"
|
||||
|
||||
$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
[System.Collections.Generic.List[string]]$workItems = @()
|
||||
|
||||
foreach ($testRun in $testRuns.value)
|
||||
{
|
||||
$testResults = Invoke-RestMethod -Uri "$($testRun.url)/results?api-version=5.0" -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
$isTestRunNameShown = $false
|
||||
|
||||
foreach ($testResult in $testResults.value)
|
||||
{
|
||||
if ("comment" -in $testResult)
|
||||
{
|
||||
$info = ConvertFrom-Json $testResult.comment
|
||||
$helixJobId = $info.HelixJobId
|
||||
$helixWorkItemName = $info.HelixWorkItemName
|
||||
|
||||
$workItem = "$helixJobId-$helixWorkItemName"
|
||||
|
||||
if (-not $workItems.Contains($workItem))
|
||||
{
|
||||
$workItems.Add($workItem)
|
||||
$filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files$accessTokenParam"
|
||||
$files = Invoke-RestMethod -Uri $filesQueryUri -Method Get
|
||||
|
||||
$screenShots = $files | where { $_.Name.EndsWith(".jpg") }
|
||||
$dumps = $files | where { $_.Name.EndsWith(".dmp") }
|
||||
$pgcFiles = $files | where { $_.Name.EndsWith(".pgc") }
|
||||
if ($screenShots.Count + $dumps.Count + $pgcFiles.Count -gt 0)
|
||||
{
|
||||
if(-Not $isTestRunNameShown)
|
||||
{
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<h2>$($testRun.name)</h2>"
|
||||
$isTestRunNameShown = $true
|
||||
}
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<h3>$helixWorkItemName</h3>"
|
||||
Generate-File-Links $screenShots "Screenshots"
|
||||
Generate-File-Links $dumps "CrashDumps"
|
||||
Generate-File-Links $pgcFiles "PGC files"
|
||||
$misc = $files | where { ($screenShots -NotContains $_) -And ($dumps -NotContains $_) -And ($visualTreeVerificationFiles -NotContains $_) -And ($pgcFiles -NotContains $_) }
|
||||
Generate-File-Links $misc "Misc"
|
||||
|
||||
foreach($pgcFile in $pgcFiles)
|
||||
{
|
||||
$flavorPath = $pgcFile.Name.Split('.')[0]
|
||||
$archPath = $pgcFile.Name.Split('.')[1]
|
||||
$fileName = $pgcFile.Name.Remove(0, $flavorPath.length + $archPath.length + 2)
|
||||
$fullPath = "$OutputFolder\PGO\$flavorPath\$archPath"
|
||||
$destination = "$fullPath\$fileName"
|
||||
|
||||
Write-Host "Copying $($pgcFile.Name) to $destination"
|
||||
|
||||
if (-Not (Test-Path $fullPath))
|
||||
{
|
||||
New-Item $fullPath -ItemType Directory
|
||||
}
|
||||
|
||||
$link = "$($pgcFile.Link)$accessTokenParam"
|
||||
$webClient.DownloadFile($link, $destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
build/Helix/RunTestsInHelix.proj
Normal file
18
build/Helix/RunTestsInHelix.proj
Normal file
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.DotNet.Helix.Sdk" DefaultTargets="Test">
|
||||
<PropertyGroup>
|
||||
<HelixSource>pr/terminal/$(BUILD_SOURCEBRANCH)/</HelixSource>
|
||||
<EnableXUnitReporter>true</EnableXUnitReporter>
|
||||
<EnableAzurePipelinesReporter>true</EnableAzurePipelinesReporter>
|
||||
<FailOnMissionControlTestFailure>true</FailOnMissionControlTestFailure>
|
||||
<HelixPreCommands>$(HelixPreCommands);set testnameprefix=$(Configuration).$(Platform);set testbuildplatform=$(Platform);set rerunPassesRequiredToAvoidFailure=$(rerunPassesRequiredToAvoidFailure)</HelixPreCommands>
|
||||
<OutputPath>..\..\bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<HelixCorrelationPayload Include="..\..\HelixPayload\$(Configuration)\$(Platform)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- These .proj files are generated by the build machine prior to running tests via GenerateTestProjFile.ps1. -->
|
||||
<Import Project="$(ProjFilesPath)\RunTestsInHelix-TerminalAppLocalTests.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
|
||||
<Import Project="$(ProjFilesPath)\RunTestsInHelix-HostTestsUIA.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
|
||||
</Project>
|
135
build/Helix/UpdateUnreliableTests.ps1
Normal file
135
build/Helix/UpdateUnreliableTests.ps1
Normal file
|
@ -0,0 +1,135 @@
|
|||
[CmdLetBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]$RerunPassesRequiredToAvoidFailure,
|
||||
|
||||
[string]$AccessToken = $env:SYSTEM_ACCESSTOKEN,
|
||||
[string]$CollectionUri = $env:SYSTEM_COLLECTIONURI,
|
||||
[string]$TeamProject = $env:SYSTEM_TEAMPROJECT,
|
||||
[string]$BuildUri = $env:BUILD_BUILDURI
|
||||
)
|
||||
|
||||
. "$PSScriptRoot/AzurePipelinesHelperScripts.ps1"
|
||||
|
||||
|
||||
$azureDevOpsRestApiHeaders = @{
|
||||
"Accept"="application/json"
|
||||
"Authorization"="Basic $([System.Convert]::ToBase64String([System.Text.ASCIIEncoding]::ASCII.GetBytes(":$AccessToken")))"
|
||||
}
|
||||
|
||||
$queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri
|
||||
Write-Host "queryUri = $queryUri"
|
||||
|
||||
# To account for unreliable tests, we'll iterate through all of the tests associated with this build, check to see any tests that were unreliable
|
||||
# (denoted by being marked as "skipped"), and if so, we'll instead mark those tests with a warning and enumerate all of the attempted runs
|
||||
# with their pass/fail states as well as any relevant error messages for failed attempts.
|
||||
$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
|
||||
$timesSeenByRunName = @{}
|
||||
|
||||
foreach ($testRun in $testRuns.value)
|
||||
{
|
||||
$testRunResultsUri = "$($testRun.url)/results?api-version=5.0"
|
||||
|
||||
Write-Host "Marking test run `"$($testRun.name)`" as in progress so we can change its results to account for unreliable tests."
|
||||
Invoke-RestMethod -Uri "$($testRun.url)?api-version=5.0" -Method Patch -Body (ConvertTo-Json @{ "state" = "InProgress" }) -Headers $azureDevOpsRestApiHeaders -ContentType "application/json" | Out-Null
|
||||
|
||||
Write-Host "Retrieving test results..."
|
||||
$testResults = Invoke-RestMethod -Uri $testRunResultsUri -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
|
||||
foreach ($testResult in $testResults.value)
|
||||
{
|
||||
$testNeedsSubResultProcessing = $false
|
||||
if ($testResult.outcome -eq "NotExecuted")
|
||||
{
|
||||
$testNeedsSubResultProcessing = $true
|
||||
}
|
||||
elseif($testResult.outcome -eq "Failed")
|
||||
{
|
||||
$testNeedsSubResultProcessing = $testResult.errorMessage -like "*_subresults.json*"
|
||||
}
|
||||
|
||||
if ($testNeedsSubResultProcessing)
|
||||
{
|
||||
Write-Host " Test $($testResult.testCaseTitle) was detected as unreliable. Updating..."
|
||||
|
||||
# The errorMessage field contains a link to the JSON-encoded rerun result data.
|
||||
$rerunResults = ConvertFrom-Json (New-Object System.Net.WebClient).DownloadString($testResult.errorMessage)
|
||||
[System.Collections.Generic.List[System.Collections.Hashtable]]$rerunDataList = @()
|
||||
$attemptCount = 0
|
||||
$passCount = 0
|
||||
$totalDuration = 0
|
||||
|
||||
foreach ($rerun in $rerunResults.results)
|
||||
{
|
||||
$rerunData = @{
|
||||
"displayName" = "Attempt #$($attemptCount + 1) - $($testResult.testCaseTitle)";
|
||||
"durationInMs" = $rerun.duration;
|
||||
"outcome" = $rerun.outcome;
|
||||
}
|
||||
|
||||
if ($rerun.outcome -eq "Passed")
|
||||
{
|
||||
$passCount++
|
||||
}
|
||||
|
||||
if ($attemptCount -gt 0)
|
||||
{
|
||||
$rerunData["sequenceId"] = $attemptCount
|
||||
}
|
||||
|
||||
Write-Host " Attempt #$($attemptCount + 1): $($rerun.outcome)"
|
||||
|
||||
if ($rerun.outcome -ne "Passed")
|
||||
{
|
||||
$screenshots = "$($rerunResults.blobPrefix)/$($rerun.screenshots -join @"
|
||||
$($rerunResults.blobSuffix)
|
||||
$($rerunResults.blobPrefix)
|
||||
"@)$($rerunResults.blobSuffix)"
|
||||
|
||||
# We subtract 1 from the error index because we added 1 so we could use 0
|
||||
# as a default value not injected into the JSON in order to keep its size down.
|
||||
# We did this because there's a maximum size enforced for the errorMessage parameter
|
||||
# in the Azure DevOps REST API.
|
||||
$fullErrorMessage = @"
|
||||
Log: $($rerunResults.blobPrefix)/$($rerun.log)$($rerunResults.blobSuffix)
|
||||
|
||||
Screenshots:
|
||||
$screenshots
|
||||
|
||||
Error log:
|
||||
$($rerunResults.errors[$rerun.errorIndex - 1])
|
||||
"@
|
||||
|
||||
$rerunData["errorMessage"] = $fullErrorMessage
|
||||
}
|
||||
|
||||
$attemptCount++
|
||||
$totalDuration += $rerun.duration
|
||||
$rerunDataList.Add($rerunData)
|
||||
}
|
||||
|
||||
$overallOutcome = "Warning"
|
||||
|
||||
if ($attemptCount -eq 2)
|
||||
{
|
||||
Write-Host " Test $($testResult.testCaseTitle) passed on the immediate rerun, so we'll mark it as unreliable."
|
||||
}
|
||||
elseif ($passCount -gt $RerunPassesRequiredToAvoidFailure)
|
||||
{
|
||||
Write-Host " Test $($testResult.testCaseTitle) passed on $passCount of $attemptCount attempts, which is greater than or equal to the $RerunPassesRequiredToAvoidFailure passes required to avoid being marked as failed. Marking as unreliable."
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host " Test $($testResult.testCaseTitle) passed on only $passCount of $attemptCount attempts, which is less than the $RerunPassesRequiredToAvoidFailure passes required to avoid being marked as failed. Marking as failed."
|
||||
$overallOutcome = "Failed"
|
||||
}
|
||||
|
||||
$updateBody = ConvertTo-Json @(@{ "id" = $testResult.id; "outcome" = $overallOutcome; "errorMessage" = " "; "durationInMs" = $totalDuration; "subResults" = $rerunDataList; "resultGroupType" = "rerun" }) -Depth 5
|
||||
Invoke-RestMethod -Uri $testRunResultsUri -Method Patch -Headers $azureDevOpsRestApiHeaders -Body $updateBody -ContentType "application/json" | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Finished updates. Re-marking test run `"$($testRun.name)`" as completed."
|
||||
Invoke-RestMethod -Uri "$($testRun.url)?api-version=5.0" -Method Patch -Body (ConvertTo-Json @{ "state" = "Completed" }) -Headers $azureDevOpsRestApiHeaders -ContentType "application/json" | Out-Null
|
||||
}
|
5
build/Helix/global.json
Normal file
5
build/Helix/global.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.20277.5"
|
||||
}
|
||||
}
|
8
build/Helix/packages.config
Normal file
8
build/Helix/packages.config
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
|
||||
<package id="TAEF.Redist.Wlk" version="10.57.200731005-develop" targetFramework="native" />
|
||||
<package id="microsoft.windows.apps.test" version="1.0.181203002" targetFramework="native" />
|
||||
<package id="runtime.win-x86.microsoft.netcore.app" version="2.1.0" targetFramework="native" />
|
||||
<package id="runtime.win-x64.microsoft.netcore.app" version="2.1.0" targetFramework="native" />
|
||||
</packages>
|
32
build/Helix/readme.md
Normal file
32
build/Helix/readme.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
This directory contains code and configuration files to run WinUI tests in Helix.
|
||||
|
||||
Helix is a cloud hosted test execution environment which is accessed via the Arcade SDK.
|
||||
More details:
|
||||
* [Arcade](https://github.com/dotnet/arcade)
|
||||
* [Helix](https://github.com/dotnet/arcade/tree/master/src/Microsoft.DotNet.Helix/Sdk)
|
||||
|
||||
WinUI tests are scheduled in Helix by the Azure DevOps Pipeline: [RunHelixTests.yml](../RunHelixTests.yml).
|
||||
|
||||
The workflow is as follows:
|
||||
1. NuGet Restore is called on the packages.config in this directory. This downloads any runtime dependencies
|
||||
that are needed to run tests.
|
||||
2. PrepareHelixPayload.ps1 is called. This copies the necessary files from various locations into a Helix
|
||||
payload directory. This directory is what will get sent to the Helix machines.
|
||||
3. RunTestsInHelix.proj is executed. This proj has a dependency on
|
||||
[Microsoft.DotNet.Helix.Sdk](https://github.com/dotnet/arcade/tree/master/src/Microsoft.DotNet.Helix/Sdk)
|
||||
which it uses to publish the Helix payload directory and to schedule the Helix Work Items. The WinUI tests
|
||||
are parallelized into multiple Helix Work Items.
|
||||
4. Each Helix Work Item calls [runtests.cmd](runtests.cmd) with a specific query to pass to
|
||||
[TAEF](https://docs.microsoft.com/en-us/windows-hardware/drivers/taef/) which runs the tests.
|
||||
5. If a test is detected to have failed, we run it again, first once, then eight more times if it fails again.
|
||||
If it fails all ten times, we report the test as failed; otherwise, we report it as unreliable,
|
||||
which will show up as a warning, but which will not fail the build. When a test is reported as unreliable,
|
||||
we include the results for each individual run via a JSON string in the original test's errorMessage field.
|
||||
6. TAEF produces logs in WTT format. Helix is able to process logs in XUnit format. We run
|
||||
[ConvertWttLogToXUnit.ps1](ConvertWttLogToXUnit.ps1) to convert the logs into the necessary format.
|
||||
7. RunTestsInHelix.proj has EnableAzurePipelinesReporter set to true. This allows the XUnit formatted test
|
||||
results to be reported back to the Azure DevOps Pipeline.
|
||||
8. We process unreliable tests once all tests have been reported by reading the JSON string from the
|
||||
errorMessage field and calling the Azure DevOps REST API to modify the unreliable tests to have sub-results
|
||||
added to the test and to mark the test as "warning", which will enable people to see exactly how the test
|
||||
failed in runs where it did.
|
106
build/Helix/runtests.cmd
Normal file
106
build/Helix/runtests.cmd
Normal file
|
@ -0,0 +1,106 @@
|
|||
setlocal ENABLEDELAYEDEXPANSION
|
||||
|
||||
echo %TIME%
|
||||
|
||||
robocopy %HELIX_CORRELATION_PAYLOAD% . /s /NP > NUL
|
||||
|
||||
echo %TIME%
|
||||
|
||||
reg add HKLM\Software\Policies\Microsoft\Windows\Appx /v AllowAllTrustedApps /t REG_DWORD /d 1 /f
|
||||
|
||||
rem enable dump collection for our test apps:
|
||||
rem note, this script is run from a 32-bit cmd, but we need to set the native reg-key
|
||||
FOR %%A IN (TestHostApp.exe,te.exe,te.processhost.exe,conhost.exe,OpenConsole.exe,WindowsTerminal.exe) DO (
|
||||
%systemroot%\sysnative\cmd.exe /c reg add "HKLM\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\%%A" /v DumpFolder /t REG_EXPAND_SZ /d %HELIX_DUMP_FOLDER% /f
|
||||
%systemroot%\sysnative\cmd.exe /c reg add "HKLM\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\%%A" /v DumpType /t REG_DWORD /d 2 /f
|
||||
%systemroot%\sysnative\cmd.exe /c reg add "HKLM\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\%%A" /v DumpCount /t REG_DWORD /d 10 /f
|
||||
)
|
||||
|
||||
echo %TIME%
|
||||
|
||||
:: kill dhandler, which is a tool designed to handle unexpected windows appearing. But since our tests are
|
||||
:: expected to show UI we don't want it running.
|
||||
taskkill -f -im dhandler.exe
|
||||
|
||||
echo %TIME%
|
||||
powershell -ExecutionPolicy Bypass .\EnsureMachineState.ps1
|
||||
echo %TIME%
|
||||
powershell -ExecutionPolicy Bypass .\InstallTestAppDependencies.ps1
|
||||
echo %TIME%
|
||||
|
||||
set testBinaryCandidates=TerminalApp.LocalTests.dll Conhost.UIA.Tests.dll
|
||||
set testBinaries=
|
||||
for %%B in (%testBinaryCandidates%) do (
|
||||
if exist %%B (
|
||||
set "testBinaries=!testBinaries! %%B"
|
||||
)
|
||||
)
|
||||
|
||||
echo %TIME%
|
||||
te.exe %testBinaries% /enablewttlogging /unicodeOutput:false /sessionTimeout:0:15 /testtimeout:0:10 /screenCaptureOnError %*
|
||||
echo %TIME%
|
||||
|
||||
powershell -ExecutionPolicy Bypass Get-Process
|
||||
|
||||
move te.wtl te_original.wtl
|
||||
|
||||
copy /y te_original.wtl %HELIX_WORKITEM_UPLOAD_ROOT%
|
||||
copy /y WexLogFileOutput\*.jpg %HELIX_WORKITEM_UPLOAD_ROOT%
|
||||
for /f "tokens=* delims=" %%a in ('dir /b *.pgc') do ren "%%a" "%testnameprefix%.%%~na.pgc"
|
||||
copy /y *.pgc %HELIX_WORKITEM_UPLOAD_ROOT%
|
||||
|
||||
set FailedTestQuery=
|
||||
for /F "tokens=* usebackq" %%I IN (`powershell -ExecutionPolicy Bypass .\OutputFailedTestQuery.ps1 te_original.wtl`) DO (
|
||||
set FailedTestQuery=%%I
|
||||
)
|
||||
|
||||
rem The first time, we'll just re-run failed tests once. In many cases, tests fail very rarely, such that
|
||||
rem a single re-run will be sufficient to detect many unreliable tests.
|
||||
if "%FailedTestQuery%" == "" goto :SkipReruns
|
||||
|
||||
echo %TIME%
|
||||
te.exe %testBinaries% /enablewttlogging /unicodeOutput:false /sessionTimeout:0:15 /testtimeout:0:10 /screenCaptureOnError /select:"%FailedTestQuery%"
|
||||
echo %TIME%
|
||||
|
||||
move te.wtl te_rerun.wtl
|
||||
|
||||
copy /y te_rerun.wtl %HELIX_WORKITEM_UPLOAD_ROOT%
|
||||
copy /y WexLogFileOutput\*.jpg %HELIX_WORKITEM_UPLOAD_ROOT%
|
||||
|
||||
rem If there are still failing tests remaining, we'll run them eight more times, so they'll have been run a total of ten times.
|
||||
rem If any tests fail all ten times, we can be pretty confident that these are actual test failures rather than unreliable tests.
|
||||
if not exist te_rerun.wtl goto :SkipReruns
|
||||
|
||||
set FailedTestQuery=
|
||||
for /F "tokens=* usebackq" %%I IN (`powershell -ExecutionPolicy Bypass .\OutputFailedTestQuery.ps1 te_rerun.wtl`) DO (
|
||||
set FailedTestQuery=%%I
|
||||
)
|
||||
|
||||
if "%FailedTestQuery%" == "" goto :SkipReruns
|
||||
|
||||
echo %TIME%
|
||||
te.exe %testBinaries% /enablewttlogging /unicodeOutput:false /sessionTimeout:0:15 /testtimeout:0:10 /screenCaptureOnError /testmode:Loop /LoopTest:8 /select:"%FailedTestQuery%"
|
||||
echo %TIME%
|
||||
|
||||
powershell -ExecutionPolicy Bypass Get-Process
|
||||
|
||||
move te.wtl te_rerun_multiple.wtl
|
||||
|
||||
copy /y te_rerun_multiple.wtl %HELIX_WORKITEM_UPLOAD_ROOT%
|
||||
copy /y WexLogFileOutput\*.jpg %HELIX_WORKITEM_UPLOAD_ROOT%
|
||||
powershell -ExecutionPolicy Bypass .\CopyVisualTreeVerificationFiles.ps1
|
||||
|
||||
:SkipReruns
|
||||
|
||||
powershell -ExecutionPolicy Bypass Get-Process
|
||||
|
||||
echo %TIME%
|
||||
powershell -ExecutionPolicy Bypass .\OutputSubResultsJsonFiles.ps1 te_original.wtl te_rerun.wtl te_rerun_multiple.wtl %testnameprefix%
|
||||
powershell -ExecutionPolicy Bypass .\ConvertWttLogToXUnit.ps1 te_original.wtl te_rerun.wtl te_rerun_multiple.wtl testResults.xml %testnameprefix%
|
||||
echo %TIME%
|
||||
|
||||
copy /y *_subresults.json %HELIX_WORKITEM_UPLOAD_ROOT%
|
||||
|
||||
type testResults.xml
|
||||
|
||||
echo %TIME%
|
5
build/packages.config
Normal file
5
build/packages.config
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
|
||||
<package id="TAEF.Redist.Wlk" version="10.57.200731005-develop" targetFramework="native" />
|
||||
</packages>
|
|
@ -2,6 +2,8 @@ parameters:
|
|||
configuration: 'Release'
|
||||
platform: ''
|
||||
additionalBuildArguments: ''
|
||||
minimumExpectedTestsExecutedCount: 10 # Sanity check for minimum expected tests to be reported
|
||||
rerunPassesRequiredToAvoidFailure: 5
|
||||
|
||||
jobs:
|
||||
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
|
@ -15,3 +17,19 @@ jobs:
|
|||
- template: build-console-steps.yml
|
||||
parameters:
|
||||
additionalBuildArguments: ${{ parameters.additionalBuildArguments }}
|
||||
|
||||
- template: helix-runtests-job.yml
|
||||
parameters:
|
||||
name: 'RunTestsInHelix'
|
||||
dependsOn: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
condition: and(succeeded(), and(eq('${{ parameters.platform }}', 'x64'), not(eq(variables['Build.Reason'], 'PullRequest'))))
|
||||
testSuite: 'DevTestSuite'
|
||||
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
|
||||
|
||||
- template: helix-processtestresults-job.yml
|
||||
parameters:
|
||||
dependsOn:
|
||||
- RunTestsInHelix
|
||||
condition: and(succeededOrFailed(), and(eq('${{ parameters.platform }}', 'x64'), not(eq(variables['Build.Reason'], 'PullRequest'))))
|
||||
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
|
||||
minimumExpectedTestsExecutedCount: ${{ parameters.minimumExpectedTestsExecutedCount }}
|
|
@ -1,5 +1,6 @@
|
|||
parameters:
|
||||
additionalBuildArguments: ''
|
||||
testLogPath: '$(Build.BinariesDirectory)\$(BuildPlatform)\$(BuildConfiguration)\testsOnBuildMachine.wtl'
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
|
@ -7,23 +8,29 @@ steps:
|
|||
clean: true
|
||||
|
||||
- task: NuGetToolInstaller@0
|
||||
displayName: Ensure NuGet 4.8.1
|
||||
displayName: 'Use NuGet 5.2.0'
|
||||
inputs:
|
||||
versionSpec: 4.8.1
|
||||
|
||||
- task: VisualStudioTestPlatformInstaller@1
|
||||
displayName: Ensure VSTest Platform
|
||||
versionSpec: 5.2.0
|
||||
|
||||
# In the Microsoft Azure DevOps tenant, NuGetCommand is ambiguous.
|
||||
# This should be `task: NuGetCommand@2`
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: Restore NuGet packages
|
||||
displayName: Restore NuGet packages for solution
|
||||
inputs:
|
||||
command: restore
|
||||
feedsToUse: config
|
||||
configPath: NuGet.config
|
||||
restoreSolution: OpenConsole.sln
|
||||
restoreDirectory: '$(Build.SourcesDirectory)\packages'
|
||||
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: Restore NuGet packages for extraneous build actions
|
||||
inputs:
|
||||
command: restore
|
||||
feedsToUse: config
|
||||
configPath: NuGet.config
|
||||
restoreSolution: build/packages.config
|
||||
restoreDirectory: '$(Build.SourcesDirectory)\packages'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\OpenConsole.sln'
|
||||
|
@ -34,7 +41,7 @@ steps:
|
|||
configuration: '$(BuildConfiguration)'
|
||||
msbuildArgs: "${{ parameters.additionalBuildArguments }}"
|
||||
clean: true
|
||||
maximumCpuCount: true
|
||||
maximumCpuCount: false
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Check MSIX for common regressions'
|
||||
|
@ -66,7 +73,7 @@ steps:
|
|||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
|
||||
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}'
|
||||
condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
|
||||
|
||||
- task: PowerShell@2
|
||||
|
@ -74,9 +81,41 @@ steps:
|
|||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
|
||||
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}'
|
||||
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Convert Test Logs from WTL to xUnit format'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\ConvertWttLogToXUnit.ps1
|
||||
arguments: -WttInputPath '${{ parameters.testLogPath }}' -WttSingleRerunInputPath 'unused.wtl' -WttMultipleRerunInputPath 'unused2.wtl' -XUnitOutputPath 'onBuildMachineResults.xml' -TestNamePrefix '$(BuildConfiguration).$(BuildPlatform)'
|
||||
condition: or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86'))
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: 'Upload converted test logs'
|
||||
inputs:
|
||||
testResultsFormat: 'xUnit' # Options: JUnit, NUnit, VSTest, xUnit, cTest
|
||||
testResultsFiles: '**/onBuildMachineResults.xml'
|
||||
#searchFolder: '$(System.DefaultWorkingDirectory)' # Optional
|
||||
#mergeTestResults: false # Optional
|
||||
#failTaskOnFailedTests: false # Optional
|
||||
testRunTitle: 'On Build Machine Tests' # Optional
|
||||
buildPlatform: $(BuildPlatform) # Optional
|
||||
buildConfiguration: $(BuildConfiguration) # Optional
|
||||
#publishRunAttachments: true # Optional
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy result logs to Artifacts'
|
||||
inputs:
|
||||
Contents: |
|
||||
**/*.wtl
|
||||
**/*onBuildMachineResults.xml
|
||||
${{ parameters.testLogPath }}
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/$(BuildPlatform)/test'
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy *.appx/*.msix to Artifacts (Non-PR builds only)'
|
||||
inputs:
|
||||
|
@ -90,9 +129,22 @@ steps:
|
|||
flattenFolders: true
|
||||
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Artifact (appx) (Non-PR builds only)'
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy outputs needed for test runs to Artifacts'
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)/appx'
|
||||
ArtifactName: 'appx-$(BuildConfiguration)'
|
||||
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
|
||||
Contents: |
|
||||
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.exe
|
||||
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.dll
|
||||
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.xml
|
||||
**/Microsoft.VCLibs.*.appx
|
||||
**/TestHostApp/*
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/$(BuildPlatform)/test'
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
condition: and(and(succeeded(), eq(variables['BuildPlatform'], 'x64')), ne(variables['Build.Reason'], 'PullRequest'))
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish All Build Artifacts'
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'drop'
|
15
build/pipelines/templates/helix-createprojfile-steps.yml
Normal file
15
build/pipelines/templates/helix-createprojfile-steps.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
parameters:
|
||||
condition: ''
|
||||
testFilePath: ''
|
||||
outputProjFileName: ''
|
||||
testSuite: ''
|
||||
taefQuery: ''
|
||||
|
||||
steps:
|
||||
- task: powershell@2
|
||||
displayName: 'Create ${{ parameters.outputProjFileName }}'
|
||||
condition: ${{ parameters.condition }}
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\GenerateTestProjFile.ps1
|
||||
arguments: -TestFile '${{ parameters.testFilePath }}' -OutputProjFile '$(Build.ArtifactStagingDirectory)\${{ parameters.outputProjFileName }}' -JobTestSuiteName '${{ parameters.testSuite }}' -TaefPath '$(Build.SourcesDirectory)\build\Helix\packages\taef.redist.wlk.10.57.200731005-develop\build\Binaries\x86' -TaefQuery '${{ parameters.taefQuery }}'
|
68
build/pipelines/templates/helix-processtestresults-job.yml
Normal file
68
build/pipelines/templates/helix-processtestresults-job.yml
Normal file
|
@ -0,0 +1,68 @@
|
|||
parameters:
|
||||
condition: 'succeededOrFailed()'
|
||||
dependsOn: ''
|
||||
rerunPassesRequiredToAvoidFailure: 5
|
||||
minimumExpectedTestsExecutedCount: 10
|
||||
checkJobAttempt: false
|
||||
pgoArtifact: ''
|
||||
|
||||
jobs:
|
||||
- job: ProcessTestResults
|
||||
condition: ${{ parameters.condition }}
|
||||
dependsOn: ${{ parameters.dependsOn }}
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
timeoutInMinutes: 120
|
||||
variables:
|
||||
helixOutputFolder: $(Build.SourcesDirectory)\HelixOutput
|
||||
|
||||
steps:
|
||||
- task: powershell@2
|
||||
displayName: 'UpdateUnreliableTests.ps1'
|
||||
condition: succeededOrFailed()
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\UpdateUnreliableTests.ps1
|
||||
arguments: -RerunPassesRequiredToAvoidFailure '${{ parameters.rerunPassesRequiredToAvoidFailure }}'
|
||||
|
||||
- task: powershell@2
|
||||
displayName: 'OutputTestResults.ps1'
|
||||
condition: succeededOrFailed()
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\OutputTestResults.ps1
|
||||
arguments: -MinimumExpectedTestsExecutedCount '${{ parameters.minimumExpectedTestsExecutedCount }}' -CheckJobAttempt $${{ parameters.checkJobAttempt }}
|
||||
|
||||
- task: powershell@2
|
||||
displayName: 'ProcessHelixFiles.ps1'
|
||||
condition: succeededOrFailed()
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
HelixAccessToken: $(HelixApiAccessToken)
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\ProcessHelixFiles.ps1
|
||||
arguments: -OutputFolder '$(helixOutputFolder)'
|
||||
|
||||
- ${{if ne(parameters.pgoArtifact, '') }}:
|
||||
- script: move /y $(helixOutputFolder)\PGO $(Build.ArtifactStagingDirectory)
|
||||
displayName: 'Move pgc files to PGO artifact'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Helix files'
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: $(helixOutputFolder)
|
||||
artifactName: drop
|
||||
|
||||
- ${{if ne(parameters.pgoArtifact, '') }}:
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish pgc files'
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: $(Build.ArtifactStagingDirectory)\PGO\Release
|
||||
artifactName: ${{ parameters.pgoArtifact }}
|
131
build/pipelines/templates/helix-runtests-job.yml
Normal file
131
build/pipelines/templates/helix-runtests-job.yml
Normal file
|
@ -0,0 +1,131 @@
|
|||
parameters:
|
||||
name: 'RunTestsInHelix'
|
||||
dependsOn: ''
|
||||
condition: ''
|
||||
testSuite: ''
|
||||
# If a Pipeline runs this template more than once, this parameter should be unique per build flavor to differentiate the
|
||||
# the different test runs:
|
||||
helixType: 'test/devtest'
|
||||
artifactName: 'drop'
|
||||
maxParallel: 4
|
||||
rerunPassesRequiredToAvoidFailure: 5
|
||||
taefQuery: ''
|
||||
# if 'useBuildOutputFromBuildId' is set, we will default to using a build from this pipeline:
|
||||
useBuildOutputFromPipeline: $(System.DefinitionId)
|
||||
matrix:
|
||||
# Release_x86:
|
||||
# buildPlatform: 'x86'
|
||||
# buildConfiguration: 'release'
|
||||
# openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml'
|
||||
# closedHelixTargetQueues: 'windows.10.amd64.client19h1.xaml'
|
||||
Release_x64:
|
||||
buildPlatform: 'x64'
|
||||
buildConfiguration: 'release'
|
||||
openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml'
|
||||
closedHelixTargetQueues: 'windows.10.amd64.client19h1.xaml'
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
dependsOn: ${{ parameters.dependsOn }}
|
||||
condition: ${{ parameters.condition }}
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
timeoutInMinutes: 120
|
||||
strategy:
|
||||
maxParallel: ${{ parameters.maxParallel }}
|
||||
matrix: ${{ parameters.matrix }}
|
||||
variables:
|
||||
artifactsDir: $(Build.SourcesDirectory)\Artifacts
|
||||
taefPath: $(Build.SourcesDirectory)\build\Helix\packages\taef.redist.wlk.10.57.200731005-develop\build\Binaries\$(buildPlatform)
|
||||
helixCommonArgs: '/binaryLogger:$(Build.SourcesDirectory)/${{parameters.name}}.$(buildPlatform).$(buildConfiguration).binlog /p:HelixBuild=$(Build.BuildId).$(buildPlatform).$(buildConfiguration) /p:Platform=$(buildPlatform) /p:Configuration=$(buildConfiguration) /p:HelixType=${{parameters.helixType}} /p:TestSuite=${{parameters.testSuite}} /p:ProjFilesPath=$(Build.ArtifactStagingDirectory) /p:rerunPassesRequiredToAvoidFailure=${{parameters.rerunPassesRequiredToAvoidFailure}}'
|
||||
|
||||
|
||||
steps:
|
||||
- task: CmdLine@1
|
||||
displayName: 'Display build machine environment variables'
|
||||
inputs:
|
||||
filename: 'set'
|
||||
|
||||
- task: NuGetToolInstaller@0
|
||||
displayName: 'Use NuGet 5.2.0'
|
||||
inputs:
|
||||
versionSpec: 5.2.0
|
||||
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: 'NuGet restore build/Helix/packages.config'
|
||||
inputs:
|
||||
restoreSolution: build/Helix/packages.config
|
||||
feedsToUse: config
|
||||
nugetConfigPath: nuget.config
|
||||
restoreDirectory: packages
|
||||
|
||||
- task: DownloadBuildArtifacts@0
|
||||
condition:
|
||||
and(succeeded(),eq(variables['useBuildOutputFromBuildId'],''))
|
||||
inputs:
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
downloadPath: '$(artifactsDir)'
|
||||
|
||||
- task: DownloadBuildArtifacts@0
|
||||
condition:
|
||||
and(succeeded(),ne(variables['useBuildOutputFromBuildId'],''))
|
||||
inputs:
|
||||
buildType: specific
|
||||
buildVersionToDownload: specific
|
||||
project: $(System.TeamProjectId)
|
||||
pipeline: ${{ parameters.useBuildOutputFromPipeline }}
|
||||
buildId: $(useBuildOutputFromBuildId)
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
downloadPath: '$(artifactsDir)'
|
||||
|
||||
- task: CmdLine@1
|
||||
displayName: 'Display Artifact Directory payload contents'
|
||||
inputs:
|
||||
filename: 'dir'
|
||||
arguments: '/s $(artifactsDir)'
|
||||
|
||||
- task: powershell@2
|
||||
displayName: 'PrepareHelixPayload.ps1'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\PrepareHelixPayload.ps1
|
||||
arguments: -Platform '$(buildPlatform)' -Configuration '$(buildConfiguration)' -ArtifactName '${{ parameters.artifactName }}'
|
||||
|
||||
- task: CmdLine@1
|
||||
displayName: 'Display Helix payload contents'
|
||||
inputs:
|
||||
filename: 'dir'
|
||||
arguments: '/s $(Build.SourcesDirectory)\HelixPayload'
|
||||
|
||||
- template: helix-createprojfile-steps.yml
|
||||
parameters:
|
||||
condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite'))
|
||||
testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\TerminalApp.LocalTests.dll'
|
||||
outputProjFileName: 'RunTestsInHelix-TerminalAppLocalTests.proj'
|
||||
testSuite: '${{ parameters.testSuite }}'
|
||||
taefQuery: ${{ parameters.taefQuery }}
|
||||
|
||||
- template: helix-createprojfile-steps.yml
|
||||
parameters:
|
||||
condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite'))
|
||||
testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\Conhost.UIA.Tests.dll'
|
||||
outputProjFileName: 'RunTestsInHelix-HostTestsUIA.proj'
|
||||
testSuite: '${{ parameters.testSuite }}'
|
||||
taefQuery: ${{ parameters.taefQuery }}
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish generated .proj files'
|
||||
inputs:
|
||||
PathtoPublish: $(Build.ArtifactStagingDirectory)
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Run tests in Helix (open queues)'
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
command: custom
|
||||
projects: build\Helix\RunTestsInHelix.proj
|
||||
custom: msbuild
|
||||
arguments: '$(helixCommonArgs) /p:IsExternal=true /p:Creator=Terminal /p:HelixTargetQueues=$(openHelixTargetQueues)'
|
||||
|
15
build/scripts/Get-WttLog.ps1
Normal file
15
build/scripts/Get-WttLog.ps1
Normal file
|
@ -0,0 +1,15 @@
|
|||
[CmdLetBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true, Position=0)][string]$BuildPlatform,
|
||||
[Parameter(Mandatory=$true, Position=1)][string]$RationalizedPlatform,
|
||||
[Parameter(Mandatory=$true, Position=2)][string]$Configuration
|
||||
)
|
||||
|
||||
|
||||
$i = Get-Item .\packages\MuxCustomBuild*
|
||||
$wtt = Join-Path -Path $i[0].FullName -ChildPath (Join-Path -Path 'tools' -ChildPath (Join-Path -Path $BuildPlatform -ChildPath 'wttlog.dll'))
|
||||
$dest = Join-Path -Path .\bin -ChildPath (Join-Path -Path $RationalizedPlatform -ChildPath ($Configuration))
|
||||
copy $wtt $dest
|
||||
|
||||
|
||||
Exit 0
|
|
@ -2,12 +2,24 @@
|
|||
Param(
|
||||
[Parameter(Mandatory=$true, Position=0)][string]$MatchPattern,
|
||||
[Parameter(Mandatory=$true, Position=1)][string]$Platform,
|
||||
[Parameter(Mandatory=$true, Position=2)][string]$Configuration
|
||||
[Parameter(Mandatory=$true, Position=2)][string]$Configuration,
|
||||
[Parameter(Mandatory=$false, Position=3)][string]$LogPath
|
||||
)
|
||||
|
||||
$testdlls = Get-ChildItem -Path ".\bin\$Platform\$Configuration" -Recurse -Filter $MatchPattern
|
||||
|
||||
&".\bin\$Platform\$Configuration\te.exe" $testdlls.FullName
|
||||
|
||||
$args = @();
|
||||
|
||||
if ($LogPath)
|
||||
{
|
||||
$args += '/enablewttlogging';
|
||||
$args += '/appendwttlogging';
|
||||
$args += "/logFile:$LogPath";
|
||||
Write-Host "Wtt Logging Enabled";
|
||||
}
|
||||
|
||||
&".\bin\$Platform\$Configuration\te.exe" $args $testdlls.FullName
|
||||
|
||||
if ($lastexitcode -Ne 0) { Exit $lastexitcode }
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>3</VersionMinor>
|
||||
<VersionMinor>4</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ Properties listed below affect the entire window, regardless of the profile sett
|
|||
| -------- | --------- | ---- | ------- | ----------- |
|
||||
| `alwaysShowTabs` | _Required_ | Boolean | `true` | When set to `true`, tabs are always displayed. When set to `false` and `showTabsInTitlebar` is set to `false`, tabs only appear after typing <kbd>Ctrl</kbd> + <kbd>T</kbd>. |
|
||||
| `copyOnSelect` | Optional | Boolean | `false` | When set to `true`, a selection is immediately copied to your clipboard upon creation. When set to `false`, the selection persists and awaits further action. |
|
||||
| `copyFormatting` | Optional | Boolean | `false` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. |
|
||||
| `copyFormatting` | Optional | Boolean, Array | `true` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. |
|
||||
| `largePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with more than 5 KiB of characters will display a warning asking you whether to continue or not with the paste. |
|
||||
| `multiLinePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with a _new line_ character will display a warning asking you whether to continue or not with the paste. |
|
||||
| `defaultProfile` | _Required_ | String | PowerShell guid | Sets the default profile. Opens by typing <kbd>Ctrl</kbd> + <kbd>T</kbd> or by clicking the '+' icon. The guid of the desired default profile is used as the value. |
|
||||
|
@ -126,7 +126,7 @@ For commands with arguments:
|
|||
| `closePane` | Close the active pane. | | | |
|
||||
| `closeTab` | Close the current tab. | | | |
|
||||
| `closeWindow` | Close the current window and all tabs within it. | | | |
|
||||
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `singleLine` | boolean | When `true`, the copied content will be copied as a single line. When `false`, newlines persist from the selected text. |
|
||||
| `copy` | Copy the selected terminal content to your Windows Clipboard. | 1. `singleLine`<br>2. `copyFormatting` | 1. boolean<br>2. boolean, array | 1. When `true`, the copied content will be copied as a single line. When `false`, newlines persist from the selected text.<br>2. When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. Not setting this value inherits the behavior of the `copyFormatting` global setting. |
|
||||
| `duplicateTab` | Make a copy and open the current tab. | | | |
|
||||
| `find` | Open the search dialog box. | | | |
|
||||
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
|
||||
|
@ -159,7 +159,7 @@ For commands with arguments:
|
|||
| ---- | ---- |
|
||||
| Function and Alphanumeric Keys | `f1-f24`, `a-z`, `0-9` |
|
||||
| Symbols | ``` ` ```, `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/` |
|
||||
| Arrow Keys | `down`, `left`, `right`, `up`, `pagedown`, `pageup`, `pgdn`, `pgup`, `end`, `home`, `plus` |
|
||||
| Arrow Keys | `down`, `left`, `right`, `up`, `pagedown`, `pageup`, `pgdn`, `pgup`, `end`, `home`, `plus`, `app`, `menu` |
|
||||
| Action Keys | `tab`, `enter`, `esc`, `escape`, `space`, `backspace`, `delete`, `insert` |
|
||||
| Numpad Keys | `numpad_0-numpad_9`, `numpad0-numpad9`, `numpad_add`, `numpad_plus`, `numpad_decimal`, `numpad_period`, `numpad_divide`, `numpad_minus`, `numpad_subtract`, `numpad_multiply` |
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
"title": "Microsoft's Windows Terminal Settings Profile Schema",
|
||||
"definitions": {
|
||||
"KeyChordSegment": {
|
||||
"pattern": "^(?<modifier>(ctrl|alt|shift)(?:\\+(ctrl|alt|shift)(?<!\\2))?(?:\\+(ctrl|alt|shift)(?<!\\2|\\3))?\\+)?(?<key>[^\\s+]|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
|
||||
"pattern": "^(?<modifier>(ctrl|alt|shift)(?:\\+(ctrl|alt|shift)(?<!\\2))?(?:\\+(ctrl|alt|shift)(?<!\\2|\\3))?\\+)?(?<key>[^\\s+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
|
||||
"type": "string",
|
||||
"description": "The string should fit the format \"[ctrl+][alt+][shift+]<keyName>\", where each modifier is optional, separated by + symbols, and keyName is either one of the names listed in the table below, or any single key character. The string should be written in full lowercase.\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)"
|
||||
"description": "The string should fit the format \"[ctrl+][alt+][shift+]<keyName>\", where each modifier is optional, separated by + symbols, and keyName is either one of the names listed in the table below, or any single key character. The string should be written in full lowercase.\napp, menu\tMENU key\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)"
|
||||
},
|
||||
"Color": {
|
||||
"default": "#",
|
||||
|
@ -34,18 +34,24 @@
|
|||
"ShortcutActionName": {
|
||||
"enum": [
|
||||
"adjustFontSize",
|
||||
"closeOtherTabs",
|
||||
"closePane",
|
||||
"closeTab",
|
||||
"closeTabsAfter",
|
||||
"closeWindow",
|
||||
"commandPalette",
|
||||
"copy",
|
||||
"duplicateTab",
|
||||
"find",
|
||||
"moveFocus",
|
||||
"newTab",
|
||||
"nextTab",
|
||||
"openNewTabDropdown",
|
||||
"openSettings",
|
||||
"openTabColorPicker",
|
||||
"paste",
|
||||
"prevTab",
|
||||
"renameTab",
|
||||
"resetFontSize",
|
||||
"resizePane",
|
||||
"scrollDown",
|
||||
|
@ -53,22 +59,17 @@
|
|||
"scrollUp",
|
||||
"scrollUpPage",
|
||||
"sendInput",
|
||||
"splitPane",
|
||||
"switchToTab",
|
||||
"toggleFocusMode",
|
||||
"toggleFullscreen",
|
||||
"toggleAlwaysOnTop",
|
||||
"toggleRetroEffect",
|
||||
"find",
|
||||
"setColorScheme",
|
||||
"setTabColor",
|
||||
"openTabColorPicker",
|
||||
"renameTab",
|
||||
"commandPalette",
|
||||
"splitPane",
|
||||
"switchToTab",
|
||||
"tabSearch",
|
||||
"toggleAlwaysOnTop",
|
||||
"toggleFocusMode",
|
||||
"toggleFullscreen",
|
||||
"togglePaneZoom",
|
||||
"toggleRetroEffect",
|
||||
"wt",
|
||||
"closeOtherTabs",
|
||||
"closeTabsAfter",
|
||||
"tabSwitcher",
|
||||
"unbound"
|
||||
],
|
||||
"type": "string"
|
||||
|
@ -90,6 +91,32 @@
|
|||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CopyFormat": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"html",
|
||||
"rtf"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"html",
|
||||
"rtf",
|
||||
"all",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"AnchorKey": {
|
||||
"enum": [
|
||||
"ctrl",
|
||||
|
@ -163,6 +190,18 @@
|
|||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, the copied content will be copied as a single line (even if there are hard line breaks present in the text). If false, newlines persist from the selected text."
|
||||
},
|
||||
"copyFormatting": {
|
||||
"default": null,
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. Not setting this value inherits the behavior of the `copyFormatting` global setting.",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CopyFormat"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -358,14 +397,16 @@
|
|||
"properties": {
|
||||
"action": { "type": "string", "pattern": "closeOtherTabs" },
|
||||
"index": {
|
||||
"type": "integer",
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": null }
|
||||
],
|
||||
"default": "",
|
||||
"description": "close the tabs following the tab at this index"
|
||||
"description": "Close the tabs other than the one at this index. If no index is provided, use the focused tab's index."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [ "index" ]
|
||||
]
|
||||
},
|
||||
"CloseTabsAfterAction": {
|
||||
"description": "Arguments for a closeTabsAfter action",
|
||||
|
@ -375,26 +416,12 @@
|
|||
"properties": {
|
||||
"action": { "type": "string", "pattern": "closeTabsAfter" },
|
||||
"index": {
|
||||
"type": "integer",
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": null }
|
||||
],
|
||||
"default": "",
|
||||
"description": "close the tabs other than the one at this index"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [ "index" ]
|
||||
},
|
||||
"TabSwitcherAction": {
|
||||
"description": "Arguments corresponding to a Tab Switcher Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "tabSwitcher" },
|
||||
"anchorKey": {
|
||||
"$ref": "#/definitions/AnchorKey",
|
||||
"default": null,
|
||||
"description": "If provided, the tab switcher will stay open as long as the anchor key is held down. The anchor key should be part of the keybinding that opens the switcher."
|
||||
"description": "Close the tabs following the tab at this index. If no index is provided, use the focused tab's index."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -421,7 +448,6 @@
|
|||
{ "$ref": "#/definitions/WtAction" },
|
||||
{ "$ref": "#/definitions/CloseOtherTabsAction" },
|
||||
{ "$ref": "#/definitions/CloseTabsAfterAction" },
|
||||
{ "$ref": "#/definitions/TabSwitcherAction" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
|
@ -468,8 +494,8 @@
|
|||
},
|
||||
"copyFormatting": {
|
||||
"default": true,
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard.",
|
||||
"type": "boolean"
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
|
||||
"$ref": "#/definitions/CopyFormat"
|
||||
},
|
||||
"largePasteWarning": {
|
||||
"default": true,
|
||||
|
@ -518,10 +544,16 @@
|
|||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"startOnUserLogin": {
|
||||
"default": false,
|
||||
"description": "When set to true, this enables the launch of Windows Terminal at startup. Setting this to false will disable the startup task entry. If the Windows Terminal startup task entry is disabled either by org policy or by user action this setting will have no effect.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"launchMode": {
|
||||
"default": "default",
|
||||
"description": "Defines whether the Terminal will launch as maximized or not.",
|
||||
"description": "Defines whether the terminal will launch as maximized, full screen, or in a window.",
|
||||
"enum": [
|
||||
"fullscreen",
|
||||
"maximized",
|
||||
"default"
|
||||
],
|
||||
|
@ -586,6 +618,11 @@
|
|||
"default": true,
|
||||
"description": "When set to \"true\" closing a window with multiple tabs open will require confirmation. When set to \"false\", the confirmation dialog will not appear.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"useTabSwitcher": {
|
||||
"default": true,
|
||||
"description": "When set to \"true\", the \"nextTab\" and \"prevTab\" commands will use the tab switcher UI.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
BIN
res/Cascadia.ttf
BIN
res/Cascadia.ttf
Binary file not shown.
Binary file not shown.
|
@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
|
|||
|
||||
### Fonts Included
|
||||
|
||||
* Cascadia Code, Cascadia Mono (2007.15)
|
||||
* from microsoft/cascadia-code@2a54363b2c867f7ae811b9a034c0024cef67de96
|
||||
* Cascadia Code, Cascadia Mono (2008.25)
|
||||
* from microsoft/cascadia-code@678eea921b0c8b921b9fb009bb16d3d2ca5b8112
|
||||
|
|
|
@ -35,7 +35,7 @@ void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
|
|||
// - erases key and its associated data from the storage
|
||||
// Arguments:
|
||||
// - key - the key to remove
|
||||
void UnicodeStorage::Erase(const key_type key)
|
||||
void UnicodeStorage::Erase(const key_type key) noexcept
|
||||
{
|
||||
_map.erase(key);
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ public:
|
|||
|
||||
void StoreGlyph(const key_type key, const mapped_type& glyph);
|
||||
|
||||
void Erase(const key_type key);
|
||||
void Erase(const key_type key) noexcept;
|
||||
|
||||
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::TerminalApp::implementation;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
@ -73,24 +74,24 @@ namespace TerminalAppLocalTests
|
|||
|
||||
const auto scheme0 = ColorScheme::FromJson(scheme0Json);
|
||||
|
||||
VERIFY_IS_TRUE(scheme0.ShouldBeLayered(scheme0Json));
|
||||
VERIFY_IS_FALSE(scheme0.ShouldBeLayered(scheme1Json));
|
||||
VERIFY_IS_TRUE(scheme0.ShouldBeLayered(scheme2Json));
|
||||
VERIFY_IS_FALSE(scheme0.ShouldBeLayered(scheme3Json));
|
||||
VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme0Json));
|
||||
VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme1Json));
|
||||
VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme2Json));
|
||||
VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme3Json));
|
||||
|
||||
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));
|
||||
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));
|
||||
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()
|
||||
|
@ -130,38 +131,38 @@ namespace TerminalAppLocalTests
|
|||
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
|
||||
|
||||
auto scheme0 = ColorScheme::FromJson(scheme0Json);
|
||||
VERIFY_ARE_EQUAL(L"scheme0", scheme0._schemeName);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
|
||||
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]);
|
||||
VERIFY_ARE_EQUAL(L"scheme0", scheme0->_schemeName);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_defaultBackground);
|
||||
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);
|
||||
scheme0->LayerJson(scheme1Json);
|
||||
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0._defaultBackground);
|
||||
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]);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0->_defaultBackground);
|
||||
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);
|
||||
scheme0->LayerJson(scheme2Json);
|
||||
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
|
||||
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]);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_defaultBackground);
|
||||
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()
|
||||
|
@ -205,19 +206,20 @@ namespace TerminalAppLocalTests
|
|||
for (auto& kv : settings._globals._colorSchemes)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"kv:%s->%s", kv.first.data(), kv.second.GetName().data()));
|
||||
L"kv:%s->%s", kv.first.data(), kv.second.Name().data()));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(1u, settings._globals.GetColorSchemes().size());
|
||||
|
||||
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
|
||||
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
|
||||
auto scheme0Proj = settings._globals._colorSchemes.find(L"scheme0")->second;
|
||||
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._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_defaultBackground);
|
||||
}
|
||||
|
||||
settings._LayerOrCreateColorScheme(scheme1Json);
|
||||
|
@ -226,18 +228,20 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(2u, settings._globals.GetColorSchemes().size());
|
||||
|
||||
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
|
||||
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
|
||||
auto scheme0Proj = settings._globals._colorSchemes.find(L"scheme0")->second;
|
||||
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
|
||||
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
|
||||
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
|
||||
auto scheme1Proj = settings._globals._colorSchemes.find(L"scheme1")->second;
|
||||
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._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_defaultBackground);
|
||||
}
|
||||
settings._LayerOrCreateColorScheme(scheme2Json);
|
||||
|
||||
|
@ -245,18 +249,20 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(2u, settings._globals.GetColorSchemes().size());
|
||||
|
||||
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
|
||||
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
|
||||
auto scheme0Proj = settings._globals._colorSchemes.find(L"scheme0")->second;
|
||||
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
|
||||
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
|
||||
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
|
||||
auto scheme1Proj = settings._globals._colorSchemes.find(L"scheme1")->second;
|
||||
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._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_defaultBackground);
|
||||
}
|
||||
settings._LayerOrCreateColorScheme(scheme3Json);
|
||||
|
||||
|
@ -264,22 +270,25 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(3u, settings._globals.GetColorSchemes().size());
|
||||
|
||||
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
|
||||
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
|
||||
auto scheme0Proj = settings._globals._colorSchemes.find(L"scheme0")->second;
|
||||
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
|
||||
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
|
||||
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
|
||||
auto scheme1Proj = settings._globals._colorSchemes.find(L"scheme1")->second;
|
||||
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
|
||||
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"") != settings._globals._colorSchemes.end());
|
||||
auto scheme2 = settings._globals._colorSchemes.find(L"")->second;
|
||||
auto scheme2Proj = settings._globals._colorSchemes.find(L"")->second;
|
||||
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._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 6, 6, 6), scheme2._defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2._defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_defaultBackground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 6, 6, 6), scheme2->_defaultForeground);
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2->_defaultBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ using namespace Microsoft::Console;
|
|||
using namespace TerminalApp;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
@ -61,25 +62,25 @@ namespace TerminalAppLocalTests
|
|||
const auto commands1Json = VerifyParseSucceeded(commands1String);
|
||||
const auto commands2Json = VerifyParseSucceeded(commands2String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(2u, commands.size());
|
||||
VERIFY_ARE_EQUAL(2u, commands.Size());
|
||||
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(4u, commands.size());
|
||||
VERIFY_ARE_EQUAL(4u, commands.Size());
|
||||
}
|
||||
|
||||
void CommandTests::LayerCommand()
|
||||
|
@ -95,13 +96,13 @@ namespace TerminalAppLocalTests
|
|||
const auto commands2Json = VerifyParseSucceeded(commands2String);
|
||||
const auto commands3Json = VerifyParseSucceeded(commands3String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
|
@ -111,8 +112,8 @@ namespace TerminalAppLocalTests
|
|||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action());
|
||||
|
@ -121,8 +122,8 @@ namespace TerminalAppLocalTests
|
|||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
|
||||
|
@ -133,7 +134,7 @@ namespace TerminalAppLocalTests
|
|||
// This last command should "unbind" the action.
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands3Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,14 +154,14 @@ namespace TerminalAppLocalTests
|
|||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(5u, commands.size());
|
||||
VERIFY_ARE_EQUAL(5u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"command0");
|
||||
auto command = commands.Lookup(L"command0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
@ -170,7 +171,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command1");
|
||||
auto command = commands.Lookup(L"command1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
@ -180,7 +181,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command2");
|
||||
auto command = commands.Lookup(L"command2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
@ -190,7 +191,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command4");
|
||||
auto command = commands.Lookup(L"command4");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
@ -200,7 +201,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command5");
|
||||
auto command = commands.Lookup(L"command5");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
@ -217,17 +218,17 @@ namespace TerminalAppLocalTests
|
|||
const std::string commands0String{ R"([ { "name": { "key": "DuplicateTabCommandKey"}, "command": "copy" } ])" };
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
// NOTE: We're relying on DuplicateTabCommandKey being defined as
|
||||
// "Duplicate Tab" here. If that string changes in our resources,
|
||||
// this test will break.
|
||||
auto command = commands.at(L"Duplicate tab");
|
||||
auto command = commands.Lookup(L"Duplicate tab");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
|
@ -238,6 +239,14 @@ namespace TerminalAppLocalTests
|
|||
|
||||
void CommandTests::TestAutogeneratedName()
|
||||
{
|
||||
// Tests run in Helix can't report Skipped until GH#7286 is resolved.
|
||||
// Set ignore flag to make Helix run completely overlook it.
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
// This test to be corrected as a part of GH#7281
|
||||
|
||||
// This test ensures that we'll correctly create commands for actions
|
||||
// that don't have given names, pursuant to the spec in GH#6532.
|
||||
|
||||
|
@ -257,18 +266,18 @@ namespace TerminalAppLocalTests
|
|||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
|
||||
// There are only 3 commands here: all of the `"none"`, `"auto"`,
|
||||
// `"foo"`, `null`, and <no args> bindings all generate the same action,
|
||||
// which will generate just a single name for all of them.
|
||||
VERIFY_ARE_EQUAL(3u, commands.size());
|
||||
VERIFY_ARE_EQUAL(3u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane");
|
||||
auto command = commands.Lookup(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
@ -278,7 +287,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: vertical");
|
||||
auto command = commands.Lookup(L"Split pane, split: vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
@ -288,7 +297,7 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: horizontal");
|
||||
auto command = commands.Lookup(L"Split pane, split: horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
@ -307,14 +316,14 @@ namespace TerminalAppLocalTests
|
|||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane");
|
||||
auto command = commands.Lookup(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -51,7 +51,8 @@ namespace TerminalAppLocalTests
|
|||
TEST_METHOD(TryCreateSettingsType);
|
||||
TEST_METHOD(TryCreateConnectionType);
|
||||
TEST_METHOD(TryCreateXamlObjects);
|
||||
TEST_METHOD(TryCreateTab);
|
||||
|
||||
TEST_METHOD(TryInitializePage);
|
||||
|
||||
TEST_METHOD(CreateSimpleTerminalXamlType);
|
||||
TEST_METHOD(CreateTerminalMuxXamlType);
|
||||
|
@ -67,6 +68,11 @@ namespace TerminalAppLocalTests
|
|||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
|
||||
std::shared_ptr<CascadiaSettings> initialSettings);
|
||||
|
@ -125,35 +131,6 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::TryCreateTab()
|
||||
{
|
||||
// If you leave the Tab ptr owned by the RunOnUIThread lambda, it
|
||||
// will crash when the test tears down. Not totally clear why, but make
|
||||
// sure it's owned outside the lambda
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::Tab> newTab{ nullptr };
|
||||
|
||||
auto result = RunOnUIThread([&newTab]() {
|
||||
// Try creating all of:
|
||||
// 1. one of our pure c++ types (Profile)
|
||||
// 2. one of our c++winrt types (TerminalSettings, EchoConnection)
|
||||
// 3. one of our types that uses MUX/Xaml (TermControl).
|
||||
// 4. one of our types that uses MUX/Xaml in this dll (Tab).
|
||||
// Just creating all of them is enough to know that everything is working.
|
||||
const auto profileGuid{ Utils::CreateGuid() };
|
||||
TerminalSettings settings{};
|
||||
VERIFY_IS_NOT_NULL(settings);
|
||||
winrt::Microsoft::Terminal::TerminalConnection::EchoConnection conn{};
|
||||
VERIFY_IS_NOT_NULL(conn);
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl term{ settings, conn };
|
||||
VERIFY_IS_NOT_NULL(term);
|
||||
|
||||
newTab = winrt::make_self<winrt::TerminalApp::implementation::Tab>(profileGuid, term);
|
||||
VERIFY_IS_NOT_NULL(newTab);
|
||||
});
|
||||
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::CreateSimpleTerminalXamlType()
|
||||
{
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::MinMaxCloseControl> mmcc{ nullptr };
|
||||
|
@ -276,229 +253,268 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::TryInitializePage()
|
||||
{
|
||||
// This is a very simple test to prove we can create settings and a
|
||||
// TerminalPage and not only create them successfully, but also create a
|
||||
// tab using those settings successfully.
|
||||
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settingsJson0);
|
||||
auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
settings0->_ParseJsonString(settingsJson0, false);
|
||||
settings0->LayerJson(settings0->_userSettings);
|
||||
settings0->_ValidateSettings();
|
||||
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
_initializeTerminalPage(page, settings0);
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::TryDuplicateBadTab()
|
||||
{
|
||||
Log::Comment(L"This test regressed recently - it is temporarily disabled while GH#5169 is investigated");
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
return;
|
||||
// * Create a tab with a profile with GUID 1
|
||||
// * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// * Try calling _DuplicateTabViewItem on tab 1
|
||||
// * No new tab should be created (and more importantly, the app should not crash)
|
||||
//
|
||||
// Created to test GH#2455
|
||||
|
||||
// // * Create a tab with a profile with GUID 1
|
||||
// // * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// // * Try calling _DuplicateTabViewItem on tab 1
|
||||
// // * No new tab should be created (and more importantly, the app should not crash)
|
||||
// //
|
||||
// // Created to test GH#2455
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
// const std::string settingsJson0{ R"(
|
||||
// {
|
||||
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
// "profiles": [
|
||||
// {
|
||||
// "name" : "profile0",
|
||||
// "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
// "historySize": 1
|
||||
// },
|
||||
// {
|
||||
// "name" : "profile1",
|
||||
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
// "historySize": 2
|
||||
// }
|
||||
// ]
|
||||
// })" };
|
||||
const std::string settingsJson1{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
// const std::string settingsJson1{ R"(
|
||||
// {
|
||||
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
// "profiles": [
|
||||
// {
|
||||
// "name" : "profile1",
|
||||
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
// "historySize": 2
|
||||
// }
|
||||
// ]
|
||||
// })" };
|
||||
VerifyParseSucceeded(settingsJson0);
|
||||
auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
settings0->_ParseJsonString(settingsJson0, false);
|
||||
settings0->LayerJson(settings0->_userSettings);
|
||||
settings0->_ValidateSettings();
|
||||
|
||||
// VerifyParseSucceeded(settingsJson0);
|
||||
// auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
// VERIFY_IS_NOT_NULL(settings0);
|
||||
// settings0->_ParseJsonString(settingsJson0, false);
|
||||
// settings0->LayerJson(settings0->_userSettings);
|
||||
// settings0->_ValidateSettings();
|
||||
VerifyParseSucceeded(settingsJson1);
|
||||
auto settings1 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings1);
|
||||
settings1->_ParseJsonString(settingsJson1, false);
|
||||
settings1->LayerJson(settings1->_userSettings);
|
||||
settings1->_ValidateSettings();
|
||||
|
||||
// VerifyParseSucceeded(settingsJson1);
|
||||
// auto settings1 = std::make_shared<CascadiaSettings>(false);
|
||||
// VERIFY_IS_NOT_NULL(settings1);
|
||||
// settings1->_ParseJsonString(settingsJson1, false);
|
||||
// settings1->LayerJson(settings1->_userSettings);
|
||||
// settings1->_ValidateSettings();
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
// const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
// const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
// const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
_initializeTerminalPage(page, settings0);
|
||||
|
||||
// // This is super wacky, but we can't just initialize the
|
||||
// // com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// // the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// // during TerminalPage::Create() below.
|
||||
// //
|
||||
// // Instead, create the winrt object, then get a com_ptr to the
|
||||
// // implementation _from_ the winrt object. This seems to work, even if
|
||||
// // it's weird.
|
||||
// winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
// _initializeTerminalPage(page, settings0);
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// auto result = RunOnUIThread([&page]() {
|
||||
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
Log::Comment(L"Duplicate the first tab");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateTabViewItem();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// Log::Comment(L"Duplicate the first tab");
|
||||
// result = RunOnUIThread([&page]() {
|
||||
// page->_DuplicateTabViewItem();
|
||||
// VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Change the settings of the TerminalPage so the first profile is "
|
||||
L"no longer in the list of profiles"));
|
||||
result = RunOnUIThread([&page, settings1]() {
|
||||
page->_settings = settings1;
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// Log::Comment(NoThrowString().Format(
|
||||
// L"Change the settings of the TerminalPage so the first profile is "
|
||||
// L"no longer in the list of profiles"));
|
||||
// result = RunOnUIThread([&page, settings1]() {
|
||||
// page->_settings = settings1;
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
|
||||
// Log::Comment(L"Duplicate the tab, and don't crash");
|
||||
// result = RunOnUIThread([&page]() {
|
||||
// page->_DuplicateTabViewItem();
|
||||
// VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
Log::Comment(L"Duplicate the tab, and don't crash");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateTabViewItem();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::TryDuplicateBadPane()
|
||||
{
|
||||
Log::Comment(L"This test regressed recently - it is temporarily disabled while GH#5169 is investigated");
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
return;
|
||||
// * Create a tab with a profile with GUID 1
|
||||
// * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// * Try calling _SplitPane(Duplicate) on tab 1
|
||||
// * No new pane should be created (and more importantly, the app should not crash)
|
||||
//
|
||||
// Created to test GH#2455
|
||||
|
||||
// // * Create a tab with a profile with GUID 1
|
||||
// // * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// // * Try calling _SplitPane(Duplicate) on tab 1
|
||||
// // * No new pane should be created (and more importantly, the app should not crash)
|
||||
// //
|
||||
// // Created to test GH#2455
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
// const std::string settingsJson0{ R"(
|
||||
// {
|
||||
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
// "profiles": [
|
||||
// {
|
||||
// "name" : "profile0",
|
||||
// "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
// "historySize": 1
|
||||
// },
|
||||
// {
|
||||
// "name" : "profile1",
|
||||
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
// "historySize": 2
|
||||
// }
|
||||
// ]
|
||||
// })" };
|
||||
const std::string settingsJson1{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
// const std::string settingsJson1{ R"(
|
||||
// {
|
||||
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
// "profiles": [
|
||||
// {
|
||||
// "name" : "profile1",
|
||||
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
// "historySize": 2
|
||||
// }
|
||||
// ]
|
||||
// })" };
|
||||
VerifyParseSucceeded(settingsJson0);
|
||||
auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
settings0->_ParseJsonString(settingsJson0, false);
|
||||
settings0->LayerJson(settings0->_userSettings);
|
||||
settings0->_ValidateSettings();
|
||||
|
||||
// VerifyParseSucceeded(settingsJson0);
|
||||
// auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
// VERIFY_IS_NOT_NULL(settings0);
|
||||
// settings0->_ParseJsonString(settingsJson0, false);
|
||||
// settings0->LayerJson(settings0->_userSettings);
|
||||
// settings0->_ValidateSettings();
|
||||
VerifyParseSucceeded(settingsJson1);
|
||||
auto settings1 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings1);
|
||||
settings1->_ParseJsonString(settingsJson1, false);
|
||||
settings1->LayerJson(settings1->_userSettings);
|
||||
settings1->_ValidateSettings();
|
||||
|
||||
// VerifyParseSucceeded(settingsJson1);
|
||||
// auto settings1 = std::make_shared<CascadiaSettings>(false);
|
||||
// VERIFY_IS_NOT_NULL(settings1);
|
||||
// settings1->_ParseJsonString(settingsJson1, false);
|
||||
// settings1->LayerJson(settings1->_userSettings);
|
||||
// settings1->_ValidateSettings();
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
// const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
// const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
// const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
_initializeTerminalPage(page, settings0);
|
||||
|
||||
// // This is super wacky, but we can't just initialize the
|
||||
// // com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// // the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// // during TerminalPage::Create() below.
|
||||
// //
|
||||
// // Instead, create the winrt object, then get a com_ptr to the
|
||||
// // implementation _from_ the winrt object. This seems to work, even if
|
||||
// // it's weird.
|
||||
// winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
// _initializeTerminalPage(page, settings0);
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// auto result = RunOnUIThread([&page]() {
|
||||
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(1, tab->GetLeafPaneCount());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// result = RunOnUIThread([&page]() {
|
||||
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
// auto tab = page->_GetStrongTabImpl(0);
|
||||
// VERIFY_ARE_EQUAL(1, tab->_GetLeafPaneCount());
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
|
||||
// Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
|
||||
// result = RunOnUIThread([&page]() {
|
||||
// page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
// auto tab = page->_GetStrongTabImpl(0);
|
||||
// VERIFY_ARE_EQUAL(2, tab->_GetLeafPaneCount());
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Change the settings of the TerminalPage so the first profile is "
|
||||
L"no longer in the list of profiles"));
|
||||
result = RunOnUIThread([&page, settings1]() {
|
||||
page->_settings = settings1;
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// Log::Comment(NoThrowString().Format(
|
||||
// L"Change the settings of the TerminalPage so the first profile is "
|
||||
// L"no longer in the list of profiles"));
|
||||
// result = RunOnUIThread([&page, settings1]() {
|
||||
// page->_settings = settings1;
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
|
||||
// Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
|
||||
// result = RunOnUIThread([&page]() {
|
||||
// page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2,
|
||||
tab->GetLeafPaneCount(),
|
||||
L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
// auto tab = page->_GetStrongTabImpl(0);
|
||||
// VERIFY_ARE_EQUAL(2,
|
||||
// tab->_GetLeafPaneCount(),
|
||||
// L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
|
||||
// auto cleanup = wil::scope_exit([] {
|
||||
// auto result = RunOnUIThread([]() {
|
||||
// // There's something causing us to crash north of
|
||||
// // TSFInputControl::NotifyEnter, or LayoutRequested. It's very
|
||||
// // unclear what that issue is. Since these tests don't run in
|
||||
// // CI, simply log a message so that the dev running these tests
|
||||
// // knows it's expected.
|
||||
// Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
|
||||
// });
|
||||
// VERIFY_SUCCEEDED(result);
|
||||
// });
|
||||
auto cleanup = wil::scope_exit([] {
|
||||
auto result = RunOnUIThread([]() {
|
||||
// There's something causing us to crash north of
|
||||
// TSFInputControl::NotifyEnter, or LayoutRequested. It's very
|
||||
// unclear what that issue is. Since these tests don't run in
|
||||
// CI, simply log a message so that the dev running these tests
|
||||
// knows it's expected.
|
||||
Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -74,14 +74,14 @@
|
|||
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\TerminalAppLib.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
|
||||
|
||||
<!-- If you don't reference these projects here, the
|
||||
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\dll\TerminalApp.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Globals ======================== -->
|
||||
|
@ -89,7 +89,7 @@
|
|||
<!-- ====================== Compiler & Linker Flags ===================== -->
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
|
||||
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
|
||||
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj">
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\dll\TerminalApp.vcxproj">
|
||||
<Project>{ca5cad1a-44bd-4ac7-ac72-f16e576fdd12}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
|
|
|
@ -79,6 +79,8 @@ try
|
|||
case WM_RBUTTONUP:
|
||||
ReleaseCapture();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Suppress all mouse events that made it into the terminal.
|
||||
|
@ -132,6 +134,8 @@ try
|
|||
terminal->_hwnd.release();
|
||||
terminal->Teardown();
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
|
@ -238,7 +242,7 @@ HRESULT HwndTerminal::Initialize()
|
|||
_terminal->Create(COORD{ 80, 25 }, 1000, *_renderer);
|
||||
_terminal->SetDefaultBackground(RGB(12, 12, 12));
|
||||
_terminal->SetDefaultForeground(RGB(204, 204, 204));
|
||||
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); });
|
||||
_terminal->SetWriteInputCallback([=](std::wstring& input) noexcept { _WriteTextToConnection(input); });
|
||||
localPointerToThread->EnablePainting();
|
||||
|
||||
_multiClickTime = std::chrono::milliseconds{ GetDoubleClickTime() };
|
||||
|
@ -731,6 +735,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
|
|||
|
||||
publicTerminal->_terminal->SetDefaultForeground(theme.DefaultForeground);
|
||||
publicTerminal->_terminal->SetDefaultBackground(theme.DefaultBackground);
|
||||
publicTerminal->_renderEngine->SetSelectionBackground(theme.DefaultSelectionBackground, theme.SelectionBackgroundAlpha);
|
||||
|
||||
// Set the font colors
|
||||
for (size_t tableIndex = 0; tableIndex < 16; tableIndex++)
|
||||
|
|
|
@ -12,10 +12,13 @@
|
|||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
// Keep in sync with TerminalTheme.cs
|
||||
typedef struct _TerminalTheme
|
||||
{
|
||||
COLORREF DefaultBackground;
|
||||
COLORREF DefaultForeground;
|
||||
COLORREF DefaultSelectionBackground;
|
||||
float SelectionBackgroundAlpha;
|
||||
DispatchTypes::CursorStyle CursorStyle;
|
||||
COLORREF ColorTable[16];
|
||||
} TerminalTheme, *LPTerminalTheme;
|
||||
|
|
|
@ -7,45 +7,44 @@
|
|||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::string_view AdjustFontSizeKey{ "adjustFontSize" };
|
||||
static constexpr std::string_view CloseOtherTabsKey{ "closeOtherTabs" };
|
||||
static constexpr std::string_view ClosePaneKey{ "closePane" };
|
||||
static constexpr std::string_view CloseTabKey{ "closeTab" };
|
||||
static constexpr std::string_view CloseTabsAfterKey{ "closeTabsAfter" };
|
||||
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
|
||||
static constexpr std::string_view CopyTextKey{ "copy" };
|
||||
static constexpr std::string_view PasteTextKey{ "paste" };
|
||||
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
|
||||
static constexpr std::string_view DuplicateTabKey{ "duplicateTab" };
|
||||
static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
|
||||
static constexpr std::string_view FindKey{ "find" };
|
||||
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
|
||||
static constexpr std::string_view NewTabKey{ "newTab" };
|
||||
static constexpr std::string_view NewWindowKey{ "newWindow" };
|
||||
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
|
||||
static constexpr std::string_view CloseTabKey{ "closeTab" };
|
||||
static constexpr std::string_view ClosePaneKey{ "closePane" };
|
||||
static constexpr std::string_view SwitchtoTabKey{ "switchToTab" };
|
||||
static constexpr std::string_view NextTabKey{ "nextTab" };
|
||||
static constexpr std::string_view PrevTabKey{ "prevTab" };
|
||||
static constexpr std::string_view AdjustFontSizeKey{ "adjustFontSize" };
|
||||
static constexpr std::string_view ResetFontSizeKey{ "resetFontSize" };
|
||||
static constexpr std::string_view ScrollupKey{ "scrollUp" };
|
||||
static constexpr std::string_view ScrolldownKey{ "scrollDown" };
|
||||
static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" };
|
||||
static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" };
|
||||
static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
|
||||
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
|
||||
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings
|
||||
static constexpr std::string_view SendInputKey{ "sendInput" };
|
||||
static constexpr std::string_view SplitPaneKey{ "splitPane" };
|
||||
static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" };
|
||||
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
|
||||
static constexpr std::string_view PasteTextKey{ "paste" };
|
||||
static constexpr std::string_view PrevTabKey{ "prevTab" };
|
||||
static constexpr std::string_view RenameTabKey{ "renameTab" };
|
||||
static constexpr std::string_view ResetFontSizeKey{ "resetFontSize" };
|
||||
static constexpr std::string_view ResizePaneKey{ "resizePane" };
|
||||
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
|
||||
static constexpr std::string_view FindKey{ "find" };
|
||||
static constexpr std::string_view ToggleRetroEffectKey{ "toggleRetroEffect" };
|
||||
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
|
||||
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
|
||||
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
|
||||
static constexpr std::string_view ScrolldownKey{ "scrollDown" };
|
||||
static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" };
|
||||
static constexpr std::string_view ScrollupKey{ "scrollUp" };
|
||||
static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" };
|
||||
static constexpr std::string_view SendInputKey{ "sendInput" };
|
||||
static constexpr std::string_view SetColorSchemeKey{ "setColorScheme" };
|
||||
static constexpr std::string_view SetTabColorKey{ "setTabColor" };
|
||||
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
|
||||
static constexpr std::string_view RenameTabKey{ "renameTab" };
|
||||
static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
|
||||
static constexpr std::string_view SplitPaneKey{ "splitPane" };
|
||||
static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
|
||||
static constexpr std::string_view TabSearchKey{ "tabSearch" };
|
||||
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
|
||||
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
|
||||
static constexpr std::string_view CloseOtherTabsKey{ "closeOtherTabs" };
|
||||
static constexpr std::string_view CloseTabsAfterKey{ "closeTabsAfter" };
|
||||
static constexpr std::string_view ToggleTabSwitcherKey{ "tabSwitcher" };
|
||||
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
|
||||
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
|
||||
static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" };
|
||||
static constexpr std::string_view ToggleRetroEffectKey{ "toggleRetroEffect" };
|
||||
|
||||
static constexpr std::string_view ActionKey{ "action" };
|
||||
|
||||
|
@ -65,45 +64,45 @@ namespace winrt::TerminalApp::implementation
|
|||
// the map are all const for the lifetime of the app, we have nothing to worry
|
||||
// about here.
|
||||
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionKeyNamesMap{
|
||||
{ AdjustFontSizeKey, ShortcutAction::AdjustFontSize },
|
||||
{ CloseOtherTabsKey, ShortcutAction::CloseOtherTabs },
|
||||
{ ClosePaneKey, ShortcutAction::ClosePane },
|
||||
{ CloseTabKey, ShortcutAction::CloseTab },
|
||||
{ CloseTabsAfterKey, ShortcutAction::CloseTabsAfter },
|
||||
{ CloseWindowKey, ShortcutAction::CloseWindow },
|
||||
{ CopyTextKey, ShortcutAction::CopyText },
|
||||
{ PasteTextKey, ShortcutAction::PasteText },
|
||||
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
|
||||
{ DuplicateTabKey, ShortcutAction::DuplicateTab },
|
||||
{ ExecuteCommandlineKey, ShortcutAction::ExecuteCommandline },
|
||||
{ FindKey, ShortcutAction::Find },
|
||||
{ MoveFocusKey, ShortcutAction::MoveFocus },
|
||||
{ NewTabKey, ShortcutAction::NewTab },
|
||||
{ NewWindowKey, ShortcutAction::NewWindow },
|
||||
{ CloseWindowKey, ShortcutAction::CloseWindow },
|
||||
{ CloseTabKey, ShortcutAction::CloseTab },
|
||||
{ ClosePaneKey, ShortcutAction::ClosePane },
|
||||
{ NextTabKey, ShortcutAction::NextTab },
|
||||
{ PrevTabKey, ShortcutAction::PrevTab },
|
||||
{ AdjustFontSizeKey, ShortcutAction::AdjustFontSize },
|
||||
{ ResetFontSizeKey, ShortcutAction::ResetFontSize },
|
||||
{ ScrollupKey, ShortcutAction::ScrollUp },
|
||||
{ ScrolldownKey, ShortcutAction::ScrollDown },
|
||||
{ ScrolluppageKey, ShortcutAction::ScrollUpPage },
|
||||
{ ScrolldownpageKey, ShortcutAction::ScrollDownPage },
|
||||
{ SwitchToTabKey, ShortcutAction::SwitchToTab },
|
||||
{ ResizePaneKey, ShortcutAction::ResizePane },
|
||||
{ MoveFocusKey, ShortcutAction::MoveFocus },
|
||||
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
|
||||
{ OpenSettingsKey, ShortcutAction::OpenSettings },
|
||||
{ OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker },
|
||||
{ PasteTextKey, ShortcutAction::PasteText },
|
||||
{ PrevTabKey, ShortcutAction::PrevTab },
|
||||
{ RenameTabKey, ShortcutAction::RenameTab },
|
||||
{ ResetFontSizeKey, ShortcutAction::ResetFontSize },
|
||||
{ ResizePaneKey, ShortcutAction::ResizePane },
|
||||
{ ScrolldownKey, ShortcutAction::ScrollDown },
|
||||
{ ScrolldownpageKey, ShortcutAction::ScrollDownPage },
|
||||
{ ScrollupKey, ShortcutAction::ScrollUp },
|
||||
{ ScrolluppageKey, ShortcutAction::ScrollUpPage },
|
||||
{ SendInputKey, ShortcutAction::SendInput },
|
||||
{ SetColorSchemeKey, ShortcutAction::SetColorScheme },
|
||||
{ ToggleRetroEffectKey, ShortcutAction::ToggleRetroEffect },
|
||||
{ SetTabColorKey, ShortcutAction::SetTabColor },
|
||||
{ SplitPaneKey, ShortcutAction::SplitPane },
|
||||
{ SwitchToTabKey, ShortcutAction::SwitchToTab },
|
||||
{ TabSearchKey, ShortcutAction::TabSearch },
|
||||
{ ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop },
|
||||
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
|
||||
{ ToggleFocusModeKey, ShortcutAction::ToggleFocusMode },
|
||||
{ ToggleFullscreenKey, ShortcutAction::ToggleFullscreen },
|
||||
{ ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop },
|
||||
{ SendInputKey, ShortcutAction::SendInput },
|
||||
{ SplitPaneKey, ShortcutAction::SplitPane },
|
||||
{ TogglePaneZoomKey, ShortcutAction::TogglePaneZoom },
|
||||
{ SetTabColorKey, ShortcutAction::SetTabColor },
|
||||
{ OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker },
|
||||
{ ToggleRetroEffectKey, ShortcutAction::ToggleRetroEffect },
|
||||
{ UnboundKey, ShortcutAction::Invalid },
|
||||
{ FindKey, ShortcutAction::Find },
|
||||
{ RenameTabKey, ShortcutAction::RenameTab },
|
||||
{ ExecuteCommandlineKey, ShortcutAction::ExecuteCommandline },
|
||||
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
|
||||
{ CloseOtherTabsKey, ShortcutAction::CloseOtherTabs },
|
||||
{ CloseTabsAfterKey, ShortcutAction::CloseTabsAfter },
|
||||
{ ToggleTabSwitcherKey, ShortcutAction::ToggleTabSwitcher },
|
||||
};
|
||||
|
||||
using ParseResult = std::tuple<IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
|
||||
|
@ -115,37 +114,21 @@ namespace winrt::TerminalApp::implementation
|
|||
// placed into this map, with the corresponding deserializer function as the
|
||||
// value.
|
||||
static const std::map<ShortcutAction, ParseActionFunction, std::less<>> argParsers{
|
||||
{ ShortcutAction::CopyText, winrt::TerminalApp::implementation::CopyTextArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::NewTab, winrt::TerminalApp::implementation::NewTabArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::SwitchToTab, winrt::TerminalApp::implementation::SwitchToTabArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::ResizePane, winrt::TerminalApp::implementation::ResizePaneArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::MoveFocus, winrt::TerminalApp::implementation::MoveFocusArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::AdjustFontSize, winrt::TerminalApp::implementation::AdjustFontSizeArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::SendInput, winrt::TerminalApp::implementation::SendInputArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::SetColorScheme, winrt::TerminalApp::implementation::SetColorSchemeArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::SetTabColor, winrt::TerminalApp::implementation::SetTabColorArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::RenameTab, winrt::TerminalApp::implementation::RenameTabArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::ExecuteCommandline, winrt::TerminalApp::implementation::ExecuteCommandlineArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::CloseOtherTabs, winrt::TerminalApp::implementation::CloseOtherTabsArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::CloseTabsAfter, winrt::TerminalApp::implementation::CloseTabsAfterArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::ToggleTabSwitcher, winrt::TerminalApp::implementation::ToggleTabSwitcherArgs::FromJson },
|
||||
{ ShortcutAction::AdjustFontSize, AdjustFontSizeArgs::FromJson },
|
||||
{ ShortcutAction::CloseOtherTabs, CloseOtherTabsArgs::FromJson },
|
||||
{ ShortcutAction::CloseTabsAfter, CloseTabsAfterArgs::FromJson },
|
||||
{ ShortcutAction::CopyText, CopyTextArgs::FromJson },
|
||||
{ ShortcutAction::ExecuteCommandline, ExecuteCommandlineArgs::FromJson },
|
||||
{ ShortcutAction::MoveFocus, MoveFocusArgs::FromJson },
|
||||
{ ShortcutAction::NewTab, NewTabArgs::FromJson },
|
||||
{ ShortcutAction::OpenSettings, OpenSettingsArgs::FromJson },
|
||||
{ ShortcutAction::RenameTab, RenameTabArgs::FromJson },
|
||||
{ ShortcutAction::ResizePane, ResizePaneArgs::FromJson },
|
||||
{ ShortcutAction::SendInput, SendInputArgs::FromJson },
|
||||
{ ShortcutAction::SetColorScheme, SetColorSchemeArgs::FromJson },
|
||||
{ ShortcutAction::SetTabColor, SetTabColorArgs::FromJson },
|
||||
{ ShortcutAction::SplitPane, SplitPaneArgs::FromJson },
|
||||
{ ShortcutAction::SwitchToTab, SwitchToTabArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::Invalid, nullptr },
|
||||
};
|
||||
|
@ -263,44 +246,45 @@ namespace winrt::TerminalApp::implementation
|
|||
// to load the resources at _init_, only at runtime.
|
||||
static const auto GeneratedActionNames = []() {
|
||||
return std::unordered_map<ShortcutAction, winrt::hstring>{
|
||||
{ ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
|
||||
{ ShortcutAction::CloseOtherTabs, L"" }, // Intentionally omitted, must be generated by GenerateName
|
||||
{ ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
|
||||
{ ShortcutAction::CloseTab, RS_(L"CloseTabCommandKey") },
|
||||
{ ShortcutAction::CloseTabsAfter, L"" }, // Intentionally omitted, must be generated by GenerateName
|
||||
{ ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
|
||||
{ ShortcutAction::CopyText, RS_(L"CopyTextCommandKey") },
|
||||
{ ShortcutAction::PasteText, RS_(L"PasteTextCommandKey") },
|
||||
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
|
||||
{ ShortcutAction::DuplicateTab, RS_(L"DuplicateTabCommandKey") },
|
||||
{ ShortcutAction::ExecuteCommandline, RS_(L"ExecuteCommandlineCommandKey") },
|
||||
{ ShortcutAction::Find, RS_(L"FindCommandKey") },
|
||||
{ ShortcutAction::Invalid, L"" },
|
||||
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
|
||||
{ ShortcutAction::NewTab, RS_(L"NewTabCommandKey") },
|
||||
{ ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") },
|
||||
{ ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
|
||||
{ ShortcutAction::CloseTab, RS_(L"CloseTabCommandKey") },
|
||||
{ ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
|
||||
{ ShortcutAction::NextTab, RS_(L"NextTabCommandKey") },
|
||||
{ ShortcutAction::PrevTab, RS_(L"PrevTabCommandKey") },
|
||||
{ ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
|
||||
{ ShortcutAction::ResetFontSize, RS_(L"ResetFontSizeCommandKey") },
|
||||
{ ShortcutAction::ScrollUp, RS_(L"ScrollUpCommandKey") },
|
||||
{ ShortcutAction::ScrollDown, RS_(L"ScrollDownCommandKey") },
|
||||
{ ShortcutAction::ScrollUpPage, RS_(L"ScrollUpPageCommandKey") },
|
||||
{ ShortcutAction::ScrollDownPage, RS_(L"ScrollDownPageCommandKey") },
|
||||
{ ShortcutAction::SwitchToTab, RS_(L"SwitchToTabCommandKey") },
|
||||
{ ShortcutAction::ResizePane, RS_(L"ResizePaneCommandKey") },
|
||||
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
|
||||
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
|
||||
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
|
||||
{ ShortcutAction::ToggleRetroEffect, RS_(L"ToggleRetroEffectCommandKey") },
|
||||
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
|
||||
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
|
||||
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
|
||||
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
|
||||
{ ShortcutAction::PasteText, RS_(L"PasteTextCommandKey") },
|
||||
{ ShortcutAction::PrevTab, RS_(L"PrevTabCommandKey") },
|
||||
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
|
||||
{ ShortcutAction::ResetFontSize, RS_(L"ResetFontSizeCommandKey") },
|
||||
{ ShortcutAction::ResizePane, RS_(L"ResizePaneCommandKey") },
|
||||
{ ShortcutAction::ScrollDown, RS_(L"ScrollDownCommandKey") },
|
||||
{ ShortcutAction::ScrollDownPage, RS_(L"ScrollDownPageCommandKey") },
|
||||
{ ShortcutAction::ScrollUp, RS_(L"ScrollUpCommandKey") },
|
||||
{ ShortcutAction::ScrollUpPage, RS_(L"ScrollUpPageCommandKey") },
|
||||
{ ShortcutAction::SendInput, L"" },
|
||||
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
|
||||
{ ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") },
|
||||
{ ShortcutAction::Invalid, L"" },
|
||||
{ ShortcutAction::Find, RS_(L"FindCommandKey") },
|
||||
{ ShortcutAction::SetColorScheme, L"" },
|
||||
{ ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
|
||||
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
|
||||
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
|
||||
{ ShortcutAction::ExecuteCommandline, RS_(L"ExecuteCommandlineCommandKey") },
|
||||
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
|
||||
{ ShortcutAction::SwitchToTab, RS_(L"SwitchToTabCommandKey") },
|
||||
{ ShortcutAction::TabSearch, RS_(L"TabSearchCommandKey") },
|
||||
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
|
||||
{ ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") },
|
||||
{ ShortcutAction::CloseOtherTabs, L"" }, // Intentionally omitted, must be generated by GenerateName
|
||||
{ ShortcutAction::CloseTabsAfter, L"" }, // Intentionally omitted, must be generated by GenerateName
|
||||
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
|
||||
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
|
||||
{ ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") },
|
||||
{ ShortcutAction::ToggleRetroEffect, RS_(L"ToggleRetroEffectCommandKey") },
|
||||
};
|
||||
}();
|
||||
|
||||
|
|
|
@ -20,12 +20,13 @@
|
|||
#include "SetTabColorArgs.g.cpp"
|
||||
#include "RenameTabArgs.g.cpp"
|
||||
#include "ExecuteCommandlineArgs.g.cpp"
|
||||
#include "ToggleTabSwitcherArgs.g.h"
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
winrt::hstring NewTerminalArgs::GenerateName() const
|
||||
|
@ -67,11 +68,47 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
winrt::hstring CopyTextArgs::GenerateName() const
|
||||
{
|
||||
std::wstringstream ss;
|
||||
|
||||
if (_SingleLine)
|
||||
{
|
||||
return RS_(L"CopyTextAsSingleLineCommandKey");
|
||||
ss << RS_(L"CopyTextAsSingleLineCommandKey").c_str();
|
||||
}
|
||||
return RS_(L"CopyTextCommandKey");
|
||||
else
|
||||
{
|
||||
ss << RS_(L"CopyTextCommandKey").c_str();
|
||||
}
|
||||
|
||||
if (_CopyFormatting != nullptr)
|
||||
{
|
||||
ss << L", copyFormatting: ";
|
||||
if (_CopyFormatting.Value() == CopyFormat::All)
|
||||
{
|
||||
ss << L"all, ";
|
||||
}
|
||||
else if (_CopyFormatting.Value() == static_cast<CopyFormat>(0))
|
||||
{
|
||||
ss << L"none, ";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (WI_IsFlagSet(_CopyFormatting.Value(), CopyFormat::HTML))
|
||||
{
|
||||
ss << L"html, ";
|
||||
}
|
||||
|
||||
if (WI_IsFlagSet(_CopyFormatting.Value(), CopyFormat::RTF))
|
||||
{
|
||||
ss << L"rtf, ";
|
||||
}
|
||||
}
|
||||
|
||||
// Chop off the last ","
|
||||
auto result = ss.str();
|
||||
return winrt::hstring{ result.substr(0, result.size() - 2) };
|
||||
}
|
||||
|
||||
return winrt::hstring{ ss.str() };
|
||||
}
|
||||
|
||||
winrt::hstring NewTabArgs::GenerateName() const
|
||||
|
@ -302,37 +339,27 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
winrt::hstring CloseOtherTabsArgs::GenerateName() const
|
||||
{
|
||||
// "Close tabs other than index {0}"
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"CloseOtherTabsCommandKey")),
|
||||
_Index)
|
||||
};
|
||||
if (_Index)
|
||||
{
|
||||
// "Close tabs other than index {0}"
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"CloseOtherTabsCommandKey")),
|
||||
_Index.Value())
|
||||
};
|
||||
}
|
||||
return RS_(L"CloseOtherTabsDefaultCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring CloseTabsAfterArgs::GenerateName() const
|
||||
{
|
||||
// "Close tabs after index {0}"
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"CloseTabsAfterCommandKey")),
|
||||
_Index)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring ToggleTabSwitcherArgs::GenerateName() const
|
||||
{
|
||||
// If there's an anchor key set, don't generate a name so that
|
||||
// it won't show up in the command palette. Only an unanchored
|
||||
// tab switcher should be able to be toggled from the palette.
|
||||
// TODO: GH#7179 - once this goes in, make sure to hide the
|
||||
// anchor mode command that was given a name in settings.
|
||||
if (_AnchorKey != Windows::System::VirtualKey::None)
|
||||
if (_Index)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return RS_(L"ToggleTabSwitcherCommandKey");
|
||||
// "Close tabs after index {0}"
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"CloseTabsAfterCommandKey")),
|
||||
_Index.Value())
|
||||
};
|
||||
}
|
||||
return RS_(L"CloseTabsAfterDefaultCommandKey");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include "ExecuteCommandlineArgs.g.h"
|
||||
#include "CloseOtherTabsArgs.g.h"
|
||||
#include "CloseTabsAfterArgs.g.h"
|
||||
#include "ToggleTabSwitcherArgs.g.h"
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "Utils.h"
|
||||
|
@ -95,8 +94,10 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
CopyTextArgs() = default;
|
||||
GETSET_PROPERTY(bool, SingleLine, false);
|
||||
GETSET_PROPERTY(Windows::Foundation::IReference<Microsoft::Terminal::TerminalControl::CopyFormat>, CopyFormatting, nullptr);
|
||||
|
||||
static constexpr std::string_view SingleLineKey{ "singleLine" };
|
||||
static constexpr std::string_view CopyFormattingKey{ "copyFormatting" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
@ -106,7 +107,8 @@ namespace winrt::TerminalApp::implementation
|
|||
auto otherAsUs = other.try_as<CopyTextArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_SingleLine == _SingleLine;
|
||||
return otherAsUs->_SingleLine == _SingleLine &&
|
||||
otherAsUs->_CopyFormatting == _CopyFormatting;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
@ -115,6 +117,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<CopyTextArgs>();
|
||||
JsonUtils::GetValueForKey(json, SingleLineKey, args->_SingleLine);
|
||||
JsonUtils::GetValueForKey(json, CopyFormattingKey, args->_CopyFormatting);
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
@ -492,7 +495,7 @@ namespace winrt::TerminalApp::implementation
|
|||
struct CloseOtherTabsArgs : public CloseOtherTabsArgsT<CloseOtherTabsArgs>
|
||||
{
|
||||
CloseOtherTabsArgs() = default;
|
||||
GETSET_PROPERTY(uint32_t, Index, 0);
|
||||
GETSET_PROPERTY(winrt::Windows::Foundation::IReference<uint32_t>, Index, nullptr);
|
||||
|
||||
static constexpr std::string_view IndexKey{ "index" };
|
||||
|
||||
|
@ -520,7 +523,7 @@ namespace winrt::TerminalApp::implementation
|
|||
struct CloseTabsAfterArgs : public CloseTabsAfterArgsT<CloseTabsAfterArgs>
|
||||
{
|
||||
CloseTabsAfterArgs() = default;
|
||||
GETSET_PROPERTY(uint32_t, Index, 0);
|
||||
GETSET_PROPERTY(winrt::Windows::Foundation::IReference<uint32_t>, Index, nullptr);
|
||||
|
||||
static constexpr std::string_view IndexKey{ "index" };
|
||||
|
||||
|
@ -544,34 +547,6 @@ namespace winrt::TerminalApp::implementation
|
|||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
struct ToggleTabSwitcherArgs : public ToggleTabSwitcherArgsT<ToggleTabSwitcherArgs>
|
||||
{
|
||||
ToggleTabSwitcherArgs() = default;
|
||||
GETSET_PROPERTY(Windows::System::VirtualKey, AnchorKey, Windows::System::VirtualKey::None);
|
||||
|
||||
static constexpr std::string_view AnchorJsonKey{ "anchorKey" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<ToggleTabSwitcherArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_AnchorKey == _AnchorKey;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<ToggleTabSwitcherArgs>();
|
||||
JsonUtils::GetValueForKey(json, AnchorJsonKey, args->_AnchorKey);
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
|
|
|
@ -67,6 +67,7 @@ namespace TerminalApp
|
|||
[default_interface] runtimeclass CopyTextArgs : IActionArgs
|
||||
{
|
||||
Boolean SingleLine { get; };
|
||||
Windows.Foundation.IReference<Microsoft.Terminal.TerminalControl.CopyFormat> CopyFormatting { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass NewTabArgs : IActionArgs
|
||||
|
@ -133,16 +134,11 @@ namespace TerminalApp
|
|||
|
||||
[default_interface] runtimeclass CloseOtherTabsArgs : IActionArgs
|
||||
{
|
||||
UInt32 Index { get; };
|
||||
Windows.Foundation.IReference<UInt32> Index { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass CloseTabsAfterArgs : IActionArgs
|
||||
{
|
||||
UInt32 Index { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ToggleTabSwitcherArgs : IActionArgs
|
||||
{
|
||||
Windows.System.VirtualKey AnchorKey { get; };
|
||||
Windows.Foundation.IReference<UInt32> Index { get; };
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../AppLogic.idl";
|
||||
import "AppLogic.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
|
|
|
@ -122,7 +122,9 @@ namespace winrt::TerminalApp::implementation
|
|||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
auto activeTab = _GetFocusedTab();
|
||||
if (activeTab)
|
||||
|
||||
// Don't do anything if there's only one pane. It's already zoomed.
|
||||
if (activeTab && activeTab->GetLeafPaneCount() > 1)
|
||||
{
|
||||
// First thing's first, remove the current content from the UI
|
||||
// tree. This is important, because we might be leaving zoom, and if
|
||||
|
@ -235,7 +237,7 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::CopyTextArgs>())
|
||||
{
|
||||
const auto handled = _CopyText(realArgs.SingleLine());
|
||||
const auto handled = _CopyText(realArgs.SingleLine(), realArgs.CopyFormatting());
|
||||
args.Handled(handled);
|
||||
}
|
||||
}
|
||||
|
@ -413,7 +415,21 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
if (const auto& realArgs = actionArgs.ActionArgs().try_as<TerminalApp::CloseOtherTabsArgs>())
|
||||
{
|
||||
uint32_t index = realArgs.Index();
|
||||
uint32_t index;
|
||||
if (realArgs.Index())
|
||||
{
|
||||
index = realArgs.Index().Value();
|
||||
}
|
||||
else if (auto focusedTabIndex = _GetFocusedTabIndex())
|
||||
{
|
||||
index = *focusedTabIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing
|
||||
actionArgs.Handled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove tabs after the current one
|
||||
while (_tabs.Size() > index + 1)
|
||||
|
@ -436,7 +452,21 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
if (const auto& realArgs = actionArgs.ActionArgs().try_as<TerminalApp::CloseTabsAfterArgs>())
|
||||
{
|
||||
uint32_t index = realArgs.Index();
|
||||
uint32_t index;
|
||||
if (realArgs.Index())
|
||||
{
|
||||
index = realArgs.Index().Value();
|
||||
}
|
||||
else if (auto focusedTabIndex = _GetFocusedTabIndex())
|
||||
{
|
||||
index = *focusedTabIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing
|
||||
actionArgs.Handled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove tabs after the current one
|
||||
while (_tabs.Size() > index + 1)
|
||||
|
@ -454,27 +484,15 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleTabSwitcher(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
void TerminalPage::_HandleOpenTabSearch(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::ToggleTabSwitcherArgs>())
|
||||
{
|
||||
auto anchorKey = realArgs.AnchorKey();
|
||||
auto opt = _GetFocusedTabIndex();
|
||||
uint32_t startIdx = opt.value_or(0);
|
||||
|
||||
auto opt = _GetFocusedTabIndex();
|
||||
uint32_t startIdx = opt ? *opt : 0;
|
||||
CommandPalette().EnableTabSwitcherMode(true, startIdx);
|
||||
CommandPalette().Visibility(Visibility::Visible);
|
||||
|
||||
if (anchorKey != VirtualKey::None)
|
||||
{
|
||||
// TODO: GH#7178 - delta should also have the option of being -1, in the case when
|
||||
// a user decides to open the tab switcher going to the prev tab.
|
||||
int delta = 1;
|
||||
startIdx = (startIdx + _tabs.Size() + delta) % _tabs.Size();
|
||||
}
|
||||
|
||||
CommandPalette().EnableTabSwitcherMode(anchorKey, startIdx);
|
||||
CommandPalette().Visibility(Visibility::Visible);
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,11 @@ namespace winrt::TerminalApp::implementation
|
|||
// - The bound keychord, if this ActionAndArgs is bound to a key, otherwise nullptr.
|
||||
KeyChord AppKeyBindings::GetKeyBindingForActionWithArgs(TerminalApp::ActionAndArgs const& actionAndArgs)
|
||||
{
|
||||
if (actionAndArgs == nullptr)
|
||||
{
|
||||
return { nullptr };
|
||||
}
|
||||
|
||||
for (auto& kv : _keyShortcuts)
|
||||
{
|
||||
const auto action = kv.second.Action();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
import "../ActionArgs.idl";
|
||||
import "../ShortcutActionDispatch.idl";
|
||||
import "ActionArgs.idl";
|
||||
import "ShortcutActionDispatch.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
|
|
|
@ -38,7 +38,8 @@ static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadWar
|
|||
USES_RESOURCE(L"AtLeastOneKeybindingWarning"),
|
||||
USES_RESOURCE(L"TooManyKeysForChord"),
|
||||
USES_RESOURCE(L"MissingRequiredParameter"),
|
||||
USES_RESOURCE(L"LegacyGlobalsProperty")
|
||||
USES_RESOURCE(L"LegacyGlobalsProperty"),
|
||||
USES_RESOURCE(L"FailedToParseCommandJson")
|
||||
};
|
||||
static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadErrors::ERRORS_SIZE)> settingsLoadErrorsLabels {
|
||||
USES_RESOURCE(L"NoProfilesText"),
|
||||
|
@ -916,7 +917,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// - Implements the Alt handler (per GH#6421)
|
||||
// Return value:
|
||||
// - whether the key was handled
|
||||
bool AppLogic::OnDirectKeyEvent(const uint32_t vkey, const bool down)
|
||||
bool AppLogic::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
|
@ -927,7 +928,7 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
if (auto keyListener{ focusedObject.try_as<IDirectKeyListener>() })
|
||||
{
|
||||
if (keyListener.OnDirectKeyEvent(vkey, down))
|
||||
if (keyListener.OnDirectKeyEvent(vkey, scanCode, down))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -937,6 +938,13 @@ namespace winrt::TerminalApp::implementation
|
|||
if (auto focusedElement{ focusedObject.try_as<Windows::UI::Xaml::FrameworkElement>() })
|
||||
{
|
||||
focusedObject = focusedElement.Parent();
|
||||
|
||||
// Parent() seems to return null when the focusedElement is created from an ItemTemplate.
|
||||
// Use the VisualTreeHelper's GetParent as a fallback.
|
||||
if (!focusedObject)
|
||||
{
|
||||
focusedObject = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetParent(focusedElement);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
hstring Title();
|
||||
void TitlebarClicked();
|
||||
bool OnDirectKeyEvent(const uint32_t vkey, const bool down);
|
||||
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
|
||||
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../TerminalPage.idl";
|
||||
import "../ShortcutActionDispatch.idl";
|
||||
import "../IDirectKeyListener.idl";
|
||||
import "TerminalPage.idl";
|
||||
import "ShortcutActionDispatch.idl";
|
||||
import "IDirectKeyListener.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
|
|
|
@ -694,14 +694,11 @@ void CascadiaSettings::_ValidateNoGlobalsKey()
|
|||
// - The new settings string.
|
||||
std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::string_view settingsTemplate) const
|
||||
{
|
||||
// We're using replace_needle_in_haystack_inplace here, because it's more
|
||||
// efficient to iteratively modify a single string in-place than it is to
|
||||
// keep copying over the contents and modifying a copy (which
|
||||
// replace_needle_in_haystack would do).
|
||||
std::string finalSettings{ settingsTemplate };
|
||||
auto replace{ [](std::string& haystack, std::string_view needle, std::string_view replacement) {
|
||||
auto pos{ std::string::npos };
|
||||
while ((pos = haystack.rfind(needle, pos)) != std::string::npos)
|
||||
{
|
||||
haystack.replace(pos, needle.size(), replacement);
|
||||
}
|
||||
} };
|
||||
|
||||
std::wstring defaultProfileGuid{ DEFAULT_WINDOWS_POWERSHELL_GUID };
|
||||
if (const auto psCoreProfileGuid{ _GetProfileGuidByName(PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()) })
|
||||
|
@ -709,14 +706,22 @@ std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::strin
|
|||
defaultProfileGuid = Utils::GuidToString(*psCoreProfileGuid);
|
||||
}
|
||||
|
||||
replace(finalSettings, "%DEFAULT_PROFILE%", til::u16u8(defaultProfileGuid));
|
||||
til::replace_needle_in_haystack_inplace(finalSettings,
|
||||
"%DEFAULT_PROFILE%",
|
||||
til::u16u8(defaultProfileGuid));
|
||||
if (const auto appLogic{ winrt::TerminalApp::implementation::AppLogic::Current() })
|
||||
{
|
||||
replace(finalSettings, "%VERSION%", til::u16u8(appLogic->ApplicationVersion()));
|
||||
replace(finalSettings, "%PRODUCT%", til::u16u8(appLogic->ApplicationDisplayName()));
|
||||
til::replace_needle_in_haystack_inplace(finalSettings,
|
||||
"%VERSION%",
|
||||
til::u16u8(appLogic->ApplicationVersion()));
|
||||
til::replace_needle_in_haystack_inplace(finalSettings,
|
||||
"%PRODUCT%",
|
||||
til::u16u8(appLogic->ApplicationDisplayName()));
|
||||
}
|
||||
|
||||
replace(finalSettings, "%COMMAND_PROMPT_LOCALIZED_NAME%", RS_A(L"CommandPromptDisplayName"));
|
||||
til::replace_needle_in_haystack_inplace(finalSettings,
|
||||
"%COMMAND_PROMPT_LOCALIZED_NAME%",
|
||||
RS_A(L"CommandPromptDisplayName"));
|
||||
|
||||
return finalSettings;
|
||||
}
|
||||
|
@ -729,7 +734,7 @@ std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::strin
|
|||
// - profileGuid: the GUID of the profile to find the scheme for.
|
||||
// Return Value:
|
||||
// - a non-owning pointer to the scheme.
|
||||
const ColorScheme* CascadiaSettings::GetColorSchemeForProfile(const GUID profileGuid) const
|
||||
const ColorScheme CascadiaSettings::GetColorSchemeForProfile(const GUID profileGuid) const
|
||||
{
|
||||
auto* profile = FindProfile(profileGuid);
|
||||
if (!profile)
|
||||
|
@ -740,7 +745,7 @@ const ColorScheme* CascadiaSettings::GetColorSchemeForProfile(const GUID profile
|
|||
auto scheme = _globals.GetColorSchemes().find(schemeName);
|
||||
if (scheme != _globals.GetColorSchemes().end())
|
||||
{
|
||||
return &scheme->second;
|
||||
return scheme->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -22,6 +22,8 @@ Author(s):
|
|||
#include "Profile.h"
|
||||
#include "IDynamicProfileGenerator.h"
|
||||
|
||||
#include "ColorScheme.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
|
@ -78,7 +80,7 @@ public:
|
|||
static std::filesystem::path GetDefaultSettingsPath();
|
||||
|
||||
const Profile* FindProfile(GUID profileGuid) const noexcept;
|
||||
const ColorScheme* GetColorSchemeForProfile(const GUID profileGuid) const;
|
||||
const winrt::TerminalApp::ColorScheme GetColorSchemeForProfile(const GUID profileGuid) const;
|
||||
|
||||
std::vector<TerminalApp::SettingsLoadWarnings>& GetWarnings();
|
||||
|
||||
|
@ -99,7 +101,7 @@ private:
|
|||
void _LayerOrCreateProfile(const Json::Value& profileJson);
|
||||
Profile* _FindMatchingProfile(const Json::Value& profileJson);
|
||||
void _LayerOrCreateColorScheme(const Json::Value& schemeJson);
|
||||
ColorScheme* _FindMatchingColorScheme(const Json::Value& schemeJson);
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::ColorScheme> _FindMatchingColorScheme(const Json::Value& schemeJson);
|
||||
void _ParseJsonString(std::string_view fileData, const bool isDefaultSettings);
|
||||
static const Json::Value& _GetProfilesJsonObject(const Json::Value& json);
|
||||
static const Json::Value& _GetDisabledProfileSourcesJsonObject(const Json::Value& json);
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
// "Generated Files" directory.
|
||||
|
||||
using namespace ::TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
|
@ -709,7 +708,8 @@ void CascadiaSettings::_LayerOrCreateColorScheme(const Json::Value& schemeJson)
|
|||
}
|
||||
else
|
||||
{
|
||||
_globals.AddColorScheme(ColorScheme::FromJson(schemeJson));
|
||||
const auto scheme = implementation::ColorScheme::FromJson(schemeJson);
|
||||
_globals.AddColorScheme(*scheme);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -724,19 +724,15 @@ void CascadiaSettings::_LayerOrCreateColorScheme(const Json::Value& schemeJson)
|
|||
// Return Value:
|
||||
// - a ColorScheme that can be layered with the given json object, iff such a
|
||||
// color scheme exists.
|
||||
ColorScheme* CascadiaSettings::_FindMatchingColorScheme(const Json::Value& schemeJson)
|
||||
winrt::com_ptr<implementation::ColorScheme> CascadiaSettings::_FindMatchingColorScheme(const Json::Value& schemeJson)
|
||||
{
|
||||
if (auto schemeName = ColorScheme::GetNameFromJson(schemeJson))
|
||||
if (auto schemeName = implementation::ColorScheme::GetNameFromJson(schemeJson))
|
||||
{
|
||||
auto& schemes = _globals.GetColorSchemes();
|
||||
auto iterator = schemes.find(*schemeName);
|
||||
if (iterator != schemes.end())
|
||||
{
|
||||
// HERE BE DRAGONS: Returning a pointer to a type in the vector is
|
||||
// maybe not the _safest_ thing, but we have a mind to make Profile
|
||||
// and ColorScheme winrt types in the future, so this will be safer
|
||||
// then.
|
||||
return &iterator->second;
|
||||
return winrt::get_self<implementation::ColorScheme>(iterator->second)->get_strong();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
|
|
@ -114,11 +114,11 @@
|
|||
</StackPanel.Resources>
|
||||
<Button Padding="5"
|
||||
Click="ClearColorButton_Click"
|
||||
x:Name="ClearColorButton" x:Uid="TabColorClearButton" Content="Reset">
|
||||
x:Name="ClearColorButton" x:Uid="TabColorClearButton" CornerRadius="2" Content="Reset">
|
||||
</Button>
|
||||
<Button Padding="5"
|
||||
Click="ShowColorPickerButton_Click"
|
||||
x:Name="CustomColorButton" x:Uid="TabColorCustomButton" Content="Custom...">
|
||||
x:Name="CustomColorButton" x:Uid="TabColorCustomButton" CornerRadius="2" Content="Custom...">
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
|
|
@ -8,9 +8,12 @@
|
|||
#include "Utils.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include "ColorScheme.g.cpp"
|
||||
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::TerminalApp::implementation;
|
||||
using namespace winrt::Windows::UI;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view ForegroundKey{ "foreground" };
|
||||
|
@ -46,7 +49,7 @@ ColorScheme::ColorScheme() :
|
|||
{
|
||||
}
|
||||
|
||||
ColorScheme::ColorScheme(std::wstring name, til::color defaultFg, til::color defaultBg, til::color cursorColor) :
|
||||
ColorScheme::ColorScheme(winrt::hstring name, Color defaultFg, Color defaultBg, Color cursorColor) :
|
||||
_schemeName{ name },
|
||||
_table{},
|
||||
_defaultForeground{ defaultFg },
|
||||
|
@ -87,10 +90,10 @@ void ColorScheme::ApplyScheme(const winrt::Microsoft::Terminal::TerminalControl:
|
|||
// - json: an object which should be a serialization of a ColorScheme object.
|
||||
// Return Value:
|
||||
// - a new ColorScheme instance created from the values in `json`
|
||||
ColorScheme ColorScheme::FromJson(const Json::Value& json)
|
||||
winrt::com_ptr<ColorScheme> ColorScheme::FromJson(const Json::Value& json)
|
||||
{
|
||||
ColorScheme result;
|
||||
result.LayerJson(json);
|
||||
auto result = winrt::make_self<ColorScheme>();
|
||||
result->LayerJson(json);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -138,32 +141,34 @@ void ColorScheme::LayerJson(const Json::Value& json)
|
|||
}
|
||||
}
|
||||
|
||||
std::wstring_view ColorScheme::GetName() const noexcept
|
||||
winrt::hstring ColorScheme::Name() const noexcept
|
||||
{
|
||||
return { _schemeName };
|
||||
return _schemeName;
|
||||
}
|
||||
|
||||
std::array<til::color, COLOR_TABLE_SIZE>& ColorScheme::GetTable() noexcept
|
||||
winrt::com_array<Color> ColorScheme::Table() const noexcept
|
||||
{
|
||||
return _table;
|
||||
winrt::com_array<Color> result{ COLOR_TABLE_SIZE };
|
||||
std::transform(_table.begin(), _table.end(), result.begin(), [](til::color c) -> Color { return c; });
|
||||
return result;
|
||||
}
|
||||
|
||||
til::color ColorScheme::GetForeground() const noexcept
|
||||
Color ColorScheme::Foreground() const noexcept
|
||||
{
|
||||
return _defaultForeground;
|
||||
}
|
||||
|
||||
til::color ColorScheme::GetBackground() const noexcept
|
||||
Color ColorScheme::Background() const noexcept
|
||||
{
|
||||
return _defaultBackground;
|
||||
}
|
||||
|
||||
til::color ColorScheme::GetSelectionBackground() const noexcept
|
||||
Color ColorScheme::SelectionBackground() const noexcept
|
||||
{
|
||||
return _selectionBackground;
|
||||
}
|
||||
|
||||
til::color ColorScheme::GetCursorColor() const noexcept
|
||||
Color ColorScheme::CursorColor() const noexcept
|
||||
{
|
||||
return _cursorColor;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ Author(s):
|
|||
#include "TerminalSettings.h"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
#include "ColorScheme.g.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
|
@ -25,41 +27,44 @@ namespace TerminalAppLocalTests
|
|||
class ColorSchemeTests;
|
||||
};
|
||||
|
||||
namespace TerminalApp
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
class ColorScheme;
|
||||
};
|
||||
struct ColorScheme : ColorSchemeT<ColorScheme>
|
||||
{
|
||||
public:
|
||||
ColorScheme();
|
||||
ColorScheme(hstring name, Windows::UI::Color defaultFg, Windows::UI::Color defaultBg, Windows::UI::Color cursorColor);
|
||||
~ColorScheme();
|
||||
|
||||
class TerminalApp::ColorScheme
|
||||
void ApplyScheme(const winrt::Microsoft::Terminal::TerminalControl::IControlSettings& terminalSettings) const;
|
||||
|
||||
static com_ptr<ColorScheme> FromJson(const Json::Value& json);
|
||||
bool ShouldBeLayered(const Json::Value& json) const;
|
||||
void LayerJson(const Json::Value& json);
|
||||
|
||||
hstring Name() const noexcept;
|
||||
com_array<Windows::UI::Color> Table() const noexcept;
|
||||
Windows::UI::Color Foreground() const noexcept;
|
||||
Windows::UI::Color Background() const noexcept;
|
||||
Windows::UI::Color SelectionBackground() const noexcept;
|
||||
Windows::UI::Color CursorColor() const noexcept;
|
||||
|
||||
static std::optional<std::wstring> GetNameFromJson(const Json::Value& json);
|
||||
|
||||
private:
|
||||
hstring _schemeName;
|
||||
std::array<til::color, COLOR_TABLE_SIZE> _table;
|
||||
til::color _defaultForeground;
|
||||
til::color _defaultBackground;
|
||||
til::color _selectionBackground;
|
||||
til::color _cursorColor;
|
||||
|
||||
friend class TerminalAppLocalTests::SettingsTests;
|
||||
friend class TerminalAppLocalTests::ColorSchemeTests;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
public:
|
||||
ColorScheme();
|
||||
ColorScheme(std::wstring name, til::color defaultFg, til::color defaultBg, til::color cursorColor);
|
||||
~ColorScheme();
|
||||
|
||||
void ApplyScheme(const winrt::Microsoft::Terminal::TerminalControl::IControlSettings& terminalSettings) const;
|
||||
|
||||
static ColorScheme FromJson(const Json::Value& json);
|
||||
bool ShouldBeLayered(const Json::Value& json) const;
|
||||
void LayerJson(const Json::Value& json);
|
||||
|
||||
std::wstring_view GetName() const noexcept;
|
||||
std::array<til::color, COLOR_TABLE_SIZE>& GetTable() noexcept;
|
||||
til::color GetForeground() const noexcept;
|
||||
til::color GetBackground() const noexcept;
|
||||
til::color GetSelectionBackground() const noexcept;
|
||||
til::color GetCursorColor() const noexcept;
|
||||
|
||||
static std::optional<std::wstring> GetNameFromJson(const Json::Value& json);
|
||||
|
||||
private:
|
||||
std::wstring _schemeName;
|
||||
std::array<til::color, COLOR_TABLE_SIZE> _table;
|
||||
til::color _defaultForeground;
|
||||
til::color _defaultBackground;
|
||||
til::color _selectionBackground;
|
||||
til::color _cursorColor;
|
||||
|
||||
friend class TerminalAppLocalTests::SettingsTests;
|
||||
friend class TerminalAppLocalTests::ColorSchemeTests;
|
||||
};
|
||||
BASIC_FACTORY(ColorScheme);
|
||||
}
|
||||
|
|
21
src/cascadia/TerminalApp/ColorScheme.idl
Normal file
21
src/cascadia/TerminalApp/ColorScheme.idl
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass ColorScheme {
|
||||
ColorScheme();
|
||||
ColorScheme(String name, Windows.UI.Color defaultFg, Windows.UI.Color defaultBg, Windows.UI.Color cursorColor);
|
||||
|
||||
void ApplyScheme(Microsoft.Terminal.TerminalControl.IControlSettings terminalSettings);
|
||||
|
||||
String Name { get; };
|
||||
|
||||
Windows.UI.Color Foreground { get; };
|
||||
Windows.UI.Color Background { get; };
|
||||
Windows.UI.Color SelectionBackground { get; };
|
||||
Windows.UI.Color CursorColor { get; };
|
||||
|
||||
Windows.UI.Color[] Table { get; };
|
||||
}
|
||||
}
|
|
@ -9,17 +9,45 @@
|
|||
#include "ActionAndArgs.h"
|
||||
#include "JsonUtils.h"
|
||||
#include <LibraryResources.h>
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace ::TerminalApp;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
namespace MUX = Microsoft::UI::Xaml;
|
||||
namespace WUX = Windows::UI::Xaml;
|
||||
}
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view IconPathKey{ "iconPath" };
|
||||
static constexpr std::string_view IconKey{ "icon" };
|
||||
static constexpr std::string_view ActionKey{ "command" };
|
||||
static constexpr std::string_view ArgsKey{ "args" };
|
||||
static constexpr std::string_view IterateOnKey{ "iterateOn" };
|
||||
static constexpr std::string_view CommandsKey{ "commands" };
|
||||
|
||||
static constexpr std::string_view ProfileNameToken{ "${profile.name}" };
|
||||
static constexpr std::string_view ProfileIconToken{ "${profile.icon}" };
|
||||
static constexpr std::string_view SchemeNameToken{ "${scheme.name}" };
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
Command::Command()
|
||||
{
|
||||
_setAction(nullptr);
|
||||
}
|
||||
|
||||
Collections::IMapView<winrt::hstring, TerminalApp::Command> Command::NestedCommands()
|
||||
{
|
||||
return _subcommands ? _subcommands.GetView() : nullptr;
|
||||
}
|
||||
|
||||
bool Command::HasNestedCommands()
|
||||
{
|
||||
return _subcommands ? _subcommands.Size() > 0 : false;
|
||||
}
|
||||
// Function Description:
|
||||
// - attempt to get the name of this command from the provided json object.
|
||||
// * If the "name" property is a string, return that value.
|
||||
|
@ -79,6 +107,70 @@ namespace winrt::TerminalApp::implementation
|
|||
return actionAndArgs->GenerateName();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Actually initialize our IconSource for our _lastIconPath. Supports a variety of icons:
|
||||
// * If the icon is a path to an image, we'll use that.
|
||||
// * If it isn't, then we'll try and use the text as a FontIcon. If the
|
||||
// character is in the range of symbols reserved for the Segoe MDL2
|
||||
// Asserts, well treat it as such. Otherwise, we'll default to a Sego
|
||||
// UI icon, so things like emoji will work.
|
||||
// - MUST BE CALLED ON THE UI THREAD.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Command::RefreshIcon()
|
||||
{
|
||||
if (!_lastIconPath.empty())
|
||||
{
|
||||
_setIconSource(GetColoredIcon<winrt::WUX::Controls::IconSource>(_lastIconPath));
|
||||
|
||||
// If we fail to set the icon source using the "icon" as a path,
|
||||
// let's try it as a symbol/emoji.
|
||||
//
|
||||
// Anything longer that 2 wchar_t's _isn't_ an emoji or symbol, so
|
||||
// don't do this if it's just an invalid path.
|
||||
if (IconSource() == nullptr && _lastIconPath.size() <= 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
WUX::Controls::FontIconSource icon;
|
||||
const wchar_t ch = _lastIconPath[0];
|
||||
|
||||
// The range of MDL2 Icons isn't explicitly defined, but
|
||||
// we're using this based off the table on:
|
||||
// https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font
|
||||
const bool isMDL2Icon = ch >= L'\uE700' && ch <= L'\uF8FF';
|
||||
if (isMDL2Icon)
|
||||
{
|
||||
icon.FontFamily(WUX::Media::FontFamily{ L"Segoe MDL2 Assets" });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: you _do_ need to manually set the font here.
|
||||
icon.FontFamily(WUX::Media::FontFamily{ L"Segoe UI" });
|
||||
}
|
||||
icon.FontSize(12);
|
||||
icon.Glyph(_lastIconPath);
|
||||
_setIconSource(icon);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
if (IconSource() == nullptr)
|
||||
{
|
||||
// Set the default IconSource to a BitmapIconSource with a null source
|
||||
// (instead of just nullptr) because there's a really weird crash when swapping
|
||||
// data bound IconSourceElements in a ListViewTemplate (i.e. CommandPalette).
|
||||
// Swapping between nullptr IconSources and non-null IconSources causes a crash
|
||||
// to occur, but swapping between IconSources with a null source and non-null IconSources
|
||||
// work perfectly fine :shrug:.
|
||||
winrt::Windows::UI::Xaml::Controls::BitmapIconSource icon;
|
||||
icon.UriSource(nullptr);
|
||||
_setIconSource(icon);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Deserialize a Command from the `json` object. The json object should
|
||||
// contain a "name" and "action", and optionally an "icon".
|
||||
|
@ -99,35 +191,76 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
auto result = winrt::make_self<Command>();
|
||||
|
||||
// TODO GH#6644: iconPath not implemented quite yet. Can't seem to get
|
||||
// the binding quite right. Additionally, do we want it to be an image,
|
||||
// or a FontIcon? I've had difficulty binding either/or.
|
||||
bool nested = false;
|
||||
JsonUtils::GetValueForKey(json, IterateOnKey, result->_IterateOn);
|
||||
|
||||
if (const auto actionJson{ json[JsonKey(ActionKey)] })
|
||||
// For iterable commands, we'll make another pass at parsing them once
|
||||
// the json is patched. So ignore parsing sub-commands for now. Commands
|
||||
// will only be marked iterable on the first pass.
|
||||
if (const auto nestedCommandsJson{ json[JsonKey(CommandsKey)] })
|
||||
{
|
||||
auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings);
|
||||
// Initialize our list of subcommands.
|
||||
result->_subcommands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
|
||||
auto nestedWarnings = Command::LayerJson(result->_subcommands, nestedCommandsJson);
|
||||
// It's possible that the nested commands have some warnings
|
||||
warnings.insert(warnings.end(), nestedWarnings.begin(), nestedWarnings.end());
|
||||
|
||||
if (actionAndArgs)
|
||||
{
|
||||
result->_setAction(*actionAndArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something like
|
||||
// { name: "foo", action: "unbound" }
|
||||
// will _remove_ the "foo" command, by returning null here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result->_setName(_nameFromJsonOrAction(json, actionAndArgs));
|
||||
nested = true;
|
||||
}
|
||||
else
|
||||
else if (json.isMember(JsonKey(CommandsKey)))
|
||||
{
|
||||
// { name: "foo", action: null } will land in this case, which
|
||||
// { "name": "foo", "commands": null } will land in this case, which
|
||||
// should also be used for unbinding.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Only get the icon path right now. The icon needs to be resolved into
|
||||
// an IconSource on the UI thread, which will be done by RefreshIcon.
|
||||
JsonUtils::GetValueForKey(json, IconKey, result->_lastIconPath);
|
||||
|
||||
// If we're a nested command, we can ignore the current action.
|
||||
if (!nested)
|
||||
{
|
||||
if (const auto actionJson{ json[JsonKey(ActionKey)] })
|
||||
{
|
||||
auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings);
|
||||
|
||||
if (actionAndArgs)
|
||||
{
|
||||
result->_setAction(*actionAndArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something like
|
||||
// { name: "foo", action: "unbound" }
|
||||
// will _remove_ the "foo" command, by returning null here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If an iterable command doesn't have a name set, we'll still just
|
||||
// try and generate a fake name for the command give the string we
|
||||
// currently have. It'll probably generate something like "New tab,
|
||||
// profile: ${profile.name}". This string will only be temporarily
|
||||
// used internally, so there's no problem.
|
||||
result->_setName(_nameFromJsonOrAction(json, actionAndArgs));
|
||||
}
|
||||
else
|
||||
{
|
||||
// { name: "foo", action: null } will land in this case, which
|
||||
// should also be used for unbinding.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result->_setName(_nameFromJson(json));
|
||||
}
|
||||
|
||||
// Stash the original json value in this object. If the command is
|
||||
// iterable, we'll need to re-parse it later, once we know what all the
|
||||
// values we can iterate on are.
|
||||
result->_originalJson = json;
|
||||
|
||||
if (result->_Name.empty())
|
||||
{
|
||||
return nullptr;
|
||||
|
@ -147,7 +280,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// - json: A Json::Value containing an array of serialized commands
|
||||
// Return Value:
|
||||
// - A vector containing any warnings detected while parsing
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> Command::LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> Command::LayerJson(Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json)
|
||||
{
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> warnings;
|
||||
|
@ -162,7 +295,7 @@ namespace winrt::TerminalApp::implementation
|
|||
if (result)
|
||||
{
|
||||
// Override commands with the same name
|
||||
commands.insert_or_assign(result->Name(), *result);
|
||||
commands.Insert(result->Name(), *result);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -172,7 +305,7 @@ namespace winrt::TerminalApp::implementation
|
|||
const auto name = _nameFromJson(value);
|
||||
if (!name.empty())
|
||||
{
|
||||
commands.erase(name);
|
||||
commands.Remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,4 +314,197 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper to escape a string as a json string. This function will also
|
||||
// trim off the leading and trailing double-quotes, so the output string
|
||||
// can be inserted directly into another json blob.
|
||||
// Arguments:
|
||||
// - input: the string to JSON escape.
|
||||
// Return Value:
|
||||
// - the input string escaped properly to be inserted into another json blob.
|
||||
std::string _escapeForJson(const std::string& input)
|
||||
{
|
||||
Json::Value inJson{ input };
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder.settings_["indentation"] = "";
|
||||
std::string out{ Json::writeString(builder, inJson) };
|
||||
if (out.size() >= 2)
|
||||
{
|
||||
// trim off the leading/trailing '"'s
|
||||
auto ss{ out.substr(1, out.size() - 2) };
|
||||
return ss;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Iterate over all the provided commands, and recursively expand any
|
||||
// commands with `iterateOn` set. If we successfully generated expanded
|
||||
// commands for them, then we'll remove the original command, and add all
|
||||
// the newly generated commands.
|
||||
// - For more specific implementation details, see _expandCommand.
|
||||
// Arguments:
|
||||
// - commands: a map of commands to expand. Newly created commands will be
|
||||
// inserted into the map to replace the expandable commands.
|
||||
// - profiles: A list of all the profiles that this command should be expanded on.
|
||||
// - warnings: If there were any warnings during parsing, they'll be
|
||||
// appended to this vector.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Command::ExpandCommands(Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
gsl::span<const ::TerminalApp::Profile> profiles,
|
||||
gsl::span<winrt::TerminalApp::ColorScheme> schemes,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
|
||||
{
|
||||
std::vector<winrt::hstring> commandsToRemove;
|
||||
std::vector<winrt::TerminalApp::Command> commandsToAdd;
|
||||
|
||||
// First, collect up all the commands that need replacing.
|
||||
for (const auto& nameAndCmd : commands)
|
||||
{
|
||||
auto cmd{ get_self<implementation::Command>(nameAndCmd.Value()) };
|
||||
|
||||
auto newCommands = _expandCommand(cmd, profiles, schemes, warnings);
|
||||
if (newCommands.size() > 0)
|
||||
{
|
||||
commandsToRemove.push_back(nameAndCmd.Key());
|
||||
commandsToAdd.insert(commandsToAdd.end(), newCommands.begin(), newCommands.end());
|
||||
}
|
||||
}
|
||||
|
||||
// Second, remove all the commands that need to be removed.
|
||||
for (auto& name : commandsToRemove)
|
||||
{
|
||||
commands.Remove(name);
|
||||
}
|
||||
|
||||
// Finally, add all the new commands.
|
||||
for (auto& cmd : commandsToAdd)
|
||||
{
|
||||
commands.Insert(cmd.Name(), cmd);
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Attempts to expand the given command into many commands, if the command
|
||||
// has `"iterateOn": "profiles"` set.
|
||||
// - If it doesn't, this function will do
|
||||
// nothing and return an empty vector.
|
||||
// - If it does, we're going to attempt to build a new set of commands using
|
||||
// the given command as a prototype. We'll attempt to create a new command
|
||||
// for each and every profile, to replace the original command.
|
||||
// * For the new commands, we'll replace any instance of "${profile.name}"
|
||||
// in the original json used to create this action with the name of the
|
||||
// given profile.
|
||||
// - If we encounter any errors while re-parsing the json with the replaced
|
||||
// name, we'll just return immediately.
|
||||
// - At the end, we'll return all the new commands we've build for the given command.
|
||||
// Arguments:
|
||||
// - expandable: the Command to potentially turn into more commands
|
||||
// - profiles: A list of all the profiles that this command should be expanded on.
|
||||
// - warnings: If there were any warnings during parsing, they'll be
|
||||
// appended to this vector.
|
||||
// Return Value:
|
||||
// - and empty vector if the command wasn't expandable, otherwise a list of
|
||||
// the newly-created commands.
|
||||
std::vector<winrt::TerminalApp::Command> Command::_expandCommand(Command* const expandable,
|
||||
gsl::span<const ::TerminalApp::Profile> profiles,
|
||||
gsl::span<winrt::TerminalApp::ColorScheme> schemes,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
|
||||
{
|
||||
std::vector<winrt::TerminalApp::Command> newCommands;
|
||||
|
||||
if (expandable->HasNestedCommands())
|
||||
{
|
||||
ExpandCommands(expandable->_subcommands, profiles, schemes, warnings);
|
||||
}
|
||||
|
||||
if (expandable->_IterateOn == ExpandCommandType::None)
|
||||
{
|
||||
return newCommands;
|
||||
}
|
||||
|
||||
std::string errs; // This string will receive any error text from failing to parse.
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
|
||||
// First, get a string for the original Json::Value
|
||||
auto oldJsonString = expandable->_originalJson.toStyledString();
|
||||
|
||||
auto reParseJson = [&](const auto& newJsonString) -> bool {
|
||||
// - Now, re-parse the modified value.
|
||||
Json::Value newJsonValue;
|
||||
const auto actualDataStart = newJsonString.data();
|
||||
const auto actualDataEnd = newJsonString.data() + newJsonString.size();
|
||||
if (!reader->parse(actualDataStart, actualDataEnd, &newJsonValue, &errs))
|
||||
{
|
||||
warnings.push_back(::TerminalApp::SettingsLoadWarnings::FailedToParseCommandJson);
|
||||
// If we encounter a re-parsing error, just stop processing the rest of the commands.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pass the new json back though FromJson, to get the new expanded value.
|
||||
if (auto newCmd{ Command::FromJson(newJsonValue, warnings) })
|
||||
{
|
||||
newCommands.push_back(*newCmd);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (expandable->_IterateOn == ExpandCommandType::Profiles)
|
||||
{
|
||||
for (const auto& p : profiles)
|
||||
{
|
||||
// For each profile, create a new command. This command will have:
|
||||
// * the icon path and keychord text of the original command
|
||||
// * the Name will have any instances of "${profile.name}"
|
||||
// replaced with the profile's name
|
||||
// * for the action, we'll take the original json, replace any
|
||||
// instances of "${profile.name}" with the profile's name,
|
||||
// then re-attempt to parse the action and args.
|
||||
|
||||
// Replace all the keywords in the original json, and try and parse that
|
||||
|
||||
// - Escape the profile name for JSON appropriately
|
||||
auto escapedProfileName = _escapeForJson(til::u16u8(p.GetName()));
|
||||
auto escapedProfileIcon = _escapeForJson(til::u16u8(p.GetExpandedIconPath()));
|
||||
auto newJsonString = til::replace_needle_in_haystack(oldJsonString,
|
||||
ProfileNameToken,
|
||||
escapedProfileName);
|
||||
til::replace_needle_in_haystack_inplace(newJsonString,
|
||||
ProfileIconToken,
|
||||
escapedProfileIcon);
|
||||
|
||||
// If we encounter a re-parsing error, just stop processing the rest of the commands.
|
||||
if (!reParseJson(newJsonString))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (expandable->_IterateOn == ExpandCommandType::ColorSchemes)
|
||||
{
|
||||
for (const auto& s : schemes)
|
||||
{
|
||||
// For each scheme, create a new command. We'll take the
|
||||
// original json, replace any instances of "${scheme.name}" with
|
||||
// the scheme's name, then re-attempt to parse the action and
|
||||
// args.
|
||||
|
||||
// - Escape the profile name for JSON appropriately
|
||||
auto escapedSchemeName = _escapeForJson(til::u16u8(s.Name()));
|
||||
auto newJsonString = til::replace_needle_in_haystack(oldJsonString,
|
||||
SchemeNameToken,
|
||||
escapedSchemeName);
|
||||
|
||||
// If we encounter a re-parsing error, just stop processing the rest of the commands.
|
||||
if (!reParseJson(newJsonString))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newCommands;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*++
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
|
@ -20,17 +20,37 @@ Author(s):
|
|||
|
||||
#include "Command.g.h"
|
||||
#include "TerminalWarnings.h"
|
||||
#include "Profile.h"
|
||||
#include "..\inc\cppwinrt_utils.h"
|
||||
#include "SettingsTypes.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
class SettingsTests;
|
||||
class CommandTests;
|
||||
};
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct Command : CommandT<Command>
|
||||
{
|
||||
Command() = default;
|
||||
Command();
|
||||
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json, std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
|
||||
static void ExpandCommands(Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
gsl::span<const ::TerminalApp::Profile> profiles,
|
||||
gsl::span<winrt::TerminalApp::ColorScheme> schemes,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
|
||||
static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json);
|
||||
bool HasNestedCommands();
|
||||
Windows::Foundation::Collections::IMapView<winrt::hstring, TerminalApp::Command> NestedCommands();
|
||||
|
||||
void RefreshIcon();
|
||||
|
||||
winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker;
|
||||
|
||||
|
@ -39,6 +59,21 @@ namespace winrt::TerminalApp::implementation
|
|||
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::ActionAndArgs, Action, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, KeyChordText, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
GETSET_PROPERTY(::TerminalApp::ExpandCommandType, IterateOn, ::TerminalApp::ExpandCommandType::None);
|
||||
|
||||
private:
|
||||
Json::Value _originalJson;
|
||||
Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command> _subcommands{ nullptr };
|
||||
|
||||
winrt::hstring _lastIconPath{};
|
||||
|
||||
static std::vector<winrt::TerminalApp::Command> _expandCommand(Command* const expandable,
|
||||
gsl::span<const ::TerminalApp::Profile> profiles,
|
||||
gsl::span<winrt::TerminalApp::ColorScheme> schemes,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
friend class TerminalAppLocalTests::SettingsTests;
|
||||
friend class TerminalAppLocalTests::CommandTests;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../ShortcutActionDispatch.idl";
|
||||
import "ShortcutActionDispatch.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
|
@ -14,5 +14,9 @@ namespace TerminalApp
|
|||
String KeyChordText;
|
||||
|
||||
Windows.UI.Xaml.Controls.IconSource IconSource;
|
||||
void RefreshIcon();
|
||||
|
||||
Boolean HasNestedCommands { get; };
|
||||
Windows.Foundation.Collections.IMapView<String, Command> NestedCommands { get; };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,13 @@ namespace winrt::TerminalApp::implementation
|
|||
InitializeComponent();
|
||||
|
||||
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::Command>();
|
||||
_nestedActionStack = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
_currentNestedCommands = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
_allCommands = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
_allTabActions = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
|
||||
_switchToMode(CommandPaletteMode::ActionMode);
|
||||
|
||||
if (CommandPaletteShadow())
|
||||
{
|
||||
// Hook up the shadow on the command palette to the backdrop that
|
||||
|
@ -46,24 +50,21 @@ namespace winrt::TerminalApp::implementation
|
|||
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (Visibility() == Visibility::Visible)
|
||||
{
|
||||
if (_currentMode == CommandPaletteMode::TabSwitcherMode)
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
if (_anchorKey != VirtualKey::None)
|
||||
{
|
||||
_searchBox().Visibility(Visibility::Collapsed);
|
||||
_filteredActionsView().Focus(FocusState::Keyboard);
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
}
|
||||
|
||||
_searchBox().Visibility(Visibility::Collapsed);
|
||||
_filteredActionsView().Focus(FocusState::Keyboard);
|
||||
_filteredActionsView().SelectedIndex(_switcherStartIdx);
|
||||
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
||||
|
||||
// Do this right after becoming visible so we can quickly catch scenarios where
|
||||
// modifiers aren't held down (e.g. command palette invocation).
|
||||
_anchorKeyUpHandler();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
_updateFilteredActions();
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
}
|
||||
|
||||
|
@ -88,7 +89,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// when the ListView has been measured out and is ready, and we'll immediately
|
||||
// revoke the handler because we only needed to handle it once on initialization.
|
||||
_sizeChangedRevoker = _filteredActionsView().SizeChanged(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
|
||||
if (_currentMode == CommandPaletteMode::TabSwitcherMode && _anchorKey != VirtualKey::None)
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
_filteredActionsView().Focus(FocusState::Keyboard);
|
||||
}
|
||||
|
@ -104,7 +105,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// list. Otherwise, we're attempting to move to the previous.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_selectNextItem(const bool moveDown)
|
||||
void CommandPalette::SelectNextItem(const bool moveDown)
|
||||
{
|
||||
const auto selected = _filteredActionsView().SelectedIndex();
|
||||
const int numItems = ::base::saturated_cast<int>(_filteredActionsView().Items().Size());
|
||||
|
@ -128,19 +129,17 @@ namespace winrt::TerminalApp::implementation
|
|||
// Only give anchored tab switcher the ability to cycle through tabs with the tab button.
|
||||
// For unanchored mode, accessibility becomes an issue when we try to hijack tab since it's
|
||||
// a really widely used keyboard navigation key.
|
||||
if (_currentMode == CommandPaletteMode::TabSwitcherMode &&
|
||||
key == VirtualKey::Tab &&
|
||||
_anchorKey != VirtualKey::None)
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode && key == VirtualKey::Tab)
|
||||
{
|
||||
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift);
|
||||
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
|
||||
{
|
||||
_selectNextItem(false);
|
||||
SelectNextItem(false);
|
||||
e.Handled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectNextItem(true);
|
||||
SelectNextItem(true);
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
@ -162,66 +161,149 @@ namespace winrt::TerminalApp::implementation
|
|||
if (key == VirtualKey::Up)
|
||||
{
|
||||
// Action Mode: Move focus to the next item in the list.
|
||||
_selectNextItem(false);
|
||||
SelectNextItem(false);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Down)
|
||||
{
|
||||
// Action Mode: Move focus to the previous item in the list.
|
||||
_selectNextItem(true);
|
||||
SelectNextItem(true);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Enter)
|
||||
{
|
||||
// Action Mode: Dispatch the action of the selected command.
|
||||
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
// Action, TabSwitch or TabSearchMode Mode: Dispatch the action of the selected command.
|
||||
if (_currentMode != CommandPaletteMode::CommandlineMode)
|
||||
{
|
||||
_dispatchCommand(selectedItem.try_as<TerminalApp::Command>());
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
{
|
||||
_dispatchCommand(selectedItem.try_as<TerminalApp::Command>());
|
||||
}
|
||||
}
|
||||
// Commandline Mode: Use the input to synthesize an ExecuteCommandline action
|
||||
else if (_currentMode == CommandPaletteMode::CommandlineMode)
|
||||
{
|
||||
_dispatchCommandline();
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Escape)
|
||||
{
|
||||
// Action Mode: Dismiss the palette if the text is empty, otherwise clear the search string.
|
||||
if (_searchBox().Text().empty())
|
||||
// Action, TabSearch, TabSwitch Mode: Dismiss the palette if the
|
||||
// text is empty, otherwise clear the search string.
|
||||
if (_currentMode != CommandPaletteMode::CommandlineMode)
|
||||
{
|
||||
_dismissPalette();
|
||||
if (_searchBox().Text().empty())
|
||||
{
|
||||
_dismissPalette();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox().Text(L"");
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (_currentMode == CommandPaletteMode::CommandlineMode)
|
||||
{
|
||||
_searchBox().Text(L"");
|
||||
const auto currentInput = _getPostPrefixInput();
|
||||
if (currentInput.empty())
|
||||
{
|
||||
// The user's only input "> " so far. We should just dismiss
|
||||
// the palette. This is like dismissing the Action mode with
|
||||
// empty input.
|
||||
_dismissPalette();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear out the current input. We'll leave a ">" in the
|
||||
// input (to stay in commandline mode), and a leading space
|
||||
// (if they currently had one).
|
||||
const bool hasLeadingSpace = (_searchBox().Text().size()) - (currentInput.size()) > 1;
|
||||
_searchBox().Text(hasLeadingSpace ? L"> " : L">");
|
||||
|
||||
// This will conveniently move the cursor to the end of the
|
||||
// text input for us.
|
||||
_searchBox().Select(_searchBox().Text().size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto vkey = ::gsl::narrow_cast<WORD>(e.OriginalKey());
|
||||
|
||||
// In the interest of not telling all modes to check for keybindings, limit to TabSwitch mode for now.
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
auto success = _bindings.TryKeyChord({
|
||||
ctrlDown,
|
||||
altDown,
|
||||
shiftDown,
|
||||
vkey,
|
||||
});
|
||||
|
||||
if (success)
|
||||
{
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Implements the Alt handler
|
||||
// Return value:
|
||||
// - whether the key was handled
|
||||
bool CommandPalette::OnDirectKeyEvent(const uint32_t vkey, const uint8_t /*scanCode*/, const bool down)
|
||||
{
|
||||
auto handled = false;
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
if (vkey == VK_MENU && !down)
|
||||
{
|
||||
_anchorKeyUpHandler();
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
void CommandPalette::_keyUpHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
|
||||
if (_currentMode == CommandPaletteMode::TabSwitcherMode)
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
if (_anchorKey && key == _anchorKey.value())
|
||||
_anchorKeyUpHandler();
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handles anchor key ups during TabSwitchMode.
|
||||
// We assume that at least one modifier key should be held down in order to "anchor"
|
||||
// the ATS UI in place. So this function is called to check if any modifiers are
|
||||
// still held down, and if not, dispatch the selected tab action and close the ATS.
|
||||
// Return value:
|
||||
// - <none>
|
||||
void CommandPalette::_anchorKeyUpHandler()
|
||||
{
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
if (!ctrlDown && !altDown && !shiftDown)
|
||||
{
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
{
|
||||
// Once the user lifts the anchor key, we'll switch to the currently selected tab
|
||||
// then close the tab switcher.
|
||||
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
if (const auto data = selectedItem.try_as<TerminalApp::Command>())
|
||||
{
|
||||
if (const auto data = selectedItem.try_as<TerminalApp::Command>())
|
||||
{
|
||||
const auto actionAndArgs = data.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
_updateFilteredActions();
|
||||
_dismissPalette();
|
||||
}
|
||||
_dispatchCommand(data);
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,6 +350,29 @@ namespace winrt::TerminalApp::implementation
|
|||
_dispatchCommand(e.ClickedItem().try_as<TerminalApp::Command>());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is called when the user selects a command with subcommands. It
|
||||
// will update our UI to now display the list of subcommands instead, and
|
||||
// clear the search text so the user can search from the new list of
|
||||
// commands.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_updateUIForStackChange()
|
||||
{
|
||||
if (_searchBox().Text().empty())
|
||||
{
|
||||
// Manually call _filterTextChanged, because setting the text to the
|
||||
// empty string won't update it for us (as it won't actually change value.)
|
||||
_filterTextChanged(nullptr, nullptr);
|
||||
}
|
||||
|
||||
// Changing the value of the search box will trigger _filterTextChanged,
|
||||
// which will cause us to refresh the list of filterable commands.
|
||||
_searchBox().Text(L"");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieve the list of commands that we should currently be filtering.
|
||||
// * If the user has command with subcommands, this will return that command's subcommands.
|
||||
|
@ -282,9 +387,17 @@ namespace winrt::TerminalApp::implementation
|
|||
switch (_currentMode)
|
||||
{
|
||||
case CommandPaletteMode::ActionMode:
|
||||
if (_nestedActionStack.Size() > 0)
|
||||
{
|
||||
return _currentNestedCommands;
|
||||
}
|
||||
|
||||
return _allCommands;
|
||||
case CommandPaletteMode::TabSwitcherMode:
|
||||
case CommandPaletteMode::TabSearchMode:
|
||||
case CommandPaletteMode::TabSwitchMode:
|
||||
return _allTabActions;
|
||||
case CommandPaletteMode::CommandlineMode:
|
||||
return winrt::single_threaded_vector<TerminalApp::Command>();
|
||||
default:
|
||||
return _allCommands;
|
||||
}
|
||||
|
@ -303,20 +416,111 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
if (command)
|
||||
{
|
||||
// Close before we dispatch so that actions that open the command
|
||||
// palette like the Tab Switcher will be able to have the last laugh.
|
||||
if (command.HasNestedCommands())
|
||||
{
|
||||
// If this Command had subcommands, then don't dispatch the
|
||||
// action. Instead, display a new list of commands for the user
|
||||
// to pick from.
|
||||
_nestedActionStack.Append(command);
|
||||
ParentCommandName(command.Name());
|
||||
_currentNestedCommands.Clear();
|
||||
for (const auto& nameAndCommand : command.NestedCommands())
|
||||
{
|
||||
_currentNestedCommands.Append(nameAndCommand.Value());
|
||||
}
|
||||
|
||||
_updateUIForStackChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
// First stash the search text length, because _close will clear this.
|
||||
const auto searchTextLength = _searchBox().Text().size();
|
||||
|
||||
// An action from the root command list has depth=0
|
||||
const auto nestedCommandDepth = _nestedActionStack.Size();
|
||||
|
||||
// Close before we dispatch so that actions that open the command
|
||||
// palette like the Tab Switcher will be able to have the last laugh.
|
||||
_close();
|
||||
|
||||
const auto actionAndArgs = command.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
"CommandPaletteDispatchedAction",
|
||||
TraceLoggingDescription("Event emitted when the user selects an action in the Command Palette"),
|
||||
TraceLoggingUInt32(searchTextLength, "SearchTextLength", "Number of characters in the search string"),
|
||||
TraceLoggingUInt32(nestedCommandDepth, "NestedCommandDepth", "the depth in the tree of commands for the dispatched action"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get all the input text in _searchBox that follows the prefix character
|
||||
// and any whitespace following that prefix character. This can be used in
|
||||
// commandline mode to get all the useful input that the user input after
|
||||
// the leading ">" prefix.
|
||||
// - Note that this will behave unexpectedly in Action Mode.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the string of input following the prefix character.
|
||||
std::wstring CommandPalette::_getPostPrefixInput()
|
||||
{
|
||||
const std::wstring input{ _searchBox().Text() };
|
||||
if (input.empty())
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
const auto rawCmdline{ input.substr(1) };
|
||||
|
||||
// Trim leading whitespace
|
||||
const auto firstNonSpace = rawCmdline.find_first_not_of(L" ");
|
||||
if (firstNonSpace == std::wstring::npos)
|
||||
{
|
||||
// All the following characters are whitespace.
|
||||
return L"";
|
||||
}
|
||||
|
||||
return rawCmdline.substr(firstNonSpace);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Dispatch the current search text as a ExecuteCommandline action.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_dispatchCommandline()
|
||||
{
|
||||
const auto input = _getPostPrefixInput();
|
||||
if (input.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
winrt::hstring cmdline{ input };
|
||||
|
||||
// Build the ExecuteCommandline action from the values we've parsed on the commandline.
|
||||
auto executeActionAndArgs = winrt::make_self<implementation::ActionAndArgs>();
|
||||
executeActionAndArgs->Action(ShortcutAction::ExecuteCommandline);
|
||||
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
|
||||
args->Commandline(cmdline);
|
||||
executeActionAndArgs->Args(*args);
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
"CommandPaletteDispatchedCommandline",
|
||||
TraceLoggingDescription("Event emitted when the user runs a commandline in the Command Palette"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
|
||||
if (_dispatch.DoAction(*executeActionAndArgs))
|
||||
{
|
||||
_close();
|
||||
|
||||
const auto actionAndArgs = command.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
"CommandPaletteDispatchedAction",
|
||||
TraceLoggingDescription("Event emitted when the user selects an action in the Command Palette"),
|
||||
TraceLoggingUInt32(_searchBox().Text().size(), "SearchTextLength", "Number of characters in the search string"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -350,17 +554,46 @@ namespace winrt::TerminalApp::implementation
|
|||
void CommandPalette::_filterTextChanged(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
|
||||
{
|
||||
if (_currentMode == CommandPaletteMode::CommandlineMode || _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
_evaluatePrefix();
|
||||
}
|
||||
|
||||
_updateFilteredActions();
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
|
||||
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
|
||||
void CommandPalette::_evaluatePrefix()
|
||||
{
|
||||
auto newMode = CommandPaletteMode::ActionMode;
|
||||
|
||||
auto inputText = _searchBox().Text();
|
||||
if (inputText.size() > 0)
|
||||
{
|
||||
if (inputText[0] == L'>')
|
||||
{
|
||||
newMode = CommandPaletteMode::CommandlineMode;
|
||||
}
|
||||
}
|
||||
|
||||
if (newMode != _currentMode)
|
||||
{
|
||||
_switchToMode(newMode);
|
||||
}
|
||||
}
|
||||
|
||||
Collections::IObservableVector<TerminalApp::Command> CommandPalette::FilteredActions()
|
||||
{
|
||||
return _filteredActions;
|
||||
}
|
||||
|
||||
void CommandPalette::SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings)
|
||||
{
|
||||
_bindings = bindings;
|
||||
}
|
||||
|
||||
void CommandPalette::SetCommands(Collections::IVector<TerminalApp::Command> const& actions)
|
||||
{
|
||||
_allCommands = actions;
|
||||
|
@ -389,23 +622,31 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
_filteredActions.Append(action);
|
||||
}
|
||||
}
|
||||
|
||||
switch (_currentMode)
|
||||
{
|
||||
case CommandPaletteMode::TabSwitcherMode:
|
||||
{
|
||||
SearchBoxText(RS_(L"TabSwitcher_SearchBoxText"));
|
||||
NoMatchesText(RS_(L"TabSwitcher_NoMatchesText"));
|
||||
ControlName(RS_(L"TabSwitcherControlName"));
|
||||
break;
|
||||
}
|
||||
case CommandPaletteMode::ActionMode:
|
||||
default:
|
||||
SearchBoxText(RS_(L"CommandPalette_SearchBox/PlaceholderText"));
|
||||
NoMatchesText(RS_(L"CommandPalette_NoMatchesText/Text"));
|
||||
ControlName(RS_(L"CommandPaletteControlName"));
|
||||
break;
|
||||
}
|
||||
// Leaving this block of code outside the above if-statement
|
||||
// guarantees that the correct text is shown for the mode
|
||||
// whenever _switchToMode is called.
|
||||
switch (_currentMode)
|
||||
{
|
||||
case CommandPaletteMode::TabSearchMode:
|
||||
case CommandPaletteMode::TabSwitchMode:
|
||||
{
|
||||
SearchBoxText(RS_(L"TabSwitcher_SearchBoxText"));
|
||||
NoMatchesText(RS_(L"TabSwitcher_NoMatchesText"));
|
||||
ControlName(RS_(L"TabSwitcherControlName"));
|
||||
break;
|
||||
}
|
||||
case CommandPaletteMode::CommandlineMode:
|
||||
NoMatchesText(RS_(L"CmdPalCommandlinePrompt"));
|
||||
ControlName(RS_(L"CommandPaletteControlName"));
|
||||
break;
|
||||
case CommandPaletteMode::ActionMode:
|
||||
default:
|
||||
SearchBoxText(RS_(L"CommandPalette_SearchBox/PlaceholderText"));
|
||||
NoMatchesText(RS_(L"CommandPalette_NoMatchesText/Text"));
|
||||
ControlName(RS_(L"CommandPaletteControlName"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -468,7 +709,7 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
// If TabSwitcherMode, just add all as is. We don't want
|
||||
// them to be sorted alphabetically.
|
||||
if (_currentMode == CommandPaletteMode::TabSwitcherMode)
|
||||
if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
for (auto action : commandsToFilter)
|
||||
{
|
||||
|
@ -555,6 +796,12 @@ namespace winrt::TerminalApp::implementation
|
|||
// - <none>
|
||||
void CommandPalette::_updateFilteredActions()
|
||||
{
|
||||
if (_currentMode == CommandPaletteMode::CommandlineMode)
|
||||
{
|
||||
_filteredActions.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
auto actions = _collectFilteredActions();
|
||||
|
||||
// Make _filteredActions look identical to actions, using only Insert and Remove.
|
||||
|
@ -701,6 +948,11 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
// Clear the text box each time we close the dialog. This is consistent with VsCode.
|
||||
_searchBox().Text(L"");
|
||||
|
||||
_nestedActionStack.Clear();
|
||||
|
||||
ParentCommandName(L"");
|
||||
_currentNestedCommands.Clear();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -766,14 +1018,11 @@ namespace winrt::TerminalApp::implementation
|
|||
// - <none>
|
||||
void CommandPalette::UpdateTabIndices(const uint32_t startIdx)
|
||||
{
|
||||
if (startIdx != _allTabActions.Size() - 1)
|
||||
for (auto i = startIdx; i < _allTabActions.Size(); ++i)
|
||||
{
|
||||
for (auto i = startIdx; i < _allTabActions.Size(); ++i)
|
||||
{
|
||||
auto command = _allTabActions.GetAt(i);
|
||||
auto command = _allTabActions.GetAt(i);
|
||||
|
||||
command.Action().Args().as<implementation::SwitchToTabArgs>()->TabIndex(i);
|
||||
}
|
||||
command.Action().Args().as<implementation::SwitchToTabArgs>()->TabIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -835,11 +1084,19 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
void CommandPalette::EnableTabSwitcherMode(const VirtualKey& anchorKey, const uint32_t startIdx)
|
||||
void CommandPalette::EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx)
|
||||
{
|
||||
_switcherStartIdx = startIdx;
|
||||
_anchorKey = anchorKey;
|
||||
_switchToMode(CommandPaletteMode::TabSwitcherMode);
|
||||
|
||||
if (searchMode)
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::TabSearchMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::TabSwitchMode);
|
||||
}
|
||||
|
||||
_updateFilteredActions();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ namespace winrt::TerminalApp::implementation
|
|||
enum class CommandPaletteMode
|
||||
{
|
||||
ActionMode = 0,
|
||||
TabSwitcherMode
|
||||
TabSearchMode,
|
||||
TabSwitchMode,
|
||||
CommandlineMode
|
||||
};
|
||||
|
||||
struct CommandPalette : CommandPaletteT<CommandPalette>
|
||||
|
@ -21,25 +23,34 @@ namespace winrt::TerminalApp::implementation
|
|||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> FilteredActions();
|
||||
|
||||
void SetCommands(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& actions);
|
||||
void SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings);
|
||||
|
||||
void EnableCommandPaletteMode();
|
||||
|
||||
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
|
||||
|
||||
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
|
||||
|
||||
void SelectNextItem(const bool moveDown);
|
||||
|
||||
// Tab Switcher
|
||||
void EnableTabSwitcherMode(const Windows::System::VirtualKey& anchorKey, const uint32_t startIdx);
|
||||
void EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx);
|
||||
void OnTabsChanged(const Windows::Foundation::IInspectable& s, const Windows::Foundation::Collections::IVectorChangedEventArgs& e);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, SearchBoxText, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ControlName, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ParentCommandName, _PropertyChangedHandlers);
|
||||
|
||||
private:
|
||||
friend struct CommandPaletteT<CommandPalette>; // for Xaml to bind events
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _filteredActions{ nullptr };
|
||||
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allCommands{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _currentNestedCommands{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _filteredActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _nestedActionStack{ nullptr };
|
||||
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _commandsToFilter();
|
||||
|
@ -53,32 +64,39 @@ namespace winrt::TerminalApp::implementation
|
|||
void _keyUpHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
|
||||
void _updateUIForStackChange();
|
||||
|
||||
void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _backdropPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
|
||||
void _listItemClicked(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::ItemClickEventArgs const& e);
|
||||
|
||||
void _selectNextItem(const bool moveDown);
|
||||
|
||||
void _updateFilteredActions();
|
||||
|
||||
std::vector<winrt::TerminalApp::Command> _collectFilteredActions();
|
||||
|
||||
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
|
||||
void _close();
|
||||
|
||||
CommandPaletteMode _currentMode;
|
||||
void _switchToMode(CommandPaletteMode mode);
|
||||
|
||||
void _evaluatePrefix();
|
||||
std::wstring _getPostPrefixInput();
|
||||
|
||||
Microsoft::Terminal::TerminalControl::IKeyBindings _bindings;
|
||||
|
||||
// Tab Switcher
|
||||
std::optional<winrt::Windows::System::VirtualKey> _anchorKey;
|
||||
void GenerateCommandForTab(const uint32_t idx, bool inserted, winrt::TerminalApp::Tab& tab);
|
||||
void UpdateTabIndices(const uint32_t startIdx);
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allTabActions{ nullptr };
|
||||
uint32_t _switcherStartIdx;
|
||||
void _anchorKeyUpHandler();
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker;
|
||||
|
||||
void _dispatchCommand(const TerminalApp::Command& command);
|
||||
|
||||
void _dispatchCommandline();
|
||||
void _dismissPalette();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../Command.idl";
|
||||
import "Command.idl";
|
||||
import "IDirectKeyListener.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
String NoMatchesText { get; };
|
||||
String SearchBoxText { get; };
|
||||
String ControlName { get; };
|
||||
String ParentCommandName { get; };
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<Command> FilteredActions { get; };
|
||||
|
||||
void SetCommands(Windows.Foundation.Collections.IVector<Command> actions);
|
||||
void SetKeyBindings(Microsoft.Terminal.TerminalControl.IKeyBindings bindings);
|
||||
void EnableCommandPaletteMode();
|
||||
|
||||
void SelectNextItem(Boolean moveDown);
|
||||
|
||||
void SetDispatch(ShortcutActionDispatch dispatch);
|
||||
|
||||
void EnableTabSwitcherMode(Windows.System.VirtualKey anchorKey, UInt32 startIdx);
|
||||
void EnableTabSwitcherMode(Boolean searchMode, UInt32 startIdx);
|
||||
void OnTabsChanged(IInspectable s, Windows.Foundation.Collections.IVectorChangedEventArgs e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1903="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 8)"
|
||||
TabNavigation="Cycle"
|
||||
IsTabStop="True"
|
||||
TabNavigation="Cycle"
|
||||
IsTabStop="True"
|
||||
AllowFocusOnInteraction="True"
|
||||
PointerPressed="_rootPointerPressed"
|
||||
PreviewKeyDown="_previewKeyDownHandler"
|
||||
|
@ -27,7 +27,9 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
<Windows10version1903:ThemeShadow x:Name="CommandPaletteShadow" />
|
||||
|
||||
<!-- This creates an instance of our CommandKeyChordVisibilityConverter we can reference below -->
|
||||
<local:CommandKeyChordVisibilityConverter x:Key="CommandKeyChordVisibilityConverter"/>
|
||||
<local:EmptyStringVisibilityConverter x:Key="CommandKeyChordVisibilityConverter"/>
|
||||
<local:EmptyStringVisibilityConverter x:Key="ParentCommandVisibilityConverter"/>
|
||||
<local:HasNestedCommandsVisibilityConverter x:Key="HasNestedCommandsVisibilityConverter"/>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
|
@ -170,12 +172,23 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
</TextBox>
|
||||
|
||||
<TextBlock
|
||||
Padding="16"
|
||||
x:Name="_noMatchesText"
|
||||
FontStyle="Italic"
|
||||
Visibility="Collapsed"
|
||||
Grid.Row="1"
|
||||
Text="{x:Bind NoMatchesText, Mode=OneWay}">
|
||||
Padding="16, 0, 16, 4"
|
||||
x:Name="_parentCommandText"
|
||||
FontStyle="Italic"
|
||||
Visibility="{x:Bind ParentCommandName,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource ParentCommandVisibilityConverter}}"
|
||||
Grid.Row="1"
|
||||
Text="{x:Bind ParentCommandName, Mode=OneWay}">
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Padding="16"
|
||||
x:Name="_noMatchesText"
|
||||
FontStyle="Italic"
|
||||
Visibility="Collapsed"
|
||||
Grid.Row="1"
|
||||
Text="{x:Bind NoMatchesText, Mode=OneWay}">
|
||||
</TextBlock>
|
||||
|
||||
<ListView
|
||||
|
@ -195,7 +208,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
<DataTemplate x:DataType="local:Command">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}">
|
||||
|
@ -210,32 +223,49 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<IconSourceElement
|
||||
Grid.Column="0"
|
||||
IconSource="{x:Bind IconSource, Mode=OneWay}"/>
|
||||
Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind IconSource, Mode=OneWay}"/>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
|
||||
<!-- The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details. -->
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details. -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Visibility="{x:Bind KeyChordText,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource CommandKeyChordVisibilityConverter}}"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
Grid.Column="2"
|
||||
Visibility="{x:Bind KeyChordText,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource CommandKeyChordVisibilityConverter}}"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}" />
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
<!-- xE70E is ChevronUp. Rotated 90 degrees, it's _ChevronRight_ -->
|
||||
<FontIcon
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Glyph=""
|
||||
HorizontalAlignment="Right"
|
||||
Visibility="{x:Bind HasNestedCommands,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource HasNestedCommandsVisibilityConverter}}"
|
||||
Grid.Column="2">
|
||||
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform CenterX="0.5" CenterY="0.5" Angle="90"/>
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "pch.h"
|
||||
#include "CommandKeyChordVisibilityConverter.h"
|
||||
#include "CommandKeyChordVisibilityConverter.g.cpp"
|
||||
#include "EmptyStringVisibilityConverter.h"
|
||||
#include "EmptyStringVisibilityConverter.g.cpp"
|
||||
|
||||
using namespace winrt::Windows;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
@ -9,7 +9,7 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
// Method Description:
|
||||
// - Attempt to convert something into another type. For the
|
||||
// CommandKeyChordVisibilityConverter, we're gonna check if `value` is a
|
||||
// EmptyStringVisibilityConverter, we're gonna check if `value` is a
|
||||
// string, and try and convert it into a Visibility value. If the input
|
||||
// param wasn't a string, or was the empty string, we'll return
|
||||
// Visibility::Collapsed. Otherwise, we'll return Visible.
|
||||
|
@ -18,20 +18,20 @@ namespace winrt::TerminalApp::implementation
|
|||
// - value: the input object to attempt to convert into a Visibility.
|
||||
// Return Value:
|
||||
// - Visible if the object was a string and wasn't the empty string.
|
||||
Foundation::IInspectable CommandKeyChordVisibilityConverter::Convert(Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
Foundation::IInspectable EmptyStringVisibilityConverter::Convert(Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
{
|
||||
const auto& name = winrt::unbox_value_or<hstring>(value, L"");
|
||||
return winrt::box_value(name.empty() ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
|
||||
// unused for one-way bindings
|
||||
Foundation::IInspectable CommandKeyChordVisibilityConverter::ConvertBack(Foundation::IInspectable const& /* value */,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
Foundation::IInspectable EmptyStringVisibilityConverter::ConvertBack(Foundation::IInspectable const& /* value */,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
{
|
||||
throw hresult_not_implemented();
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "CommandKeyChordVisibilityConverter.g.h"
|
||||
#include "EmptyStringVisibilityConverter.g.h"
|
||||
#include "..\inc\cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct CommandKeyChordVisibilityConverter : CommandKeyChordVisibilityConverterT<CommandKeyChordVisibilityConverter>
|
||||
struct EmptyStringVisibilityConverter : EmptyStringVisibilityConverterT<EmptyStringVisibilityConverter>
|
||||
{
|
||||
CommandKeyChordVisibilityConverter() = default;
|
||||
EmptyStringVisibilityConverter() = default;
|
||||
|
||||
Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& targetType,
|
||||
|
@ -23,5 +23,5 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(CommandKeyChordVisibilityConverter);
|
||||
BASIC_FACTORY(EmptyStringVisibilityConverter);
|
||||
}
|
|
@ -6,14 +6,14 @@ namespace TerminalApp
|
|||
// See https://docs.microsoft.com/en-us/windows/uwp/data-binding/data-binding-quickstart
|
||||
|
||||
// We use the default attribute to declare IValueConverter as the default
|
||||
// interface. In the listing, CommandKeyChordVisibilityConverter has only a
|
||||
// interface. In the listing, EmptyStringVisibilityConverter has only a
|
||||
// constructor, and no methods, so no default interface is generated for it.
|
||||
// The default attribute is optimal if you won't be adding instance members
|
||||
// to CommandKeyChordVisibilityConverter, because no QueryInterface will be
|
||||
// to EmptyStringVisibilityConverter, because no QueryInterface will be
|
||||
// required to call the IValueConverter methods
|
||||
runtimeclass CommandKeyChordVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter
|
||||
runtimeclass EmptyStringVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
CommandKeyChordVisibilityConverter();
|
||||
EmptyStringVisibilityConverter();
|
||||
};
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@ using namespace ::Microsoft::Console;
|
|||
using namespace winrt::Microsoft::UI::Xaml::Controls;
|
||||
|
||||
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view BindingsKey{ "bindings" };
|
||||
static constexpr std::string_view ActionsKey{ "actions" };
|
||||
static constexpr std::string_view DefaultProfileKey{ "defaultProfile" };
|
||||
static constexpr std::string_view AlwaysShowTabsKey{ "alwaysShowTabs" };
|
||||
static constexpr std::string_view InitialRowsKey{ "initialRows" };
|
||||
|
@ -36,6 +36,7 @@ static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" };
|
|||
static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" };
|
||||
static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" };
|
||||
static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" };
|
||||
static constexpr std::string_view UseTabSwitcherKey{ "useTabSwitcher" };
|
||||
|
||||
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
|
||||
|
||||
|
@ -60,6 +61,7 @@ GlobalAppSettings::GlobalAppSettings() :
|
|||
_WordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||
_DebugFeaturesEnabled{ debugFeaturesDefault }
|
||||
{
|
||||
_commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
|
||||
}
|
||||
|
||||
GlobalAppSettings::~GlobalAppSettings()
|
||||
|
@ -179,6 +181,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
|||
|
||||
JsonUtils::GetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop);
|
||||
|
||||
JsonUtils::GetValueForKey(json, UseTabSwitcherKey, _UseTabSwitcher);
|
||||
|
||||
// This is a helper lambda to get the keybindings and commands out of both
|
||||
// and array of objects. We'll use this twice, once on the legacy
|
||||
// `keybindings` key, and again on the newer `bindings` key.
|
||||
|
@ -201,7 +205,7 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
|||
}
|
||||
};
|
||||
parseBindings(LegacyKeybindingsKey);
|
||||
parseBindings(BindingsKey);
|
||||
parseBindings(ActionsKey);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -212,7 +216,7 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
|||
// - <none>
|
||||
void GlobalAppSettings::AddColorScheme(ColorScheme scheme)
|
||||
{
|
||||
std::wstring name{ scheme.GetName() };
|
||||
std::wstring name{ scheme.Name() };
|
||||
_colorSchemes[name] = std::move(scheme);
|
||||
}
|
||||
|
||||
|
@ -230,7 +234,12 @@ std::vector<TerminalApp::SettingsLoadWarnings> GlobalAppSettings::GetKeybindings
|
|||
return _keybindingsWarnings;
|
||||
}
|
||||
|
||||
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GlobalAppSettings::GetCommands() const noexcept
|
||||
const winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command>& GlobalAppSettings::GetCommands() const noexcept
|
||||
{
|
||||
return _commands;
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command>& GlobalAppSettings::GetCommands() noexcept
|
||||
{
|
||||
return _commands;
|
||||
}
|
||||
|
|
|
@ -15,10 +15,11 @@ Author(s):
|
|||
--*/
|
||||
#pragma once
|
||||
#include "AppKeyBindings.h"
|
||||
#include "ColorScheme.h"
|
||||
#include "Command.h"
|
||||
#include "SettingsTypes.h"
|
||||
|
||||
#include "ColorScheme.g.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
|
@ -37,9 +38,9 @@ public:
|
|||
GlobalAppSettings();
|
||||
~GlobalAppSettings();
|
||||
|
||||
std::unordered_map<std::wstring, ColorScheme>& GetColorSchemes() noexcept;
|
||||
const std::unordered_map<std::wstring, ColorScheme>& GetColorSchemes() const noexcept;
|
||||
void AddColorScheme(ColorScheme scheme);
|
||||
std::unordered_map<std::wstring, winrt::TerminalApp::ColorScheme>& GetColorSchemes() noexcept;
|
||||
const std::unordered_map<std::wstring, winrt::TerminalApp::ColorScheme>& GetColorSchemes() const noexcept;
|
||||
void AddColorScheme(winrt::TerminalApp::ColorScheme scheme);
|
||||
|
||||
winrt::TerminalApp::AppKeyBindings GetKeybindings() const noexcept;
|
||||
|
||||
|
@ -50,7 +51,8 @@ public:
|
|||
|
||||
std::vector<TerminalApp::SettingsLoadWarnings> GetKeybindingsWarnings() const;
|
||||
|
||||
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GetCommands() const noexcept;
|
||||
const winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command>& GetCommands() const noexcept;
|
||||
winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command>& GetCommands() noexcept;
|
||||
|
||||
// These are implemented manually to handle the string/GUID exchange
|
||||
// by higher layers in the app.
|
||||
|
@ -68,7 +70,7 @@ public:
|
|||
GETSET_PROPERTY(bool, ShowTabsInTitlebar, true);
|
||||
GETSET_PROPERTY(std::wstring, WordDelimiters); // default value set in constructor
|
||||
GETSET_PROPERTY(bool, CopyOnSelect, false);
|
||||
GETSET_PROPERTY(bool, CopyFormatting, false);
|
||||
GETSET_PROPERTY(winrt::Microsoft::Terminal::TerminalControl::CopyFormat, CopyFormatting, 0);
|
||||
GETSET_PROPERTY(bool, WarnAboutLargePaste, true);
|
||||
GETSET_PROPERTY(bool, WarnAboutMultiLinePaste, true);
|
||||
GETSET_PROPERTY(LaunchPosition, InitialPosition);
|
||||
|
@ -80,6 +82,7 @@ public:
|
|||
GETSET_PROPERTY(bool, DebugFeaturesEnabled); // default value set in constructor
|
||||
GETSET_PROPERTY(bool, StartOnUserLogin, false);
|
||||
GETSET_PROPERTY(bool, AlwaysOnTop, false);
|
||||
GETSET_PROPERTY(bool, UseTabSwitcher, true);
|
||||
|
||||
private:
|
||||
std::optional<std::wstring> _unparsedDefaultProfile;
|
||||
|
@ -88,8 +91,8 @@ private:
|
|||
winrt::com_ptr<winrt::TerminalApp::implementation::AppKeyBindings> _keybindings;
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> _keybindingsWarnings;
|
||||
|
||||
std::unordered_map<std::wstring, ColorScheme> _colorSchemes;
|
||||
std::unordered_map<winrt::hstring, winrt::TerminalApp::Command> _commands;
|
||||
std::unordered_map<std::wstring, winrt::TerminalApp::ColorScheme> _colorSchemes;
|
||||
winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::TerminalApp::Command> _commands;
|
||||
|
||||
friend class TerminalAppLocalTests::SettingsTests;
|
||||
friend class TerminalAppLocalTests::ColorSchemeTests;
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
#include "pch.h"
|
||||
#include "HasNestedCommandsVisibilityConverter.h"
|
||||
#include "HasNestedCommandsVisibilityConverter.g.cpp"
|
||||
|
||||
using namespace winrt::Windows;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Method Description:
|
||||
// - Attempt to convert something into another type. For the
|
||||
// HasNestedCommandsVisibilityConverter, we're gonna check if `value` is a
|
||||
// string, and try and convert it into a Visibility value. If the input
|
||||
// param wasn't a string, or was the empty string, we'll return
|
||||
// Visibility::Collapsed. Otherwise, we'll return Visible.
|
||||
|
||||
// Arguments:
|
||||
// - value: the input object to attempt to convert into a Visibility.
|
||||
// Return Value:
|
||||
// - Visible if the object was a string and wasn't the empty string.
|
||||
Foundation::IInspectable HasNestedCommandsVisibilityConverter::Convert(Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
{
|
||||
const auto& hasNestedCommands = winrt::unbox_value_or<bool>(value, false);
|
||||
return winrt::box_value(hasNestedCommands ? Visibility::Visible : Visibility::Collapsed);
|
||||
}
|
||||
|
||||
// unused for one-way bindings
|
||||
Foundation::IInspectable HasNestedCommandsVisibilityConverter::ConvertBack(Foundation::IInspectable const& /* value */,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
{
|
||||
throw hresult_not_implemented();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "HasNestedCommandsVisibilityConverter.g.h"
|
||||
#include "..\inc\cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct HasNestedCommandsVisibilityConverter : HasNestedCommandsVisibilityConverterT<HasNestedCommandsVisibilityConverter>
|
||||
{
|
||||
HasNestedCommandsVisibilityConverter() = default;
|
||||
|
||||
Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& targetType,
|
||||
Windows::Foundation::IInspectable const& parameter,
|
||||
hstring const& language);
|
||||
|
||||
Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& targetType,
|
||||
Windows::Foundation::IInspectable const& parameter,
|
||||
hstring const& language);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(HasNestedCommandsVisibilityConverter);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
// See https://docs.microsoft.com/en-us/windows/uwp/data-binding/data-binding-quickstart
|
||||
|
||||
// We use the default attribute to declare IValueConverter as the default
|
||||
// interface. In the listing, HasNestedCommandsVisibilityConverter has only a
|
||||
// constructor, and no methods, so no default interface is generated for it.
|
||||
// The default attribute is optimal if you won't be adding instance members
|
||||
// to HasNestedCommandsVisibilityConverter, because no QueryInterface will be
|
||||
// required to call the IValueConverter methods
|
||||
runtimeclass HasNestedCommandsVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
HasNestedCommandsVisibilityConverter();
|
||||
};
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@ namespace TerminalApp
|
|||
// If you update this one, please update the one in TerminalControl\TermControl.idl
|
||||
// If you change this interface, please update the guid.
|
||||
// If you press F7 or Alt and get a runtime error, go make sure both copies are the same.
|
||||
[uuid("339e1a87-5315-4da6-96f0-565549b6472b")] interface IDirectKeyListener {
|
||||
Boolean OnDirectKeyEvent(UInt32 vkey, Boolean down);
|
||||
[uuid("0ddf4edc-3fda-4dee-97ca-a417ee3dd510")] interface IDirectKeyListener {
|
||||
Boolean OnDirectKeyEvent(UInt32 vkey, UInt8 scanCode, Boolean down);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -372,7 +372,7 @@ namespace TerminalApp::JsonUtils
|
|||
{
|
||||
// attempt to combine AllClear (explicitly) with anything else
|
||||
DeserializationError e{ element };
|
||||
e.expectedType = TypeDescription();
|
||||
e.expectedType = BaseEnumMapper::TypeDescription();
|
||||
throw e;
|
||||
}
|
||||
value |= newFlag;
|
||||
|
|
|
@ -14,11 +14,13 @@ static constexpr int MAX_CHORD_PARTS = 4;
|
|||
|
||||
// clang-format off
|
||||
static const std::unordered_map<std::wstring_view, int32_t> vkeyNamePairs {
|
||||
{ L"app" , VK_APPS },
|
||||
{ L"backspace" , VK_BACK },
|
||||
{ L"tab" , VK_TAB },
|
||||
{ L"enter" , VK_RETURN },
|
||||
{ L"esc" , VK_ESCAPE },
|
||||
{ L"escape" , VK_ESCAPE },
|
||||
{ L"menu" , VK_APPS },
|
||||
{ L"space" , VK_SPACE },
|
||||
{ L"pgup" , VK_PRIOR },
|
||||
{ L"pageup" , VK_PRIOR },
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "..\TitlebarControl.idl";
|
||||
import "TitlebarControl.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
|
|
|
@ -14,7 +14,7 @@ Author(s):
|
|||
|
||||
--*/
|
||||
#pragma once
|
||||
#include "ColorScheme.h"
|
||||
#include "ColorScheme.g.h"
|
||||
#include "SettingsTypes.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
|
@ -46,7 +46,7 @@ public:
|
|||
|
||||
~Profile();
|
||||
|
||||
winrt::TerminalApp::TerminalSettings CreateTerminalSettings(const std::unordered_map<std::wstring, ColorScheme>& schemes) const;
|
||||
winrt::TerminalApp::TerminalSettings CreateTerminalSettings(const std::unordered_map<std::wstring, winrt::TerminalApp::ColorScheme>& schemes) const;
|
||||
|
||||
Json::Value GenerateStub() const;
|
||||
static Profile FromJson(const Json::Value& json);
|
||||
|
|
|
@ -222,6 +222,10 @@
|
|||
<data name="LegacyGlobalsPropertyHrefLabel" xml:space="preserve">
|
||||
<value>For more info, see this web page.</value>
|
||||
</data>
|
||||
<data name="FailedToParseCommandJson" xml:space="preserve">
|
||||
<value>Failed to expand a command with "iterateOn" set. This command will be ignored.</value>
|
||||
<comment>{Locked="\"iterateOn\""} </comment>
|
||||
</data>
|
||||
<data name="CmdCommandArgDesc" xml:space="preserve">
|
||||
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
|
||||
</data>
|
||||
|
@ -569,18 +573,31 @@
|
|||
<value>Close tabs after index {0}</value>
|
||||
<comment>{0} will be replaced with a number</comment>
|
||||
</data>
|
||||
<data name="SetColorSchemeParentCommandName" xml:space="preserve">
|
||||
<value>Select color scheme...</value>
|
||||
</data>
|
||||
<data name="TabSearchCommandKey" xml:space="preserve">
|
||||
<value>Search for tab...</value>
|
||||
</data>
|
||||
<data name="NewTabParentCommandName" xml:space="preserve">
|
||||
<value>New Tab...</value>
|
||||
</data>
|
||||
<data name="SplitPaneParentCommandName" xml:space="preserve">
|
||||
<value>Split Pane...</value>
|
||||
</data>
|
||||
<data name="TabSwitcherControlName" xml:space="preserve">
|
||||
<value>Tab Switcher</value>
|
||||
</data>
|
||||
<data name="ToggleTabSwitcherCommandKey" xml:space="preserve">
|
||||
<value>Toggle tab switcher</value>
|
||||
</data>
|
||||
<data name="TabSwitcher_SearchBoxText" xml:space="preserve">
|
||||
<value>Type a tab name...</value>
|
||||
</data>
|
||||
<data name="TabSwitcher_NoMatchesText" xml:space="preserve">
|
||||
<value>No matching tab name</value>
|
||||
</data>
|
||||
<data name="CmdPalCommandlinePrompt" xml:space="preserve">
|
||||
<value>Enter a wt commandline to run</value>
|
||||
<comment>{Locked="wt"} </comment>
|
||||
</data>
|
||||
<data name="CrimsonColorButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Crimson</value>
|
||||
</data>
|
||||
|
@ -629,4 +646,10 @@
|
|||
<data name="DarkGrayColorButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Dark Gray</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="CloseOtherTabsDefaultCommandKey" xml:space="preserve">
|
||||
<value>Close all other tabs</value>
|
||||
</data>
|
||||
<data name="CloseTabsAfterDefaultCommandKey" xml:space="preserve">
|
||||
<value>Close all tabs after the current tab</value>
|
||||
</data>
|
||||
</root>
|
|
@ -25,4 +25,11 @@ namespace TerminalApp
|
|||
std::optional<int> x;
|
||||
std::optional<int> y;
|
||||
};
|
||||
|
||||
enum class ExpandCommandType : uint32_t
|
||||
{
|
||||
None = 0,
|
||||
Profiles,
|
||||
ColorSchemes
|
||||
};
|
||||
};
|
||||
|
|
|
@ -226,9 +226,9 @@ namespace winrt::TerminalApp::implementation
|
|||
_CloseTabsAfterHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::ToggleTabSwitcher:
|
||||
case ShortcutAction::TabSearch:
|
||||
{
|
||||
_ToggleTabSwitcherHandlers(*this, *eventArgs);
|
||||
_TabSearchHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -60,7 +60,7 @@ namespace winrt::TerminalApp::implementation
|
|||
TYPED_EVENT(ExecuteCommandline, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseOtherTabs, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseTabsAfter, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ToggleTabSwitcher, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(TabSearch, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
import "../ActionArgs.idl";
|
||||
import "ActionArgs.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ namespace TerminalApp
|
|||
ToggleCommandPalette,
|
||||
CloseOtherTabs,
|
||||
CloseTabsAfter,
|
||||
ToggleTabSwitcher
|
||||
TabSearch
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionAndArgs {
|
||||
|
@ -97,6 +97,6 @@ namespace TerminalApp
|
|||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ExecuteCommandline;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> CloseOtherTabs;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> CloseTabsAfter;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ToggleTabSwitcher;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> TabSearch;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -960,7 +960,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// - <none>
|
||||
// Return Value:
|
||||
// - The total number of leaf panes hosted by this tab.
|
||||
int Tab::_GetLeafPaneCount() const noexcept
|
||||
int Tab::GetLeafPaneCount() const noexcept
|
||||
{
|
||||
return _rootPane->GetLeafPaneCount();
|
||||
}
|
||||
|
|
|
@ -66,6 +66,8 @@ namespace winrt::TerminalApp::implementation
|
|||
void EnterZoom();
|
||||
void ExitZoom();
|
||||
|
||||
int GetLeafPaneCount() const noexcept;
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
|
@ -102,7 +104,6 @@ namespace winrt::TerminalApp::implementation
|
|||
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
|
||||
|
||||
int _GetLeafPaneCount() const noexcept;
|
||||
void _UpdateActivePane(std::shared_ptr<Pane> pane);
|
||||
|
||||
void _UpdateTabHeader();
|
||||
|
|
|
@ -24,9 +24,9 @@
|
|||
<DisableEmbeddedXbf>false</DisableEmbeddedXbf>
|
||||
<XamlComponentResourceLocation>nested</XamlComponentResourceLocation>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
|
@ -42,180 +42,190 @@
|
|||
For any types that use XAML information, if their .idl and .h aren't
|
||||
marked DependentUpon the corresponding .xaml file, XamlTypeInfo.g.h won't
|
||||
pick it up correctly. -->
|
||||
<ApplicationDefinition Include="..\App.xaml">
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
</ItemGroup>
|
||||
<!-- When we add other user controls, they should go in here as so: -->
|
||||
<ItemGroup>
|
||||
<Page Include="../MinMaxCloseControl.xaml">
|
||||
<Page Include="MinMaxCloseControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../TerminalPage.xaml">
|
||||
<Page Include="TerminalPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../TitlebarControl.xaml">
|
||||
<Page Include="TitlebarControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../TabRowControl.xaml">
|
||||
<Page Include="TabRowControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../ColorPickupFlyout.xaml">
|
||||
<Page Include="ColorPickupFlyout.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../CommandPalette.xaml">
|
||||
<Page Include="CommandPalette.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="../App.base.h" />
|
||||
<ClInclude Include="../AppCommandlineArgs.h" />
|
||||
<ClInclude Include="../Commandline.h" />
|
||||
<ClInclude Include="../MinMaxCloseControl.h">
|
||||
<DependentUpon>../MinMaxCloseControl.xaml</DependentUpon>
|
||||
<ClInclude Include="App.base.h" />
|
||||
<ClInclude Include="AppCommandlineArgs.h" />
|
||||
<ClInclude Include="Commandline.h" />
|
||||
<ClInclude Include="MinMaxCloseControl.h">
|
||||
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../TerminalPage.h">
|
||||
<DependentUpon>../TerminalPage.xaml</DependentUpon>
|
||||
<ClInclude Include="TerminalPage.h">
|
||||
<DependentUpon>TerminalPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../TitlebarControl.h">
|
||||
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
|
||||
<ClInclude Include="TitlebarControl.h">
|
||||
<DependentUpon>TitlebarControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../TabRowControl.h">
|
||||
<DependentUpon>../TabRowControl.xaml</DependentUpon>
|
||||
<ClInclude Include="TabRowControl.h">
|
||||
<DependentUpon>TabRowControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../ColorPickupFlyout.h">
|
||||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
<ClInclude Include="ColorPickupFlyout.h">
|
||||
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../CommandPalette.h">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
<ClInclude Include="CommandPalette.h">
|
||||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../Command.h">
|
||||
<DependentUpon>../Command.idl</DependentUpon>
|
||||
<ClInclude Include="Command.h">
|
||||
<DependentUpon>Command.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../CommandKeyChordVisibilityConverter.h">
|
||||
<DependentUpon>../CommandKeyChordVisibilityConverter.idl</DependentUpon>
|
||||
<ClInclude Include="EmptyStringVisibilityConverter.h">
|
||||
<DependentUpon>EmptyStringVisibilityConverter.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../Tab.h">
|
||||
<DependentUpon>../Tab.idl</DependentUpon>
|
||||
<ClInclude Include="HasNestedCommandsVisibilityConverter.h">
|
||||
<DependentUpon>HasNestedCommandsVisibilityConverter.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../Pane.h" />
|
||||
<ClInclude Include="../ColorScheme.h" />
|
||||
<ClInclude Include="../GlobalAppSettings.h" />
|
||||
<ClInclude Include="../Profile.h" />
|
||||
<ClInclude Include="../CascadiaSettings.h" />
|
||||
<ClInclude Include="../KeyChordSerialization.h" />
|
||||
<ClInclude Include="../JsonUtils.h" />
|
||||
<ClInclude Include="../Utils.h" />
|
||||
<ClInclude Include="../DefaultProfileUtils.h" />
|
||||
<ClInclude Include="../TerminalSettingsSerializationHelpers.h" />
|
||||
<ClInclude Include="../TerminalWarnings.h" />
|
||||
<ClInclude Include="../IDynamicProfileGenerator.h" />
|
||||
<ClInclude Include="../PowershellCoreProfileGenerator.h" />
|
||||
<ClInclude Include="../WslDistroGenerator.h" />
|
||||
<ClInclude Include="../AzureCloudShellGenerator.h" />
|
||||
<ClInclude Include="../TelnetGenerator.h" />
|
||||
<ClInclude Include="../ColorHelper.h" />
|
||||
<ClInclude Include="../TerminalSettings.h">
|
||||
<DependentUpon>../TerminalSettings.idl</DependentUpon>
|
||||
<ClInclude Include="Tab.h">
|
||||
<DependentUpon>Tab.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Pane.h" />
|
||||
<ClInclude Include="ColorScheme.h">
|
||||
<DependentUpon>ColorScheme.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GlobalAppSettings.h" />
|
||||
<ClInclude Include="Profile.h" />
|
||||
<ClInclude Include="CascadiaSettings.h" />
|
||||
<ClInclude Include="KeyChordSerialization.h" />
|
||||
<ClInclude Include="JsonUtils.h" />
|
||||
<ClInclude Include="Utils.h" />
|
||||
<ClInclude Include="DefaultProfileUtils.h" />
|
||||
<ClInclude Include="TerminalSettingsSerializationHelpers.h" />
|
||||
<ClInclude Include="TerminalWarnings.h" />
|
||||
<ClInclude Include="IDynamicProfileGenerator.h" />
|
||||
<ClInclude Include="PowershellCoreProfileGenerator.h" />
|
||||
<ClInclude Include="WslDistroGenerator.h" />
|
||||
<ClInclude Include="AzureCloudShellGenerator.h" />
|
||||
<ClInclude Include="TelnetGenerator.h" />
|
||||
<ClInclude Include="ColorHelper.h" />
|
||||
<ClInclude Include="TerminalSettings.h">
|
||||
<DependentUpon>TerminalSettings.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="../ShortcutActionDispatch.h">
|
||||
<DependentUpon>../ShortcutActionDispatch.idl</DependentUpon>
|
||||
<ClInclude Include="ShortcutActionDispatch.h">
|
||||
<DependentUpon>ShortcutActionDispatch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../ActionArgs.h">
|
||||
<DependentUpon>../ActionArgs.idl</DependentUpon>
|
||||
<ClInclude Include="ActionArgs.h">
|
||||
<DependentUpon>ActionArgs.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../ActionAndArgs.h">
|
||||
<DependentUpon>../ActionArgs.idl</DependentUpon>
|
||||
<ClInclude Include="ActionAndArgs.h">
|
||||
<DependentUpon>ActionArgs.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../DebugTapConnection.h" />
|
||||
<ClInclude Include="../AppKeyBindings.h">
|
||||
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
|
||||
<ClInclude Include="DebugTapConnection.h" />
|
||||
<ClInclude Include="AppKeyBindings.h">
|
||||
<DependentUpon>AppKeyBindings.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../App.h">
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
<ClInclude Include="App.h">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../AppLogic.h">
|
||||
<DependentUpon>../AppLogic.idl</DependentUpon>
|
||||
<ClInclude Include="AppLogic.h">
|
||||
<DependentUpon>AppLogic.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
<ItemGroup>
|
||||
<ClCompile Include="../init.cpp" />
|
||||
<ClCompile Include="../AppCommandlineArgs.cpp" />
|
||||
<ClCompile Include="../Commandline.cpp" />
|
||||
<ClCompile Include="../MinMaxCloseControl.cpp">
|
||||
<DependentUpon>../MinMaxCloseControl.xaml</DependentUpon>
|
||||
<ClCompile Include="init.cpp" />
|
||||
<ClCompile Include="AppCommandlineArgs.cpp" />
|
||||
<ClCompile Include="Commandline.cpp" />
|
||||
<ClCompile Include="MinMaxCloseControl.cpp">
|
||||
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../TerminalPage.cpp">
|
||||
<DependentUpon>../TerminalPage.xaml</DependentUpon>
|
||||
<ClCompile Include="TerminalPage.cpp">
|
||||
<DependentUpon>TerminalPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../TitlebarControl.cpp">
|
||||
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
|
||||
<ClCompile Include="TitlebarControl.cpp">
|
||||
<DependentUpon>TitlebarControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../TabRowControl.cpp">
|
||||
<DependentUpon>../TabRowControl.xaml</DependentUpon>
|
||||
<ClCompile Include="TabRowControl.cpp">
|
||||
<DependentUpon>TabRowControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../ColorPickupFlyout.cpp">
|
||||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
<ClCompile Include="ColorPickupFlyout.cpp">
|
||||
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../CommandPalette.cpp">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
<ClCompile Include="CommandPalette.cpp">
|
||||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../Command.cpp">
|
||||
<DependentUpon>../Command.idl</DependentUpon>
|
||||
<ClCompile Include="Command.cpp">
|
||||
<DependentUpon>Command.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../CommandKeyChordVisibilityConverter.cpp">
|
||||
<DependentUpon>../CommandKeyChordVisibilityConverter.idl</DependentUpon>
|
||||
<ClCompile Include="EmptyStringVisibilityConverter.cpp">
|
||||
<DependentUpon>EmptyStringVisibilityConverter.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../Tab.cpp">
|
||||
<DependentUpon>../Tab.idl</DependentUpon>
|
||||
<ClCompile Include="HasNestedCommandsVisibilityConverter.cpp">
|
||||
<DependentUpon>HasNestedCommandsVisibilityConverter.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../Pane.cpp" />
|
||||
<ClCompile Include="../ColorScheme.cpp" />
|
||||
<ClCompile Include="../GlobalAppSettings.cpp" />
|
||||
<ClCompile Include="../Profile.cpp" />
|
||||
<ClCompile Include="../CascadiaSettings.cpp" />
|
||||
<ClCompile Include="../CascadiaSettingsSerialization.cpp" />
|
||||
<ClCompile Include="../AppKeyBindingsSerialization.cpp" />
|
||||
<ClCompile Include="../KeyChordSerialization.cpp" />
|
||||
<ClCompile Include="../DefaultProfileUtils.cpp" />
|
||||
<ClCompile Include="../PowershellCoreProfileGenerator.cpp" />
|
||||
<ClCompile Include="../WslDistroGenerator.cpp" />
|
||||
<ClCompile Include="../AzureCloudShellGenerator.cpp" />
|
||||
<ClCompile Include="../Pane.LayoutSizeNode.cpp" />
|
||||
<ClCompile Include="../ColorHelper.cpp" />
|
||||
<ClCompile Include="../DebugTapConnection.cpp" />
|
||||
<ClCompile Include="../Utils.cpp" />
|
||||
<ClCompile Include="../TerminalSettings.cpp">
|
||||
<DependentUpon>../TerminalSettings.idl</DependentUpon>
|
||||
<ClCompile Include="Tab.cpp">
|
||||
<DependentUpon>Tab.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Pane.cpp" />
|
||||
<ClCompile Include="ColorScheme.cpp">
|
||||
<DependentUpon>ColorScheme.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GlobalAppSettings.cpp" />
|
||||
<ClCompile Include="Profile.cpp" />
|
||||
<ClCompile Include="CascadiaSettings.cpp" />
|
||||
<ClCompile Include="CascadiaSettingsSerialization.cpp" />
|
||||
<ClCompile Include="AppKeyBindingsSerialization.cpp" />
|
||||
<ClCompile Include="KeyChordSerialization.cpp" />
|
||||
<ClCompile Include="DefaultProfileUtils.cpp" />
|
||||
<ClCompile Include="PowershellCoreProfileGenerator.cpp" />
|
||||
<ClCompile Include="WslDistroGenerator.cpp" />
|
||||
<ClCompile Include="AzureCloudShellGenerator.cpp" />
|
||||
<ClCompile Include="Pane.LayoutSizeNode.cpp" />
|
||||
<ClCompile Include="ColorHelper.cpp" />
|
||||
<ClCompile Include="DebugTapConnection.cpp" />
|
||||
<ClCompile Include="Utils.cpp" />
|
||||
<ClCompile Include="TerminalSettings.cpp">
|
||||
<DependentUpon>TerminalSettings.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../AppKeyBindings.cpp">
|
||||
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
|
||||
<ClCompile Include="AppKeyBindings.cpp">
|
||||
<DependentUpon>AppKeyBindings.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../ShortcutActionDispatch.cpp">
|
||||
<DependentUpon>../ShortcutActionDispatch.idl</DependentUpon>
|
||||
<ClCompile Include="ShortcutActionDispatch.cpp">
|
||||
<DependentUpon>ShortcutActionDispatch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../ActionAndArgs.cpp">
|
||||
<DependentUpon>../ActionArgs.idl</DependentUpon>
|
||||
<ClCompile Include="ActionAndArgs.cpp">
|
||||
<DependentUpon>ActionArgs.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../ActionArgs.cpp">
|
||||
<DependentUpon>../ActionArgs.idl</DependentUpon>
|
||||
<ClCompile Include="ActionArgs.cpp">
|
||||
<DependentUpon>ActionArgs.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../App.cpp">
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
<ClCompile Include="App.cpp">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../AppActionHandlers.cpp">
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
<ClCompile Include="AppActionHandlers.cpp">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../AppLogic.cpp">
|
||||
<DependentUpon>../AppLogic.idl</DependentUpon>
|
||||
<ClCompile Include="AppLogic.cpp">
|
||||
<DependentUpon>AppLogic.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
|
||||
you want to use jsoncpp -->
|
||||
|
@ -228,48 +238,50 @@
|
|||
<ItemGroup>
|
||||
<!-- If you add idl files here, make sure to include their implementation's
|
||||
header in TerminalApp.vcxproj (as well as in this file) -->
|
||||
<Midl Include="../IDirectKeyListener.idl" />
|
||||
<Midl Include="../App.idl">
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="App.idl">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</Midl>
|
||||
<Midl Include="../ShortcutActionDispatch.idl" />
|
||||
<Midl Include="../AppKeyBindings.idl" />
|
||||
<Midl Include="../AppLogic.idl" />
|
||||
<Midl Include="../ActionArgs.idl" />
|
||||
<Midl Include="../MinMaxCloseControl.idl">
|
||||
<DependentUpon>../MinMaxCloseControl.xaml</DependentUpon>
|
||||
<Midl Include="ShortcutActionDispatch.idl" />
|
||||
<Midl Include="AppKeyBindings.idl" />
|
||||
<Midl Include="AppLogic.idl" />
|
||||
<Midl Include="ActionArgs.idl" />
|
||||
<Midl Include="MinMaxCloseControl.idl">
|
||||
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../TerminalPage.idl">
|
||||
<DependentUpon>../TerminalPage.xaml</DependentUpon>
|
||||
<Midl Include="TerminalPage.idl">
|
||||
<DependentUpon>TerminalPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../TitlebarControl.idl">
|
||||
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
|
||||
<Midl Include="TitlebarControl.idl">
|
||||
<DependentUpon>TitlebarControl.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../TabRowControl.idl">
|
||||
<DependentUpon>../TabRowControl.xaml</DependentUpon>
|
||||
<Midl Include="TabRowControl.idl">
|
||||
<DependentUpon>TabRowControl.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../ColorPickupFlyout.idl">
|
||||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
<Midl Include="ColorPickupFlyout.idl">
|
||||
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../CommandPalette.idl">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
<Midl Include="CommandPalette.idl">
|
||||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../Command.idl" />
|
||||
<Midl Include="../CommandKeyChordVisibilityConverter.idl" />
|
||||
<Midl Include="../Tab.idl" />
|
||||
<Midl Include="../TerminalSettings.idl" />
|
||||
<Midl Include="Command.idl" />
|
||||
<Midl Include="EmptyStringVisibilityConverter.idl" />
|
||||
<Midl Include="HasNestedCommandsVisibilityConverter.idl" />
|
||||
<Midl Include="Tab.idl" />
|
||||
<Midl Include="TerminalSettings.idl" />
|
||||
<Midl Include="ColorScheme.idl" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
<PRIResource Include="..\Resources\en-US\Resources.resw" />
|
||||
<OCResourceDirectory Include="../Resources" />
|
||||
<None Include="../packages.config" />
|
||||
<PRIResource Include="Resources\en-US\Resources.resw" />
|
||||
<OCResourceDirectory Include="Resources" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
|
@ -331,8 +343,8 @@
|
|||
<!-- ========================= Globals ======================== -->
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<Import Project="..\..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\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>
|
||||
|
@ -363,16 +375,16 @@
|
|||
<!-- This target will take our defaults.json and stamp it into a .h file that
|
||||
we can include in the code directly. This way, we don't need to worry about
|
||||
failing to load the default settings at runtime. -->
|
||||
<Target Name="_TerminalAppGenerateDefaultsH" Inputs="..\defaults.json" Outputs="Generated Files\defaults.h" BeforeTargets="BeforeClCompile">
|
||||
<Exec Command="powershell.exe -noprofile –ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile ..\defaults.json -OutPath '"Generated Files\defaults.h"' -VariableName DefaultJson" />
|
||||
<Target Name="_TerminalAppGenerateDefaultsH" Inputs="defaults.json" Outputs="Generated Files\defaults.h" BeforeTargets="BeforeClCompile">
|
||||
<Exec Command="powershell.exe -noprofile –ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile defaults.json -OutPath '"Generated Files\defaults.h"' -VariableName DefaultJson" />
|
||||
</Target>
|
||||
<!-- A different set of defaults for Universal variant -->
|
||||
<Target Name="_TerminalAppGenerateDefaultsUniversalH" Inputs="..\defaults-universal.json" Outputs="Generated Files\defaults-universal.h" BeforeTargets="BeforeClCompile">
|
||||
<Exec Command="powershell.exe -noprofile –ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile ..\defaults-universal.json -OutPath '"Generated Files\defaults-universal.h"' -VariableName DefaultUniversalJson" />
|
||||
<Target Name="_TerminalAppGenerateDefaultsUniversalH" Inputs="defaults-universal.json" Outputs="Generated Files\defaults-universal.h" BeforeTargets="BeforeClCompile">
|
||||
<Exec Command="powershell.exe -noprofile –ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile defaults-universal.json -OutPath '"Generated Files\defaults-universal.h"' -VariableName DefaultUniversalJson" />
|
||||
</Target>
|
||||
<!-- Same as above, but for the default settings.json template -->
|
||||
<Target Name="_TerminalAppGenerateUserSettingsH" Inputs="..\userDefaults.json" Outputs="Generated Files\userDefaults.h" BeforeTargets="BeforeClCompile">
|
||||
<Exec Command="powershell.exe -noprofile –ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile ..\userDefaults.json -OutPath '"Generated Files\userDefaults.h"' -VariableName UserSettingsJson" />
|
||||
<Target Name="_TerminalAppGenerateUserSettingsH" Inputs="userDefaults.json" Outputs="Generated Files\userDefaults.h" BeforeTargets="BeforeClCompile">
|
||||
<Exec Command="powershell.exe -noprofile –ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile userDefaults.json -OutPath '"Generated Files\userDefaults.h"' -VariableName UserSettingsJson" />
|
||||
</Target>
|
||||
|
||||
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
|
|
@ -148,6 +148,9 @@
|
|||
<Midl Include="../TerminalSettings.idl">
|
||||
<Filter>settings</Filter>
|
||||
</Midl>
|
||||
<Midl Include="../ColorScheme.idl">
|
||||
<Filter>settings</Filter>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="../packages.config" />
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue