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:
Dustin Howett 2020-08-26 20:11:55 +00:00
commit f357e379fc
171 changed files with 8140 additions and 2282 deletions

View file

@ -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

View file

@ -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

View file

@ -99401,6 +99401,8 @@ DCP
DCPR
DCPSK
DCS
Dcs
dcs
DCT
DCTN
DCTS

View file

@ -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

View file

@ -25,6 +25,7 @@ Kourosh
kowalczyk
leonmsft
Lepilleur
lukesampson
Manandhar
mbadolato
Mehrain

View file

@ -1,10 +0,0 @@
abcd
dst
EFG
EFGh
EMPTYBOX
GFEh
nrcs
Remoting
Scs
Shobjidl

View file

@ -1,3 +1,4 @@
abcd
abcde
abcdef
ABCDEFG
@ -10,6 +11,8 @@ abcdefghijklmnopqrstuvwxyz
ABE
BBGGRR
BBBBBBBBBBBBBBDDDD
EFG
EFGh
QQQQQQQQQQABCDEFGHIJ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ

View file

@ -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

View file

@ -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" />

View file

@ -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

View file

@ -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

View 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
}

View 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)

View 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

View 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

View 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;
}
}
}

View 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
}

View 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)

View 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
}

View 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;]"
}

View 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"

View 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)
}
}
}
}
}
}

View 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>

View 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
View file

@ -0,0 +1,5 @@
{
"msbuild-sdks": {
"Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.20277.5"
}
}

View 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
View 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
View 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
View 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>

View file

@ -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 }}

View file

@ -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'

View 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 }}'

View 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 }}

View 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)'

View 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

View file

@ -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 }

View file

@ -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.

View file

@ -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` |

View file

@ -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": [

Binary file not shown.

Binary file not shown.

View file

@ -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

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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);
});
}
}

View file

@ -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. -->

View file

@ -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>

View file

@ -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++)

View file

@ -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;

View file

@ -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") },
};
}();

View file

@ -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");
}
}

View file

@ -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

View file

@ -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; };
};
}

View file

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "../AppLogic.idl";
import "AppLogic.idl";
namespace TerminalApp
{

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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
{

View file

@ -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
{

View file

@ -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();

View file

@ -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
{

View file

@ -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
{

View file

@ -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);

View file

@ -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;

View file

@ -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>

View file

@ -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;
}

View file

@ -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);
}

View 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; };
}
}

View file

@ -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;
}
}

View file

@ -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;
};
}

View file

@ -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; };
}
}

View file

@ -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();
}
}

View file

@ -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();
};
}

View file

@ -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);
}
}

View file

@ -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="&#xE70E;"
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>

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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();
};
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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();
};
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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 },

View file

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "..\TitlebarControl.idl";
import "TitlebarControl.idl";
namespace TerminalApp
{

View file

@ -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);

View file

@ -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>

View file

@ -25,4 +25,11 @@ namespace TerminalApp
std::optional<int> x;
std::optional<int> y;
};
enum class ExpandCommandType : uint32_t
{
None = 0,
Profiles,
ColorSchemes
};
};

View file

@ -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:

View file

@ -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:

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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();

View file

@ -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 '&quot;Generated Files\defaults.h&quot;' -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 '&quot;Generated Files\defaults.h&quot;' -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 '&quot;Generated Files\defaults-universal.h&quot;' -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 '&quot;Generated Files\defaults-universal.h&quot;' -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 '&quot;Generated Files\userDefaults.h&quot;' -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 '&quot;Generated Files\userDefaults.h&quot;' -VariableName UserSettingsJson" />
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />

View file

@ -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