Compare commits
6 Commits
release-1.
...
dev/miniks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7649fc8a06 | ||
|
|
343ff0913d | ||
|
|
39d67e3859 | ||
|
|
78c1bc10f7 | ||
|
|
b46b5d0f6f | ||
|
|
7e8f0398c3 |
@@ -1,19 +1,17 @@
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AlignConsecutiveMacros: false
|
||||
#AllowAllArgumentsOnNextLine: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
#AllowAllConstructorInitializersOnNextLine: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
#AllowShortLambdasOnASingleLine: Inline
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
@@ -22,7 +20,6 @@ AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
@@ -50,7 +47,6 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Regroup
|
||||
@@ -77,7 +73,7 @@ ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
#SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
@@ -92,6 +88,6 @@ SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
Standard: Cpp11
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
|
||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,8 +9,7 @@
|
||||
* [ ] Closes #xxx
|
||||
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
|
||||
* [ ] Tests added/passed
|
||||
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
|
||||
* [ ] Schema updated.
|
||||
* [ ] Requires documentation to be updated
|
||||
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx
|
||||
|
||||
<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
|
||||
|
||||
1
.gitignore
vendored
@@ -48,7 +48,6 @@ dlldata.c
|
||||
project.lock.json
|
||||
artifacts/
|
||||
|
||||
*_h.h
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
|
||||
@@ -125,7 +125,7 @@ Team members will be happy to help review specs and guide them to completion.
|
||||
|
||||
### Help Wanted
|
||||
|
||||
Once the team have approved an issue/spec, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/terminal/labels/Help%20Wanted).
|
||||
Once the team have approved an issue/spec, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/terminal/labels/Help-Wanted).
|
||||
|
||||
---
|
||||
|
||||
@@ -155,4 +155,4 @@ Once your code has been reviewed and approved by the requisite number of team me
|
||||
|
||||
## Thank you
|
||||
|
||||
Thank you in advance for your contribution! Now, [what's next on the list](https://github.com/microsoft/terminal/labels/Help%20Wanted)? 😜
|
||||
Thank you in advance for your contribution! Now, [what's next on the list](https://github.com/microsoft/terminal/labels/Help-Wanted)? 😜
|
||||
|
||||
40
NOTICE.md
@@ -179,42 +179,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
```
|
||||
|
||||
## {fmt}
|
||||
|
||||
**Source**: https://github.com/fmtlib/fmt
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2012 - present, Victor Zverovich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--- Optional exception to the license ---
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into a machine-executable object form of such
|
||||
source code, you may redistribute such embedded portions in such object form
|
||||
without including the above copyright and permission notices.
|
||||
```
|
||||
```
|
||||
@@ -8,12 +8,7 @@
|
||||
<!--<add key="Static Package Dependencies" value="dep\packages" />-->
|
||||
|
||||
<!-- 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" />
|
||||
<add key="Windows Terminal NuGet Feed" value="https://terminalnuget.blob.core.windows.net/feed/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" />
|
||||
|
||||
564
OpenConsole.sln
46
README.md
@@ -2,8 +2,7 @@
|
||||
|
||||
This repository contains the source code for:
|
||||
|
||||
* [Windows Terminal](https://aka.ms/terminal)
|
||||
* [Windows Terminal Preview](https://aka.ms/terminal-preview)
|
||||
* [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal-preview/9n0dx20hk701)
|
||||
* The Windows console host (`conhost.exe`)
|
||||
* Components shared between the two projects
|
||||
* [ColorTool](https://github.com/Microsoft/Terminal/tree/master/src/tools/ColorTool)
|
||||
@@ -11,7 +10,6 @@ This repository contains the source code for:
|
||||
|
||||
Related repositories include:
|
||||
|
||||
* [Windows Terminal Documentation](https://docs.microsoft.com/windows/terminal) ([Repo: Contribute to the docs](https://github.com/MicrosoftDocs/terminal))
|
||||
* [Console API Documentation](https://github.com/MicrosoftDocs/Console-Docs)
|
||||
* [Cascadia Code Font](https://github.com/Microsoft/Cascadia-Code)
|
||||
|
||||
@@ -21,7 +19,7 @@ Related repositories include:
|
||||
|
||||
### Microsoft Store [Recommended]
|
||||
|
||||
Install the [Windows Terminal from the Microsoft Store][store-install-link]. This allows you to always be on the latest version when we release new builds with automatic upgrades.
|
||||
Install the [Windows Terminal from the Microsoft Store][store-install-link]. This allows you to always be on the latest version when we release new builds with automatic upgrades.
|
||||
|
||||
This is our preferred method.
|
||||
|
||||
@@ -36,14 +34,6 @@ For users who are unable to install Terminal from the Microsoft Store, Terminal
|
||||
> * Be sure to install the [Desktop Bridge VC++ v14 Redistributable Package](https://www.microsoft.com/en-us/download/details.aspx?id=53175) otherwise Terminal may not install and/or run and may crash at startup
|
||||
> * Terminal will not auto-update when new builds are released so you will need to regularly install the latest Terminal release to receive all the latest fixes and improvements!
|
||||
|
||||
#### Via Windows Package Manager CLI (aka winget)
|
||||
|
||||
[winget](https://github.com/microsoft/winget-cli) users can download and install the latest Terminal release by installing the `Microsoft.WindowsTerminal` package:
|
||||
|
||||
```powershell
|
||||
winget install --id=Microsoft.WindowsTerminal -e
|
||||
```
|
||||
|
||||
#### Via Chocolatey (unofficial)
|
||||
|
||||
[Chocolatey](https://chocolatey.org) users can download and install the latest Terminal release by installing the `microsoft-windows-terminal` package:
|
||||
@@ -60,28 +50,8 @@ 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
|
||||
|
||||
The plan for delivering Windows Terminal 2.0 [is described here](/doc/terminal-v2-roadmap.md) and will be updated as the project proceeds.
|
||||
|
||||
## Project Build Status
|
||||
|
||||
Project|Build Status
|
||||
@@ -91,6 +61,12 @@ ColorTool|, and will be updated as the project proceeds.
|
||||
|
||||
---
|
||||
|
||||
## Terminal & Console Overview
|
||||
|
||||
Please take a few minutes to review the overview below before diving into the code:
|
||||
@@ -155,7 +131,7 @@ Solution: Make sure you're building & deploying the `CascadiaPackage` project in
|
||||
|
||||
## Documentation
|
||||
|
||||
All project documentation is located at aka.ms/terminal-docs. If you would like to contribute to the documentation, please submit a pull request on the [Windows Terminal Documentation repo](https://github.com/MicrosoftDocs/terminal).
|
||||
All project documentation is located in the `./doc` folder. If you would like to contribute to the documentation, please submit a pull request.
|
||||
|
||||
---
|
||||
|
||||
@@ -174,12 +150,12 @@ Please file new issues, feature requests and suggestions, but **DO search for si
|
||||
If you would like to ask a question that you feel doesn't warrant an issue (yet), please reach out to us via Twitter:
|
||||
|
||||
* Kayla Cinnamon, Program Manager: [@cinnamon\_msft](https://twitter.com/cinnamon_msft)
|
||||
* Rich Turner, Program Manager: [@richturn\_ms](https://twitter.com/richturn_ms)
|
||||
* Dustin Howett, Engineering Lead: [@dhowett](https://twitter.com/DHowett)
|
||||
* Michael Niksa, Senior Developer: [@michaelniksa](https://twitter.com/MichaelNiksa)
|
||||
* 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
|
||||
|
||||
@@ -251,4 +227,4 @@ For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [open
|
||||
[conduct-code]: https://opensource.microsoft.com/codeofconduct/
|
||||
[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
|
||||
[conduct-email]: mailto:opencode@microsoft.com
|
||||
[store-install-link]: https://aka.ms/terminal
|
||||
[store-install-link]: https://aka.ms/windowsterminal
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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)
|
||||
@@ -1,112 +0,0 @@
|
||||
$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
|
||||
@@ -1,336 +0,0 @@
|
||||
[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
|
||||
@@ -1,669 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
# 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
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
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)
|
||||
@@ -1,32 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
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;]"
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
[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"
|
||||
@@ -1,112 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<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>
|
||||
@@ -1,135 +0,0 @@
|
||||
[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
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.20277.5"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,32 +0,0 @@
|
||||
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.
|
||||
@@ -1,106 +0,0 @@
|
||||
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%
|
||||
@@ -1,5 +1,6 @@
|
||||
<SignConfigXML>
|
||||
<job platform="" configuration="" certSubject="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" jobname="EngFunSimpleSign" approvers="">
|
||||
<file src="__INPATHROOT__\Microsoft.WindowsTerminal*.msixbundle" signType="136020001" />
|
||||
<file src="__INPATHROOT__\Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle" signType="136020001" dest="__OUTPATHROOT__\Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle" />
|
||||
<file src="__INPATHROOT__\Microsoft.WindowsTerminalUniversal_8wekyb3d8bbwe.msixbundle" signType="136020001" dest="__OUTPATHROOT__\Microsoft.WindowsTerminalUniversal_8wekyb3d8bbwe.msixbundle" />
|
||||
</job>
|
||||
</SignConfigXML>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?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>
|
||||
@@ -27,42 +27,21 @@ variables:
|
||||
# 0.0.1904.0900
|
||||
name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr)
|
||||
|
||||
stages:
|
||||
- stage: Audit_x64
|
||||
displayName: Audit Mode
|
||||
dependsOn: []
|
||||
condition: succeeded()
|
||||
jobs:
|
||||
- template: ./templates/build-console-audit-job.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
- stage: Build_x64
|
||||
displayName: Build x64
|
||||
dependsOn: []
|
||||
condition: succeeded()
|
||||
jobs:
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
- stage: Build_x86
|
||||
displayName: Build x86
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: x86
|
||||
- stage: Build_ARM64
|
||||
displayName: Build ARM64
|
||||
dependsOn: []
|
||||
condition: not(eq(variables['Build.Reason'], 'PullRequest'))
|
||||
jobs:
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: ARM64
|
||||
- stage: Scripts
|
||||
displayName: Code Health Scripts
|
||||
dependsOn: []
|
||||
condition: succeeded()
|
||||
jobs:
|
||||
- template: ./templates/check-formatting.yml
|
||||
jobs:
|
||||
- template: ./templates/build-console-audit-job.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: x86
|
||||
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: ARM64
|
||||
|
||||
- template: ./templates/check-formatting.yml
|
||||
|
||||
@@ -15,14 +15,6 @@ variables:
|
||||
# store publication machinery happy.
|
||||
name: 'Terminal_$(date:yyMM).$(date:dd)$(rev:rrr)'
|
||||
|
||||
# Build Arguments:
|
||||
# WindowsTerminalOfficialBuild=[true,false]
|
||||
# true - this is running on our build agent
|
||||
# false - running locally
|
||||
# WindowsTerminalBranding=[Dev,Preview,Release]
|
||||
# <none> - Development build resources (default)
|
||||
# Preview - Preview build resources
|
||||
# Release - regular build resources
|
||||
jobs:
|
||||
- template: ./templates/build-console-audit-job.yml
|
||||
parameters:
|
||||
@@ -31,17 +23,17 @@ jobs:
|
||||
- template: ./templates/build-console-int.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
|
||||
additionalBuildArguments: /p:WindowsTerminalReleaseBuild=true
|
||||
|
||||
- template: ./templates/build-console-int.yml
|
||||
parameters:
|
||||
platform: x86
|
||||
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
|
||||
additionalBuildArguments: /p:WindowsTerminalReleaseBuild=true
|
||||
|
||||
- template: ./templates/build-console-int.yml
|
||||
parameters:
|
||||
platform: arm64
|
||||
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
|
||||
additionalBuildArguments: /p:WindowsTerminalReleaseBuild=true
|
||||
|
||||
- template: ./templates/check-formatting.yml
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ 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 }}
|
||||
@@ -17,19 +15,3 @@ jobs:
|
||||
- template: build-console-steps.yml
|
||||
parameters:
|
||||
additionalBuildArguments: ${{ parameters.additionalBuildArguments }}
|
||||
|
||||
- template: helix-runtests-job.yml
|
||||
parameters:
|
||||
name: 'RunTestsInHelix'
|
||||
dependsOn: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
condition: and(succeeded(), and(eq('${{ parameters.platform }}', 'x64'), not(eq(variables['Build.Reason'], 'PullRequest'))))
|
||||
testSuite: 'DevTestSuite'
|
||||
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
|
||||
|
||||
- template: helix-processtestresults-job.yml
|
||||
parameters:
|
||||
dependsOn:
|
||||
- RunTestsInHelix
|
||||
condition: and(succeededOrFailed(), and(eq('${{ parameters.platform }}', 'x64'), not(eq(variables['Build.Reason'], 'PullRequest'))))
|
||||
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
|
||||
minimumExpectedTestsExecutedCount: ${{ parameters.minimumExpectedTestsExecutedCount }}
|
||||
@@ -1,6 +1,5 @@
|
||||
parameters:
|
||||
additionalBuildArguments: ''
|
||||
testLogPath: '$(Build.BinariesDirectory)\$(BuildPlatform)\$(BuildConfiguration)\testsOnBuildMachine.wtl'
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
@@ -8,29 +7,23 @@ steps:
|
||||
clean: true
|
||||
|
||||
- task: NuGetToolInstaller@0
|
||||
displayName: 'Use NuGet 5.2.0'
|
||||
displayName: Ensure NuGet 4.8.1
|
||||
inputs:
|
||||
versionSpec: 5.2.0
|
||||
versionSpec: 4.8.1
|
||||
|
||||
- task: VisualStudioTestPlatformInstaller@1
|
||||
displayName: Ensure VSTest Platform
|
||||
|
||||
# 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 for solution
|
||||
displayName: Restore NuGet packages
|
||||
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'
|
||||
@@ -41,7 +34,7 @@ steps:
|
||||
configuration: '$(BuildConfiguration)'
|
||||
msbuildArgs: "${{ parameters.additionalBuildArguments }}"
|
||||
clean: true
|
||||
maximumCpuCount: false
|
||||
maximumCpuCount: true
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Check MSIX for common regressions'
|
||||
@@ -73,7 +66,7 @@ steps:
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}'
|
||||
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
|
||||
condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
|
||||
|
||||
- task: PowerShell@2
|
||||
@@ -81,41 +74,9 @@ steps:
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}'
|
||||
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
|
||||
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:
|
||||
@@ -129,22 +90,9 @@ steps:
|
||||
flattenFolders: true
|
||||
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy outputs needed for test runs to Artifacts'
|
||||
inputs:
|
||||
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'
|
||||
displayName: 'Publish Artifact (appx) (Non-PR builds only)'
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'drop'
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)/appx'
|
||||
ArtifactName: 'appx-$(BuildConfiguration)'
|
||||
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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 }}'
|
||||
@@ -1,68 +0,0 @@
|
||||
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 }}
|
||||
@@ -1,131 +0,0 @@
|
||||
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)'
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="BeforeGenerateProjectPriFile" DependsOnTargets="OpenConsoleCollectWildcardPRIFiles" />
|
||||
|
||||
<!--
|
||||
The vcxproj system does not support wildcards at the root level of a project.
|
||||
This poses a problem, as we want to include resw files that are not checked into the
|
||||
repository. Since they're usually localized and stored in directories named after
|
||||
their languages, we can't exactly explicitly simultaneously list them all and remain
|
||||
sane. We want to use wildcards to make our lives easier.
|
||||
|
||||
This rule takes OCResourceDirectory items and includes all resw files that live
|
||||
underneath them.
|
||||
|
||||
** TIRED **
|
||||
(does not work because of wildcards)
|
||||
<PRIResource Include="Resources/*/Resources.resw" />
|
||||
|
||||
** WIRED **
|
||||
(keep the en-US resource in the project, because it is checked in and VS will show it)
|
||||
<PRIResource Include="Resources/en-US/Resources.resw" />
|
||||
<OCResourceDirectory Include="Resources" />
|
||||
-->
|
||||
<Target Name="OpenConsoleCollectWildcardPRIFiles">
|
||||
<CreateItem Include="@(OCResourceDirectory->'%(Identity)\**\*.resw')">
|
||||
<Output TaskParameter="Include" ItemName="_OCFoundPRIFiles" />
|
||||
</CreateItem>
|
||||
<ItemGroup>
|
||||
<_OCFoundPRIFiles Include="@(PRIResource)" />
|
||||
<PRIResource Remove="@(PRIResource)" />
|
||||
<PRIResource Include="@(_OCFoundPRIFiles->Distinct())" />
|
||||
</ItemGroup>
|
||||
<Message Text="$(ProjectName) (wildcard PRIs) -> @(PRIResource)" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,14 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Position=0, Mandatory=$true)][string]$MarkdownNoticePath,
|
||||
[Parameter(Position=1, Mandatory=$true)][string]$OutputPath
|
||||
)
|
||||
|
||||
@"
|
||||
<html>
|
||||
<head><title>Third-Party Notices</title></head>
|
||||
<body>
|
||||
$(ConvertFrom-Markdown $MarkdownNoticePath | Select -Expand Html)
|
||||
</body>
|
||||
</html>
|
||||
"@ | Out-File -Encoding UTF-8 $OutputPath -Force
|
||||
@@ -1,15 +0,0 @@
|
||||
[CmdLetBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true, Position=0)][string]$BuildPlatform,
|
||||
[Parameter(Mandatory=$true, Position=1)][string]$RationalizedPlatform,
|
||||
[Parameter(Mandatory=$true, Position=2)][string]$Configuration
|
||||
)
|
||||
|
||||
|
||||
$i = Get-Item .\packages\MuxCustomBuild*
|
||||
$wtt = Join-Path -Path $i[0].FullName -ChildPath (Join-Path -Path 'tools' -ChildPath (Join-Path -Path $BuildPlatform -ChildPath 'wttlog.dll'))
|
||||
$dest = Join-Path -Path .\bin -ChildPath (Join-Path -Path $RationalizedPlatform -ChildPath ($Configuration))
|
||||
copy $wtt $dest
|
||||
|
||||
|
||||
Exit 0
|
||||
@@ -2,24 +2,12 @@
|
||||
Param(
|
||||
[Parameter(Mandatory=$true, Position=0)][string]$MatchPattern,
|
||||
[Parameter(Mandatory=$true, Position=1)][string]$Platform,
|
||||
[Parameter(Mandatory=$true, Position=2)][string]$Configuration,
|
||||
[Parameter(Mandatory=$false, Position=3)][string]$LogPath
|
||||
[Parameter(Mandatory=$true, Position=2)][string]$Configuration
|
||||
)
|
||||
|
||||
$testdlls = Get-ChildItem -Path ".\bin\$Platform\$Configuration" -Recurse -Filter $MatchPattern
|
||||
|
||||
|
||||
$args = @();
|
||||
|
||||
if ($LogPath)
|
||||
{
|
||||
$args += '/enablewttlogging';
|
||||
$args += '/appendwttlogging';
|
||||
$args += "/logFile:$LogPath";
|
||||
Write-Host "Wtt Logging Enabled";
|
||||
}
|
||||
|
||||
&".\bin\$Platform\$Configuration\te.exe" $args $testdlls.FullName
|
||||
&".\bin\$Platform\$Configuration\te.exe" $testdlls.FullName
|
||||
|
||||
if ($lastexitcode -Ne 0) { Exit $lastexitcode }
|
||||
|
||||
|
||||
@@ -68,27 +68,6 @@ Try {
|
||||
}
|
||||
}
|
||||
|
||||
$dependencies = $Manifest.Package.Dependencies.PackageDependency.Name
|
||||
$depsHasVclibsDesktop = ("Microsoft.VCLibs.140.00.UWPDesktop" -in $dependencies) -or ("Microsoft.VCLibs.140.00.Debug.UWPDesktop" -in $dependencies)
|
||||
$depsHasVcLibsAppX = ("Microsoft.VCLibs.140.00" -in $dependencies) -or ("Microsoft.VCLibs.140.00.Debug" -in $dependencies)
|
||||
$filesHasVclibsDesktop = ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140.dll" -EA:Ignore)) -or ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140d.dll" -EA:Ignore))
|
||||
$filesHasVclibsAppX = ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140_app.dll" -EA:Ignore)) -or ($null -ne (Get-Item "$AppxPackageRootPath\vcruntime140d_app.dll" -EA:Ignore))
|
||||
|
||||
If ($depsHasVclibsDesktop -Eq $filesHasVclibsDesktop) {
|
||||
$eitherBoth = if ($depsHasVclibsDesktop) { "both" } else { "neither" }
|
||||
$neitherNor = if ($depsHasVclibsDesktop) { "and" } else { "nor" }
|
||||
Throw "Package has $eitherBoth Dependency $neitherNor Integrated Desktop VCLibs"
|
||||
}
|
||||
|
||||
If ($depsHasVclibsAppx -Eq $filesHasVclibsAppx) {
|
||||
if ($depsHasVclibsAppx) {
|
||||
# We've shipped like this forever, so downgrade to warning.
|
||||
Write-Warning "Package has both Dependency and Integrated AppX VCLibs"
|
||||
} else {
|
||||
Throw "Package has neither Dependency nor Integrated AppX VCLibs"
|
||||
}
|
||||
}
|
||||
|
||||
### Check that we have an App.xbf (which is a proxy for our resources having been merged)
|
||||
$resourceXpath = '/PriInfo/ResourceMap/ResourceMapSubtree[@name="Files"]/NamedResource[@name="App.xbf"]'
|
||||
$AppXbf = $PRIFile.SelectSingleNode($resourceXpath)
|
||||
@@ -101,11 +80,6 @@ Try {
|
||||
Throw "Failed to find cpprest142_2_10.dll -- check the WAP packaging project"
|
||||
}
|
||||
|
||||
If (($null -eq (Get-Item "$AppxPackageRootPath\wtd.exe" -EA:Ignore)) -And
|
||||
($null -eq (Get-Item "$AppxPackageRootPath\wt.exe" -EA:Ignore))) {
|
||||
Throw "Failed to find wt.exe/wtd.exe -- check the WAP packaging project"
|
||||
}
|
||||
|
||||
} Finally {
|
||||
Remove-Item -Recurse -Force $AppxPackageRootPath
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
".db",
|
||||
".wrn",
|
||||
".rec",
|
||||
".err",
|
||||
".xlsx"
|
||||
".err"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<PropertyGroup Label="Version">
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>3</VersionMinor>
|
||||
<VersionMajor>0</VersionMajor>
|
||||
<VersionMinor>11</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
4440
dep/CLI11/CLI11.hpp
@@ -1,5 +1,5 @@
|
||||
# CLI11
|
||||
|
||||
Taken from [release v1.9.0](https://github.com/CLIUtils/CLI11/releases/tag/v1.9.0), source commit
|
||||
[dd0d8e4](https://github.com/CLIUtils/CLI11/commit/dd0d8e4fe729e5b1110232c7a5c9566dad884686)
|
||||
Taken from [release v1.8.0](https://github.com/CLIUtils/CLI11/releases/tag/v1.8.0), source commit
|
||||
[13becad](https://github.com/CLIUtils/CLI11/commit/13becaddb657eacd090537719a669d66d393b8b2)
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{"Registrations":[
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/CLIUtils/CLI11",
|
||||
"commitHash": "dd0d8e4fe729e5b1110232c7a5c9566dad884686"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 1
|
||||
}
|
||||
2
dep/gsl
@@ -2,15 +2,6 @@
|
||||
|
||||
[Amalgamated](https://github.com/open-source-parsers/jsoncpp/wiki/Amalgamated)
|
||||
from source commit
|
||||
[6aba23f](https://github.com/open-source-parsers/jsoncpp/commit/6aba23f4a8628d599a9ef7fa4811c4ff6e4070e2),
|
||||
release 1.9.3.
|
||||
[ddabf50](https://github.com/open-source-parsers/jsoncpp/commit/ddabf50f72cf369bf652a95c4d9fe31a1865a781),
|
||||
release 1.8.4.
|
||||
|
||||
> Generating amalgamated source and header JsonCpp is provided with a script to
|
||||
> generate a single header and a single source file to ease inclusion into an
|
||||
> existing project. The amalgamated source can be generated at any time by
|
||||
> running the following command from the top-directory (this requires Python
|
||||
> 3.4+):
|
||||
>
|
||||
> ```
|
||||
> python amalgamate.py
|
||||
> ```
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{"Registrations":[
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/open-source-parsers/jsoncpp",
|
||||
"commitHash": "6aba23f4a8628d599a9ef7fa4811c4ff6e4070e2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 1
|
||||
}
|
||||
@@ -79,151 +79,6 @@ license you like.
|
||||
/// to prevent private header inclusion.
|
||||
#define JSON_IS_AMALGAMATION
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
// Beginning of content of file: include/json/version.h
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef JSON_VERSION_H_INCLUDED
|
||||
#define JSON_VERSION_H_INCLUDED
|
||||
|
||||
// Note: version must be updated in three places when doing a release. This
|
||||
// annoying process ensures that amalgamate, CMake, and meson all report the
|
||||
// correct version.
|
||||
// 1. /meson.build
|
||||
// 2. /include/json/version.h
|
||||
// 3. /CMakeLists.txt
|
||||
// IMPORTANT: also update the SOVERSION!!
|
||||
|
||||
#define JSONCPP_VERSION_STRING "1.9.3"
|
||||
#define JSONCPP_VERSION_MAJOR 1
|
||||
#define JSONCPP_VERSION_MINOR 9
|
||||
#define JSONCPP_VERSION_PATCH 3
|
||||
#define JSONCPP_VERSION_QUALIFIER
|
||||
#define JSONCPP_VERSION_HEXA \
|
||||
((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \
|
||||
(JSONCPP_VERSION_PATCH << 8))
|
||||
|
||||
#ifdef JSONCPP_USING_SECURE_MEMORY
|
||||
#undef JSONCPP_USING_SECURE_MEMORY
|
||||
#endif
|
||||
#define JSONCPP_USING_SECURE_MEMORY 0
|
||||
// If non-zero, the library zeroes any memory that it has allocated before
|
||||
// it frees its memory.
|
||||
|
||||
#endif // JSON_VERSION_H_INCLUDED
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
// End of content of file: include/json/version.h
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
// Beginning of content of file: include/json/allocator.h
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
||||
// Distributed under MIT license, or public domain if desired and
|
||||
// recognized in your jurisdiction.
|
||||
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
||||
|
||||
#ifndef JSON_ALLOCATOR_H_INCLUDED
|
||||
#define JSON_ALLOCATOR_H_INCLUDED
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#pragma pack(push, 8)
|
||||
|
||||
namespace Json {
|
||||
template <typename T> class SecureAllocator {
|
||||
public:
|
||||
// Type definitions
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
/**
|
||||
* Allocate memory for N items using the standard allocator.
|
||||
*/
|
||||
pointer allocate(size_type n) {
|
||||
// allocate using "global operator new"
|
||||
return static_cast<pointer>(::operator new(n * sizeof(T)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Release memory which was allocated for N items at pointer P.
|
||||
*
|
||||
* The memory block is filled with zeroes before being released.
|
||||
* The pointer argument is tagged as "volatile" to prevent the
|
||||
* compiler optimizing out this critical step.
|
||||
*/
|
||||
void deallocate(volatile pointer p, size_type n) {
|
||||
std::memset(p, 0, n * sizeof(T));
|
||||
// free using "global operator delete"
|
||||
::operator delete(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an item in-place at pointer P.
|
||||
*/
|
||||
template <typename... Args> void construct(pointer p, Args&&... args) {
|
||||
// construct using "placement new" and "perfect forwarding"
|
||||
::new (static_cast<void*>(p)) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
size_type max_size() const { return size_t(-1) / sizeof(T); }
|
||||
|
||||
pointer address(reference x) const { return std::addressof(x); }
|
||||
|
||||
const_pointer address(const_reference x) const { return std::addressof(x); }
|
||||
|
||||
/**
|
||||
* Destroy an item in-place at pointer P.
|
||||
*/
|
||||
void destroy(pointer p) {
|
||||
// destroy using "explicit destructor"
|
||||
p->~T();
|
||||
}
|
||||
|
||||
// Boilerplate
|
||||
SecureAllocator() {}
|
||||
template <typename U> SecureAllocator(const SecureAllocator<U>&) {}
|
||||
template <typename U> struct rebind { using other = SecureAllocator<U>; };
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
bool operator==(const SecureAllocator<T>&, const SecureAllocator<U>&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
bool operator!=(const SecureAllocator<T>&, const SecureAllocator<U>&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Json
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif // JSON_ALLOCATOR_H_INCLUDED
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
// End of content of file: include/json/allocator.h
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
// Beginning of content of file: include/json/config.h
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
@@ -235,14 +90,19 @@ bool operator!=(const SecureAllocator<T>&, const SecureAllocator<U>&) {
|
||||
|
||||
#ifndef JSON_CONFIG_H_INCLUDED
|
||||
#define JSON_CONFIG_H_INCLUDED
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <istream>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <stddef.h>
|
||||
#include <string> //typedef String
|
||||
#include <stdint.h> //typedef int64_t, uint64_t
|
||||
|
||||
/// If defined, indicates that json library is embedded in CppTL library.
|
||||
//# define JSON_IN_CPPTL 1
|
||||
|
||||
/// If defined, indicates that json may leverage CppTL library
|
||||
//# define JSON_USE_CPPTL 1
|
||||
/// If defined, indicates that cpptl vector based map should be used instead of
|
||||
/// std::map
|
||||
/// as Value container.
|
||||
//# define JSON_USE_CPPTL_SMALLMAP 1
|
||||
|
||||
// If non-zero, the library uses exceptions to report bad input instead of C
|
||||
// assertion macros. The default is to use exceptions.
|
||||
@@ -250,132 +110,164 @@ bool operator!=(const SecureAllocator<T>&, const SecureAllocator<U>&) {
|
||||
#define JSON_USE_EXCEPTION 1
|
||||
#endif
|
||||
|
||||
// Temporary, tracked for removal with issue #982.
|
||||
#ifndef JSON_USE_NULLREF
|
||||
#define JSON_USE_NULLREF 1
|
||||
#endif
|
||||
|
||||
/// If defined, indicates that the source file is amalgamated
|
||||
/// to prevent private header inclusion.
|
||||
/// Remarks: it is automatically defined in the generated amalgamated header.
|
||||
// #define JSON_IS_AMALGAMATION
|
||||
|
||||
// Export macros for DLL visibility
|
||||
#if defined(JSON_DLL_BUILD)
|
||||
#ifdef JSON_IN_CPPTL
|
||||
#include <cpptl/config.h>
|
||||
#ifndef JSON_USE_CPPTL
|
||||
#define JSON_USE_CPPTL 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef JSON_IN_CPPTL
|
||||
#define JSON_API CPPTL_API
|
||||
#elif defined(JSON_DLL_BUILD)
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#define JSON_API __declspec(dllexport)
|
||||
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define JSON_API __attribute__((visibility("default")))
|
||||
#endif // if defined(_MSC_VER)
|
||||
|
||||
#elif defined(JSON_DLL)
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#define JSON_API __declspec(dllimport)
|
||||
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
||||
#endif // if defined(_MSC_VER)
|
||||
#endif // ifdef JSON_DLL_BUILD
|
||||
|
||||
#endif // ifdef JSON_IN_CPPTL
|
||||
#if !defined(JSON_API)
|
||||
#define JSON_API
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1800
|
||||
#error \
|
||||
"ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities"
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
// As recommended at
|
||||
// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010
|
||||
extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size,
|
||||
const char* format, ...);
|
||||
#define jsoncpp_snprintf msvc_pre1900_c99_snprintf
|
||||
#else
|
||||
#define jsoncpp_snprintf std::snprintf
|
||||
#endif
|
||||
|
||||
// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
|
||||
// integer
|
||||
// Storages, and 64 bits integer support is disabled.
|
||||
// #define JSON_NO_INT64 1
|
||||
|
||||
// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools.
|
||||
// C++11 should be used directly in JSONCPP.
|
||||
#define JSONCPP_OVERRIDE override
|
||||
#if defined(_MSC_VER) // MSVC
|
||||
# if _MSC_VER <= 1200 // MSVC 6
|
||||
// Microsoft Visual Studio 6 only support conversion from __int64 to double
|
||||
// (no conversion from unsigned __int64).
|
||||
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
||||
// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
|
||||
// characters in the debug information)
|
||||
// All projects I've ever seen with VS6 were using this globally (not bothering
|
||||
// with pragma push/pop).
|
||||
# pragma warning(disable : 4786)
|
||||
# endif // MSVC 6
|
||||
|
||||
# if _MSC_VER >= 1500 // MSVC 2008
|
||||
/// Indicates that the following function is deprecated.
|
||||
# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
|
||||
# endif
|
||||
|
||||
#endif // defined(_MSC_VER)
|
||||
|
||||
// In c++11 the override keyword allows you to explicitly define that a function
|
||||
// is intended to override the base-class version. This makes the code more
|
||||
// managable and fixes a set of common hard-to-find bugs.
|
||||
#if __cplusplus >= 201103L
|
||||
# define JSONCPP_OVERRIDE override
|
||||
# define JSONCPP_NOEXCEPT noexcept
|
||||
#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900
|
||||
# define JSONCPP_OVERRIDE override
|
||||
# define JSONCPP_NOEXCEPT throw()
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1900
|
||||
# define JSONCPP_OVERRIDE override
|
||||
# define JSONCPP_NOEXCEPT noexcept
|
||||
#else
|
||||
# define JSONCPP_OVERRIDE
|
||||
# define JSONCPP_NOEXCEPT throw()
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_RVALUE_REFERENCES
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010
|
||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
||||
#endif // MSVC >= 2010
|
||||
|
||||
#ifdef __clang__
|
||||
#if __has_extension(attribute_deprecated_with_message)
|
||||
#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message)))
|
||||
#if __has_feature(cxx_rvalue_references)
|
||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
||||
#endif // has_feature
|
||||
|
||||
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
||||
#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L)
|
||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
||||
#endif // GXX_EXPERIMENTAL
|
||||
|
||||
#endif // __clang__ || __GNUC__
|
||||
|
||||
#endif // not defined JSON_HAS_RVALUE_REFERENCES
|
||||
|
||||
#ifndef JSON_HAS_RVALUE_REFERENCES
|
||||
#define JSON_HAS_RVALUE_REFERENCES 0
|
||||
#endif
|
||||
#elif defined(__GNUC__) // not clang (gcc comes later since clang emulates gcc)
|
||||
#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
||||
#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message)))
|
||||
#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
|
||||
#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
|
||||
#endif // GNUC version
|
||||
#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates
|
||||
// MSVC)
|
||||
#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
|
||||
#endif // __clang__ || __GNUC__ || _MSC_VER
|
||||
|
||||
#ifdef __clang__
|
||||
# if __has_extension(attribute_deprecated_with_message)
|
||||
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
||||
# endif
|
||||
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
||||
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
||||
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
||||
# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
|
||||
# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
|
||||
# endif // GNUC version
|
||||
#endif // __clang__ || __GNUC__
|
||||
|
||||
#if !defined(JSONCPP_DEPRECATED)
|
||||
#define JSONCPP_DEPRECATED(message)
|
||||
#endif // if !defined(JSONCPP_DEPRECATED)
|
||||
|
||||
#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6))
|
||||
#define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
||||
#if __GNUC__ >= 6
|
||||
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_IS_AMALGAMATION)
|
||||
|
||||
#include "allocator.h"
|
||||
#include "version.h"
|
||||
# include "version.h"
|
||||
|
||||
# if JSONCPP_USING_SECURE_MEMORY
|
||||
# include "allocator.h" //typedef Allocator
|
||||
# endif
|
||||
|
||||
#endif // if !defined(JSON_IS_AMALGAMATION)
|
||||
|
||||
namespace Json {
|
||||
using Int = int;
|
||||
using UInt = unsigned int;
|
||||
typedef int Int;
|
||||
typedef unsigned int UInt;
|
||||
#if defined(JSON_NO_INT64)
|
||||
using LargestInt = int;
|
||||
using LargestUInt = unsigned int;
|
||||
typedef int LargestInt;
|
||||
typedef unsigned int LargestUInt;
|
||||
#undef JSON_HAS_INT64
|
||||
#else // if defined(JSON_NO_INT64)
|
||||
// For Microsoft Visual use specific types as long long is not supported
|
||||
#if defined(_MSC_VER) // Microsoft Visual Studio
|
||||
using Int64 = __int64;
|
||||
using UInt64 = unsigned __int64;
|
||||
typedef __int64 Int64;
|
||||
typedef unsigned __int64 UInt64;
|
||||
#else // if defined(_MSC_VER) // Other platforms, use long long
|
||||
using Int64 = int64_t;
|
||||
using UInt64 = uint64_t;
|
||||
#endif // if defined(_MSC_VER)
|
||||
using LargestInt = Int64;
|
||||
using LargestUInt = UInt64;
|
||||
typedef int64_t Int64;
|
||||
typedef uint64_t UInt64;
|
||||
#endif // if defined(_MSC_VER)
|
||||
typedef Int64 LargestInt;
|
||||
typedef UInt64 LargestUInt;
|
||||
#define JSON_HAS_INT64
|
||||
#endif // if defined(JSON_NO_INT64)
|
||||
|
||||
template <typename T>
|
||||
using Allocator =
|
||||
typename std::conditional<JSONCPP_USING_SECURE_MEMORY, SecureAllocator<T>,
|
||||
std::allocator<T>>::type;
|
||||
using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>;
|
||||
using IStringStream =
|
||||
std::basic_istringstream<String::value_type, String::traits_type,
|
||||
String::allocator_type>;
|
||||
using OStringStream =
|
||||
std::basic_ostringstream<String::value_type, String::traits_type,
|
||||
String::allocator_type>;
|
||||
using IStream = std::istream;
|
||||
using OStream = std::ostream;
|
||||
} // namespace Json
|
||||
|
||||
// Legacy names (formerly macros).
|
||||
using JSONCPP_STRING = Json::String;
|
||||
using JSONCPP_ISTRINGSTREAM = Json::IStringStream;
|
||||
using JSONCPP_OSTRINGSTREAM = Json::OStringStream;
|
||||
using JSONCPP_ISTREAM = Json::IStream;
|
||||
using JSONCPP_OSTREAM = Json::OStream;
|
||||
#if JSONCPP_USING_SECURE_MEMORY
|
||||
#define JSONCPP_STRING std::basic_string<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
||||
#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
||||
#define JSONCPP_OSTREAM std::basic_ostream<char, std::char_traits<char>>
|
||||
#define JSONCPP_ISTRINGSTREAM std::basic_istringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
||||
#define JSONCPP_ISTREAM std::istream
|
||||
#else
|
||||
#define JSONCPP_STRING std::string
|
||||
#define JSONCPP_OSTRINGSTREAM std::ostringstream
|
||||
#define JSONCPP_OSTREAM std::ostream
|
||||
#define JSONCPP_ISTRINGSTREAM std::istringstream
|
||||
#define JSONCPP_ISTREAM std::istream
|
||||
#endif // if JSONCPP_USING_SECURE_MEMORY
|
||||
} // end namespace Json
|
||||
|
||||
#endif // JSON_CONFIG_H_INCLUDED
|
||||
|
||||
@@ -407,23 +299,17 @@ using JSONCPP_OSTREAM = Json::OStream;
|
||||
namespace Json {
|
||||
|
||||
// writer.h
|
||||
class StreamWriter;
|
||||
class StreamWriterBuilder;
|
||||
class Writer;
|
||||
class FastWriter;
|
||||
class StyledWriter;
|
||||
class StyledStreamWriter;
|
||||
|
||||
// reader.h
|
||||
class Reader;
|
||||
class CharReader;
|
||||
class CharReaderBuilder;
|
||||
|
||||
// json_features.h
|
||||
// features.h
|
||||
class Features;
|
||||
|
||||
// value.h
|
||||
using ArrayIndex = unsigned int;
|
||||
typedef unsigned int ArrayIndex;
|
||||
class StaticString;
|
||||
class Path;
|
||||
class PathArgument;
|
||||
|
||||
BIN
dep/llvm/clang-format.exe
Normal file
2
dep/wil
19
doc/Niksa.md
@@ -9,8 +9,6 @@ This document serves as a storage point for those posts.
|
||||
- [Output Processing between "Far East" and "Western"](#fesb)
|
||||
- [Why do we not backport things?](#backport)
|
||||
- [Why can't we have mixed elevated and non-elevated tabs in the Terminal?](#elevation)
|
||||
- [What's the difference between a shell and a terminal?](#shell-vs-terminal)
|
||||
|
||||
|
||||
## <a name="cmd"></a>Why do we avoid changing CMD.exe?
|
||||
`setlocal` doesn't behave the same way as an environment variable. It's a thing that would have to be put in at the top of the batch script that is `somefile.cmd` as one of its first commands to adjust the way that one specific batch file is processed by the `cmd.exe` engine. That's probably not suitable for your needs, but that's the way we have to go.
|
||||
@@ -181,20 +179,3 @@ Other platforms have accepted that risk in preference for user convenience. They
|
||||
|
||||
Original Source: https://github.com/microsoft/terminal/issues/632#issuecomment-519375707
|
||||
|
||||
## <a name="shell-vs-terminal"></a>What's the difference between a shell and a terminal?
|
||||
|
||||
_guest speaker @zadjii-msft_
|
||||
|
||||
I think there might be a bit of a misunderstanding here - there are two different kinds of applications we're talking about here:
|
||||
* shell applications, like `cmd.exe`, `powershell`, `zsh`, etc. These are text-only applications that emit streams of characters. They don't care at all about how they're eventually rendered to the user. These are also sometimes referred to as "commandline client" applications.
|
||||
* terminal applications, like the Windows Terminal, gnome-terminal, xterm, iterm2, hyper. These are graphical applications that can be used to render the output of commandline clients.
|
||||
|
||||
On Windows, if you just run `cmd.exe` directly, the OS will create an instance of `conhost.exe` as the _terminal_ for `cmd.exe`. The same thing happens for `powershell.exe`, the system will creates a new conhost window for any client that's not already connected to a terminal of some sort. This has lead to an enormous amount of confusion for people thinking that a conhost window is actually a "`cmd` window". `cmd` can't have a window, it's just a commandline application. Its window is always some other terminal.
|
||||
|
||||
Any terminal can run any commandline client application. So you can use the Windows Terminal to run whatever shell you want. I use mine for both `cmd` and `powershell`, and also WSL:
|
||||
|
||||

|
||||
|
||||
It's not the Terminal's responsibility to remember the commands executed by a commandline client. That's the responsibility of the _shell_. How would the terminal remember commands executed by something like `emacs` or `vim`? Those are both applications where the user is typing input and hitting enter, like they would at a cmd prompt, but without something that resembles a command history.
|
||||
|
||||
Original Source: https://github.com/microsoft/terminal/issues/6500#issuecomment-670035468
|
||||
|
||||
@@ -67,12 +67,12 @@ To update the version of a given package, use the following snippet
|
||||
|
||||
where:
|
||||
- `$PackageName` is the name of the package, e.g. Microsoft.UI.Xaml
|
||||
- `$OldVersionNumber` is the version number currently used, e.g. 2.5.0-prerelease.200609001
|
||||
- `$OldVersionNumber` is the version number currently used, e.g. 2.3.191217003-prerelease
|
||||
- `$NewVersionNumber` is the version number you want to migrate to, e.g. 2.4.200117003-prerelease
|
||||
|
||||
Example usage:
|
||||
|
||||
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.5.0-prerelease.200609001/2.4.200117003-prerelease/g'`
|
||||
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.3.191217003-prerelease/2.4.200117003-prerelease/g'`
|
||||
|
||||
## Using .nupkg files instead of downloaded Nuget packages
|
||||
If you want to use .nupkg files instead of the downloaded Nuget package, you can do this with the following steps:
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
# New Json Utility API
|
||||
|
||||
## Raw value conversion (GetValue)
|
||||
|
||||
`GetValue` is a convenience helper that will either read a value into existing storage (type-deduced) or
|
||||
return a JSON value coerced into the specified type.
|
||||
|
||||
When reading into existing storage, it returns a boolean indicating whether that storage was modified.
|
||||
|
||||
If the JSON value cannot be converted to the specified type, an exception will be generated.
|
||||
|
||||
```c++
|
||||
std::string one;
|
||||
std::optional<std::string> two;
|
||||
|
||||
JsonUtils::GetValue(json, one);
|
||||
// one is populated or unchanged.
|
||||
|
||||
JsonUtils::GetValue(json, two);
|
||||
// two is populated, nullopt or unchanged
|
||||
|
||||
auto three = JsonUtils::GetValue<std::string>(json);
|
||||
// three is populated or zero-initialized
|
||||
|
||||
auto four = JsonUtils::GetValue<std::optional<std::string>>(json);
|
||||
// four is populated or nullopt
|
||||
```
|
||||
|
||||
## Key lookup (GetValueForKey)
|
||||
|
||||
`GetValueForKey` follows the same rules as `GetValue`, but takes an additional key.
|
||||
It is assumed that the JSON value passed to GetValueForKey is of `object` type.
|
||||
|
||||
```c++
|
||||
std::string one;
|
||||
std::optional<std::string> two;
|
||||
|
||||
JsonUtils::GetValueForKey(json, "firstKey", one);
|
||||
// one is populated or unchanged.
|
||||
|
||||
JsonUtils::GetValueForKey(json, "secondKey", two);
|
||||
// two is populated, nullopt or unchanged
|
||||
|
||||
auto three = JsonUtils::GetValueForKey<std::string>(json, "thirdKey");
|
||||
// three is populated or zero-initialized
|
||||
|
||||
auto four = JsonUtils::GetValueForKey<std::optional<std::string>>(json, "fourthKey");
|
||||
// four is populated or nullopt
|
||||
```
|
||||
|
||||
## Rationale: Value-Returning Getters
|
||||
|
||||
JsonUtils provides two types of `GetValue...`: value-returning and reference-filling.
|
||||
|
||||
The reference-filling fixtures use type deduction so that a developer does not
|
||||
need to specify template parameters on every `GetValue` call. It excels at
|
||||
populating class members during deserialization.
|
||||
|
||||
The value-returning fixtures, on the other hand, are very useful for partial
|
||||
deserialization and key detection when you do not need to deserialize an entire
|
||||
instance of a class or you need to reason about the presence of members.
|
||||
|
||||
To provide a concrete example of the latter, consider:
|
||||
|
||||
```c++
|
||||
if (const auto guid{ GetValueForKey<std::optional<GUID>>(json, "guid") })
|
||||
// This condition is only true if there was a "guid" member in the provided JSON object.
|
||||
// It can be accessed through *guid.
|
||||
}
|
||||
```
|
||||
|
||||
If you are... | Use
|
||||
--------------|-----
|
||||
Deserializing | `GetValue(..., storage)`
|
||||
Interrogating | `storage = GetValue<T>(...)`
|
||||
|
||||
## Converting User-Defined Types
|
||||
|
||||
All conversions are done using specializations of
|
||||
`JsonUtils::ConversionTrait<T>`. To implement a converter for a user-defined
|
||||
type, you must implement a specialization of `JsonUtils::ConversionTrait<T>`.
|
||||
|
||||
Every specialization over `T` must implement `static T FromJson(const Json::Value&)`
|
||||
and `static bool CanConvert(const Json::Value&)`.
|
||||
|
||||
```c++
|
||||
struct MyCustomType { int val; };
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<MyCustomType>
|
||||
{
|
||||
// This trait converts a string of the format "[0-9]" to a value of type MyCustomType.
|
||||
|
||||
static MyCustomType FromJson(const Json::Value& json)
|
||||
{
|
||||
return MyCustomType{ json.asString()[0] - '0' };
|
||||
}
|
||||
|
||||
static bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isString();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Converting User-Defined Enumerations
|
||||
|
||||
Enumeration types represent a single choice out of multiple options.
|
||||
|
||||
In a JSON data model, they are typically represented as strings.
|
||||
|
||||
For parsing enumerations, JsonUtils provides the `JSON_ENUM_MAPPER` macro. It
|
||||
can be used to establish a converter that will take a set of known strings and
|
||||
convert them to values.
|
||||
|
||||
```c++
|
||||
JSON_ENUM_MAPPER(CursorStyle)
|
||||
{
|
||||
// pair_type is provided by ENUM_MAPPER.
|
||||
JSON_MAPPINGS(5) = {
|
||||
pair_type{ "bar", CursorStyle::Bar },
|
||||
pair_type{ "vintage", CursorStyle::Vintage },
|
||||
pair_type{ "underscore", CursorStyle::Underscore },
|
||||
pair_type{ "filledBox", CursorStyle::FilledBox },
|
||||
pair_type{ "emptyBox", CursorStyle::EmptyBox }
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
If the enum mapper fails to convert the provided string, it will throw an
|
||||
exception.
|
||||
|
||||
### Converting User-Defined Flag Sets
|
||||
|
||||
Flags represent a multiple-choice selection. They are typically implemented as
|
||||
enums with bitfield values intended to be ORed together.
|
||||
|
||||
In JSON, a set of flags may be represented by a single string (`"flagName"`) or
|
||||
an array of strings (`["flagOne", "flagTwo"]`).
|
||||
|
||||
JsonUtils provides a `JSON_FLAG_MAPPER` macro that can be used to produce a
|
||||
specialization for a set of flags.
|
||||
|
||||
Given the following flag enum,
|
||||
|
||||
```c++
|
||||
enum class JsonTestFlags : int
|
||||
{
|
||||
FlagOne = 1 << 0,
|
||||
FlagTwo = 1 << 1
|
||||
};
|
||||
```
|
||||
|
||||
You can register a flag mapper with the `JSON_FLAG_MAPPER` macro as follows:
|
||||
|
||||
```c++
|
||||
JSON_FLAG_MAPPER(JsonTestFlags)
|
||||
{
|
||||
JSON_MAPPINGS(2) = {
|
||||
pair_type{ "flagOne", JsonTestFlags::FlagOne },
|
||||
pair_type{ "flagTwo", JsonTestFlags::FlagTwo },
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The `FLAG_MAPPER` also provides two convenience definitions, `AllSet` and
|
||||
`AllClear`, that can be used to represent "all choices" and "no choices"
|
||||
respectively.
|
||||
|
||||
```c++
|
||||
JSON_FLAG_MAPPER(JsonTestFlags)
|
||||
{
|
||||
JSON_MAPPINGS(4) = {
|
||||
pair_type{ "never", AllClear },
|
||||
pair_type{ "flagOne", JsonTestFlags::FlagOne },
|
||||
pair_type{ "flagTwo", JsonTestFlags::FlagTwo },
|
||||
pair_type{ "always", AllSet },
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Because flag values are additive, `["always", "flagOne"]` will result in the
|
||||
same behavior as `"always"`.
|
||||
|
||||
If the flag mapper encounters an unknown flag, it will throw an exception.
|
||||
|
||||
If the flag mapper encounters a logical discontinuity such as `["never", "flagOne"]`
|
||||
(as in the above example), it will throw an exception.
|
||||
|
||||
### Advanced Use
|
||||
|
||||
`GetValue` and `GetValueForKey` can be passed, as their final arguments, any
|
||||
value whose type implements the same interface as `ConversionTrait<T>`--that
|
||||
is, `FromJson(const Json::Value&)` and `CanConvert(const Json::Value&)`.
|
||||
|
||||
This allows for one-off conversions without a specialization of
|
||||
`ConversionTrait` or even stateful converters.
|
||||
|
||||
#### Stateful Converter Sample
|
||||
|
||||
```c++
|
||||
struct MultiplyingConverter {
|
||||
int BaseValue;
|
||||
|
||||
bool CanConvert(const Json::Value&) { return true; }
|
||||
|
||||
int FromJson(const Json::Value& value)
|
||||
{
|
||||
return value.asInt() * BaseValue;
|
||||
}
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
Json::Value json{ 66 }; // A JSON value containing the number 66
|
||||
MultiplyingConverter conv{ 10 };
|
||||
|
||||
auto v = JsonUtils::GetValue<int>(json, conv);
|
||||
// v is equal to 660.
|
||||
```
|
||||
|
||||
## Behavior Chart
|
||||
|
||||
### GetValue(T&) (type-deducing)
|
||||
|
||||
-|json type invalid|json null|valid
|
||||
-|-|-|-
|
||||
`T`|❌ exception|🔵 unchanged|✔ converted
|
||||
`std::optional<T>`|❌ exception|🟨 `nullopt`|✔ converted
|
||||
|
||||
### GetValue<T>() (returning)
|
||||
|
||||
-|json type invalid|json null|valid
|
||||
-|-|-|-
|
||||
`T`|❌ exception|🟨 `T{}` (zero value)|✔ converted
|
||||
`std::optional<T>`|❌ exception|🟨 `nullopt`|✔ converted
|
||||
|
||||
### GetValueForKey(T&) (type-deducing)
|
||||
|
||||
GetValueForKey builds on the behavior set from GetValue by adding
|
||||
a "key not found" state. The remaining three cases are the same.
|
||||
|
||||
val type|key not found|_json type invalid_|_json null_|_valid_
|
||||
-|-|-|-|-
|
||||
`T`|🔵 unchanged|_❌ exception_|_🔵 unchanged_|_✔ converted_
|
||||
`std::optional<T>`|_🔵 unchanged_|_❌ exception_|_🟨 `nullopt`_|_✔ converted_
|
||||
|
||||
### GetValueForKey<T>() (return value)
|
||||
|
||||
val type|key not found|_json type invalid_|_json null_|_valid_
|
||||
-|-|-|-|-
|
||||
`T`|🟨 `T{}` (zero value)|_❌ exception_|_🟨 `T{}` (zero value)_|_✔ converted_
|
||||
`std::optional<T>`|🟨 `nullopt`|_❌ exception_|_🟨 `nullopt`_|_✔ converted_
|
||||
|
||||
### Future Direction
|
||||
|
||||
These converters lend themselves very well to automatic _serialization_.
|
||||
@@ -1,35 +1,27 @@
|
||||
# Settings.json Documentation
|
||||
# Profiles.json Documentation
|
||||
|
||||
## Globals
|
||||
|
||||
Properties listed below affect the entire window, regardless of the profile settings.
|
||||
|
||||
| Property | Necessity | Type | Default | Description |
|
||||
| -------- | --------- | ---- | ------- | ----------- |
|
||||
| `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, 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. |
|
||||
| `initialCols` | _Required_ | Integer | `120` | The number of columns displayed in the window upon first load. |
|
||||
| `initialPosition` | Optional | String | `","` | The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If `launchMode` is set to `"maximized"`, the window will be maximized on the monitor specified by those coordinates. |
|
||||
| `initialRows` | _Required_ | Integer | `30` | The number of rows displayed in the window upon first load. |
|
||||
| `launchMode` | Optional | String | `default` | Defines whether the Terminal will launch as maximized or not. Possible values: `"default"`, `"maximized"` |
|
||||
| `theme` | _Required_ | String | `system` | Sets the theme of the application. Possible values: `"light"`, `"dark"`, `"system"` |
|
||||
| `rowsToScroll` | Optional | Integer | `system` | The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or "system". |
|
||||
| `requestedTheme` | _Required_ | String | `system` | Sets the theme of the application. Possible values: `"light"`, `"dark"`, `"system"` |
|
||||
| `showTerminalTitleInTitlebar` | _Required_ | Boolean | `true` | When set to `true`, titlebar displays the title of the selected tab. When set to `false`, titlebar displays "Windows Terminal". |
|
||||
| `showTabsInTitlebar` | Optional | Boolean | `true` | When set to `true`, the tabs are moved into the titlebar and the titlebar disappears. When set to `false`, the titlebar sits above the tabs. |
|
||||
| `snapToGridOnResize` | Optional | Boolean | `false` | When set to `true`, the window will snap to the nearest character boundary on resize. When `false`, the window will resize "smoothly" |
|
||||
| `tabWidthMode` | Optional | String | `equal` | Sets the width of the tabs. Possible values: <br><ul><li>`"equal"`: sizes each tab to the same width</li><li>`"titleLength"`: sizes each tab to the length of its title</li><li>`"compact"`: sizes each tab to the length of its title when focused, and shrinks to the size of only the icon when the tab is unfocused.</li></ul> |
|
||||
| `tabWidthMode` | Optional | String | `equal` | Sets the width of the tabs. Possible values: `"equal"`, `"titleLength"` |
|
||||
| `wordDelimiters` | Optional | String | <code> /\()"'-:,.;<>~!@#$%^&*|+=[]{}~?│</code><br>_(`│` is `U+2502 BOX DRAWINGS LIGHT VERTICAL`)_ | Determines the delimiters used in a double click selection. |
|
||||
| `confirmCloseAllTabs` | Optional | Boolean | `true` | When set to `true` closing a window with multiple tabs open WILL require confirmation. When set to `false` closing a window with multiple tabs open WILL NOT require confirmation. |
|
||||
| `startOnUserLogin` | Optional | Boolean | `false` | When set to `true` enables the launch of Windows Terminal at startup. Setting to `false` will disable the startup task entry. Note: if the Windows Terminal startup task entry is disabled either by org policy or by user action this setting will have no effect. |
|
||||
| `disabledProfileSources` | Optional | Array[String] | `[]` | Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup. This array can contain any combination of `Windows.Terminal.Wsl`, `Windows.Terminal.Azure`, or `Windows.Terminal.PowershellCore`. For more information, see [UsingJsonSettings.md](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md#dynamic-profiles) |
|
||||
| `experimental.rendering.forceFullRepaint` | Optional | Boolean | `false` | When set to true, we will redraw the entire screen each frame. When set to false, we will render only the updates to the screen between frames. |
|
||||
| `experimental.rendering.software` | Optional | Boolean | `false` | When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one. |
|
||||
|
||||
## Profiles
|
||||
|
||||
Properties listed below are specific to each unique profile.
|
||||
|
||||
| Property | Necessity | Type | Default | Description |
|
||||
@@ -45,31 +37,29 @@ Properties listed below are specific to each unique profile.
|
||||
| `backgroundImageStretchMode` | Optional | String | `uniformToFill` | Sets how the background image is resized to fill the window. Possible values: `"none"`, `"fill"`, `"uniform"`, `"uniformToFill"` |
|
||||
| `closeOnExit` | Optional | String | `graceful` | Sets how the profile reacts to termination or failure to launch. Possible values: `"graceful"` (close when `exit` is typed or the process exits normally), `"always"` (always close) and `"never"` (never close). `true` and `false` are accepted as synonyms for `"graceful"` and `"never"` respectively. |
|
||||
| `colorScheme` | Optional | String | `Campbell` | Name of the terminal color scheme to use. Color schemes are defined under `schemes`. |
|
||||
| `colorTable` | Optional | Array[String] | | Array of colors used in the profile if `colorscheme` is not set. Array follows the format defined in `schemes`. |
|
||||
| `commandline` | Optional | String | | Executable used in the profile. |
|
||||
| `cursorColor` | Optional | String | | Sets the cursor color of the profile. Overrides `cursorColor` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
|
||||
| `cursorHeight` | Optional | Integer | | Sets the percentage height of the cursor starting from the bottom. Only works when `cursorShape` is set to `"vintage"`. Accepts values from 25-100. |
|
||||
| `cursorShape` | Optional | String | `bar` | Sets the cursor shape for the profile. Possible values: `"vintage"` ( ▃ ), `"bar"` ( ┃ ), `"underscore"` ( ▁ ), `"filledBox"` ( █ ), `"emptyBox"` ( ▯ ) |
|
||||
| `fontFace` | Optional | String | `Cascadia Mono` | Name of the font face used in the profile. We will try to fallback to Consolas if this can't be found or is invalid. |
|
||||
| `fontFace` | Optional | String | `Cascadia Code` | Name of the font face used in the profile. We will try to fallback to Consolas if this can't be found or is invalid. |
|
||||
| `fontSize` | Optional | Integer | `12` | Sets the font size. |
|
||||
| `fontWeight` | Optional | String | `normal` | Sets the weight (lightness or heaviness of the strokes) for the given font. Possible values: `"thin"`, `"extra-light"`, `"light"`, `"semi-light"`, `"normal"`, `"medium"`, `"semi-bold"`, `"bold"`, `"extra-bold"`, `"black"`, `"extra-black"`, or the corresponding numeric representation of OpenType font weight. |
|
||||
| `foreground` | Optional | String | | Sets the foreground color of the profile. Overrides `foreground` set in color scheme if `colorscheme` is set. Uses hex color format: `#rgb` or `"#rrggbb"`. |
|
||||
| `hidden` | Optional | Boolean | `false` | If set to true, the profile will not appear in the list of profiles. This can be used to hide default profiles and dynamically generated profiles, while leaving them in your settings file. |
|
||||
| `historySize` | Optional | Integer | `9001` | The number of lines above the ones displayed in the window you can scroll back to. |
|
||||
| `icon` | Optional | String | | Image file location of the icon used in the profile. Displays within the tab and the dropdown menu. |
|
||||
| `padding` | Optional | String | `8, 8, 8, 8` | Sets the padding around the text within the window. Can have three different formats: `"#"` sets the same padding for all sides, `"#, #"` sets the same padding for left-right and top-bottom, and `"#, #, #, #"` sets the padding individually for left, top, right, and bottom. |
|
||||
| `scrollbarState` | Optional | String | `"visible"` | Defines the visibility of the scrollbar. Possible values: `"visible"`, `"hidden"` |
|
||||
| `scrollbarState` | Optional | String | | Defines the visibility of the scrollbar. Possible values: `"visible"`, `"hidden"` |
|
||||
| `selectionBackground` | Optional | String | | Sets the selection background color of the profile. Overrides `selectionBackground` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
|
||||
| `snapOnInput` | Optional | Boolean | `true` | When set to `true`, the window will scroll to the command input line when typing. When set to `false`, the window will not scroll when you start typing. |
|
||||
| `altGrAliasing` | Optional | Boolean | `true` | By default Windows treats Ctrl+Alt as an alias for AltGr. When altGrAliasing is set to false, this behavior will be disabled. |
|
||||
| `source` | Optional | String | | Stores the name of the profile generator that originated this profile. _There are no discoverable values for this field._ |
|
||||
| `startingDirectory` | Optional | String | `%USERPROFILE%` | The directory the shell starts in when it is loaded. |
|
||||
| `suppressApplicationTitle` | Optional | Boolean | `false` | When set to `true`, `tabTitle` overrides the default title of the tab and any title change messages from the application will be suppressed. When set to `false`, `tabTitle` behaves as normal. |
|
||||
| `suppressApplicationTitle` | Optional | Boolean | | When set to `true`, `tabTitle` overrides the default title of the tab and any title change messages from the application will be suppressed. When set to `false`, `tabTitle` behaves as normal. |
|
||||
| `tabTitle` | Optional | String | | If set, will replace the `name` as the title to pass to the shell on startup. Some shells (like `bash`) may choose to ignore this initial value, while others (`cmd`, `powershell`) may use this value over the lifetime of the application. |
|
||||
| `useAcrylic` | Optional | Boolean | `false` | When set to `true`, the window will have an acrylic background. When set to `false`, the window will have a plain, untextured background. The transparency only applies to focused windows due to OS limitation. |
|
||||
| `experimental.retroTerminalEffect` | Optional | Boolean | `false` | When set to `true`, enable retro terminal effects. This is an experimental feature, and its continued existence is not guaranteed. |
|
||||
|
||||
## Schemes
|
||||
|
||||
Properties listed below are specific to each color scheme. [ColorTool](https://github.com/microsoft/terminal/tree/master/src/tools/ColorTool) is a great tool you can use to create and explore new color schemes. All colors use hex color format.
|
||||
|
||||
| Property | Necessity | Type | Description |
|
||||
@@ -97,13 +87,12 @@ Properties listed below are specific to each color scheme. [ColorTool](https://g
|
||||
| `yellow` | _Required_ | String | Sets the color used as ANSI yellow. |
|
||||
|
||||
## Keybindings
|
||||
|
||||
Properties listed below are specific to each custom key binding.
|
||||
|
||||
| Property | Necessity | Type | Description |
|
||||
| -------- | ---- | ----------- | ----------- |
|
||||
| `command` | _Required_ | String | The command executed when the associated key bindings are pressed. |
|
||||
| `keys` | _Required_ | Array[String] or String | Defines the key combinations used to call the command. |
|
||||
| `keys` | _Required_ | Array[String] | Defines the key combinations used to call the command. |
|
||||
| `action` | Optional | String | Adds additional functionality to certain commands. |
|
||||
|
||||
### Implemented Commands and Actions
|
||||
@@ -122,13 +111,14 @@ For commands with arguments:
|
||||
|
||||
| Command | Command Description | Action (*=required) | Action Arguments | Argument Descriptions |
|
||||
| ------- | ------------------- | ------ | ---------------- | ----------------- |
|
||||
| `adjustFontSize` | Change the text size by a specified point amount. | `delta` | integer | Amount of size change per command invocation. |
|
||||
| `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. | 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. |
|
||||
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
|
||||
| `decreaseFontSize` | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
|
||||
| `duplicateTab` | Make a copy and open the current tab. | | | |
|
||||
| `find` | Open the search dialog box. | | | |
|
||||
| `increaseFontSize` | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
|
||||
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
|
||||
| `newTab` | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
|
||||
| `nextTab` | Open the tab to the right of the current one. | | | |
|
||||
@@ -142,8 +132,7 @@ For commands with arguments:
|
||||
| `scrollUp` | Move the screen up. | | | |
|
||||
| `scrollUpPage` | Move the screen up a whole page. | | | |
|
||||
| `scrollDownPage` | Move the screen down a whole page. | | | |
|
||||
| `sendInput` | Sends some text input to the shell. | `input` | string | The text input to feed into the shell.<br>ANSI escape sequences may be used. Escape codes like `\x1b` must be written as `\u001b`.<br>For instance the input `"text\n"` will write "text" followed by a newline. `"\u001b[D"` will behave as if the left arrow button had been pressed. |
|
||||
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile`<br>7. `splitMode` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string<br>7. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name.<br>7. Controls how the pane splits. Only accepts `duplicate` which will duplicate the focused pane's profile into a new pane. |
|
||||
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
|
||||
| `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
|
||||
| `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | |
|
||||
| `unbound` | Unbind the associated keys from any command. | | | |
|
||||
@@ -151,20 +140,18 @@ For commands with arguments:
|
||||
### Accepted Modifiers and Keys
|
||||
|
||||
#### Modifiers
|
||||
`ctrl+`, `shift+`, `alt+`
|
||||
`Ctrl+`, `Shift+`, `Alt+`
|
||||
|
||||
#### Keys
|
||||
|
||||
| Type | Keys |
|
||||
| ---- | ---- |
|
||||
| Function and Alphanumeric Keys | `f1-f24`, `a-z`, `0-9` |
|
||||
| Symbols | ``` ` ```, `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/` |
|
||||
| Arrow Keys | `down`, `left`, `right`, `up`, `pagedown`, `pageup`, `pgdn`, `pgup`, `end`, `home`, `plus`, `app`, `menu` |
|
||||
| Arrow Keys | `down`, `left`, `right`, `up`, `pagedown`, `pageup`, `pgdn`, `pgup`, `end`, `home`, `plus` |
|
||||
| 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` |
|
||||
|
||||
## Background Images and Icons
|
||||
|
||||
Some Terminal settings allow you to specify custom background images and icons. It is recommended that custom images and icons are stored in system-provided folders and are referred to using the correct [URI Schemes](https://docs.microsoft.com/en-us/windows/uwp/app-resources/uri-schemes). URI Schemes provide a way to reference files independent of their physical paths (which may change in the future).
|
||||
|
||||
The most useful URI schemes to remember when customizing background images and icons are:
|
||||
@@ -177,7 +164,6 @@ The most useful URI schemes to remember when customizing background images and i
|
||||
> ⚠ Note: Do not rely on file references using the `ms-appx` URI Scheme (i.e. icons). These files are considered an internal implementation detail and may change name/location or may be omitted in the future.
|
||||
|
||||
### Icons
|
||||
|
||||
Terminal displays icons for each of your profiles which Terminal generates for any built-in shells - PowerShell Core, PowerShell, and any installed Linux/WSL distros. Each profile refers to a stock icon via the `ms-appx` URI Scheme.
|
||||
|
||||
> ⚠ Note: Do not rely on the files referenced by the `ms-appx` URI Scheme - they are considered an internal implementation detail and may change name/location or may be omitted in the future.
|
||||
@@ -191,7 +177,6 @@ You can refer to you own icons if you wish, e.g.:
|
||||
> 👉 Tip: Icons should be sized to 32x32px in an appropriate raster image format (e.g. .PNG, .GIF, or .ICO) to avoid having to scale your icons during runtime (causing a noticeable delay and loss of quality.)
|
||||
|
||||
### Custom Background Images
|
||||
|
||||
You can apply a background image to each of your profiles, allowing you to configure/brand/style each of your profiles independently from one another if you wish.
|
||||
|
||||
To do so, specify your preferred `backgroundImage`, position it using `backgroundImageAlignment`, set its opacity with `backgroundImageOpacity`, and/or specify how your image fill the available space using `backgroundImageStretchMode`.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"$id": "https://github.com/microsoft/terminal/blob/master/doc/cascadia/profiles.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema#",
|
||||
"title": "Microsoft's Windows Terminal Settings Profile Schema",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"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+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
|
||||
"pattern": "^(?<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)$",
|
||||
"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.\napp, menu\tMENU key\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)"
|
||||
"description": "The string should fit the format \"[ctrl+][alt+][shift+]<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 (+)"
|
||||
},
|
||||
"Color": {
|
||||
"default": "#",
|
||||
@@ -18,14 +18,6 @@
|
||||
"pattern": "^(-?\\d+)?(,\\s?(-?\\d+)?)?$",
|
||||
"type": "string"
|
||||
},
|
||||
"DynamicProfileSource": {
|
||||
"enum": [
|
||||
"Windows.Terminal.Wsl",
|
||||
"Windows.Terminal.Azure",
|
||||
"Windows.Terminal.PowershellCore"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ProfileGuid": {
|
||||
"default": "{}",
|
||||
"pattern": "^\\{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\}$",
|
||||
@@ -33,43 +25,59 @@
|
||||
},
|
||||
"ShortcutActionName": {
|
||||
"enum": [
|
||||
"adjustFontSize",
|
||||
"closeOtherTabs",
|
||||
"closePane",
|
||||
"closeTab",
|
||||
"closeTabsAfter",
|
||||
"closeWindow",
|
||||
"commandPalette",
|
||||
"copy",
|
||||
"copyTextWithoutNewlines",
|
||||
"decreaseFontSize",
|
||||
"duplicateTab",
|
||||
"find",
|
||||
"increaseFontSize",
|
||||
"moveFocus",
|
||||
"moveFocusDown",
|
||||
"moveFocusLeft",
|
||||
"moveFocusRight",
|
||||
"moveFocusUp",
|
||||
"newTab",
|
||||
"newTabProfile0",
|
||||
"newTabProfile1",
|
||||
"newTabProfile2",
|
||||
"newTabProfile3",
|
||||
"newTabProfile4",
|
||||
"newTabProfile5",
|
||||
"newTabProfile6",
|
||||
"newTabProfile7",
|
||||
"newTabProfile8",
|
||||
"nextTab",
|
||||
"openNewTabDropdown",
|
||||
"openSettings",
|
||||
"openTabColorPicker",
|
||||
"paste",
|
||||
"prevTab",
|
||||
"renameTab",
|
||||
"resetFontSize",
|
||||
"resizePane",
|
||||
"resizePaneDown",
|
||||
"resizePaneLeft",
|
||||
"resizePaneRight",
|
||||
"resizePaneUp",
|
||||
"scrollDown",
|
||||
"scrollDownPage",
|
||||
"scrollUp",
|
||||
"scrollUpPage",
|
||||
"sendInput",
|
||||
"setColorScheme",
|
||||
"setTabColor",
|
||||
"splitHorizontal",
|
||||
"splitVertical",
|
||||
"splitPane",
|
||||
"switchToTab",
|
||||
"tabSearch",
|
||||
"toggleAlwaysOnTop",
|
||||
"toggleFocusMode",
|
||||
"switchToTab0",
|
||||
"switchToTab1",
|
||||
"switchToTab2",
|
||||
"switchToTab3",
|
||||
"switchToTab4",
|
||||
"switchToTab5",
|
||||
"switchToTab6",
|
||||
"switchToTab7",
|
||||
"switchToTab8",
|
||||
"toggleFullscreen",
|
||||
"toggleRetroEffect",
|
||||
"wt",
|
||||
"unbound"
|
||||
"find"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -90,40 +98,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CopyFormat": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"html",
|
||||
"rtf"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"html",
|
||||
"rtf",
|
||||
"all",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"AnchorKey": {
|
||||
"enum": [
|
||||
"ctrl",
|
||||
"alt",
|
||||
"shift"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NewTerminalArgs": {
|
||||
"properties": {
|
||||
"commandline": {
|
||||
@@ -144,7 +118,7 @@
|
||||
},
|
||||
"index": {
|
||||
"type": "integer",
|
||||
"description": "The index of the profile in the new tab dropdown (starting at 0)"
|
||||
"description": "The index of the profile in the new tab dropdown to open"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -161,23 +135,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdjustFontSizeAction": {
|
||||
"description": "Arguments corresponding to an Adjust Font Size Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "adjustFontSize" },
|
||||
"delta": {
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "How much to change the current font point size"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [ "delta" ]
|
||||
},
|
||||
"CopyAction": {
|
||||
"description": "Arguments corresponding to a Copy Text Action",
|
||||
"allOf": [
|
||||
@@ -185,22 +142,10 @@
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "copy" },
|
||||
"singleLine": {
|
||||
"trimWhitespace": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
"default": true,
|
||||
"description": "If true, whitespace is removed and newlines are maintained. If false, newlines are removed and whitespace is maintained."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,28 +214,6 @@
|
||||
],
|
||||
"required": [ "direction" ]
|
||||
},
|
||||
"SendInputAction": {
|
||||
"description": "Arguments corresponding to a Send Input Action",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShortcutAction"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"pattern": "sendInput"
|
||||
},
|
||||
"input": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The text input to feed into the shell. ANSI escape sequences may be used. Escape codes like \\x1b must be written as \\u001b."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [ "input" ]
|
||||
},
|
||||
"SplitPaneAction": {
|
||||
"description": "Arguments corresponding to a Split Pane Action",
|
||||
"allOf": [
|
||||
@@ -302,129 +225,12 @@
|
||||
"split": {
|
||||
"$ref": "#/definitions/SplitState",
|
||||
"default": "auto",
|
||||
"description": "The orientation to split the pane in. Possible values:\n -\"auto\" (splits pane based on remaining space)\n -\"horizontal\" (think [-])\n -\"vertical\" (think [|])"
|
||||
},
|
||||
"splitMode": {
|
||||
"default": "duplicate",
|
||||
"description": "Control how the pane splits. Only accepts \"duplicate\" which will duplicate the focused pane's profile into a new pane."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"OpenSettingsAction": {
|
||||
"description": "Arguments corresponding to a Open Settings Action",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ShortcutAction"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"pattern": "openSettings"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"default": "settingsFile",
|
||||
"description": "The settings file to open.",
|
||||
"enum": [
|
||||
"settingsFile",
|
||||
"defaultsFile",
|
||||
"allFiles"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SetTabColorAction": {
|
||||
"description": "Arguments corresponding to a Set Tab Color Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "setTabColor" },
|
||||
"color": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": null,
|
||||
"description": "If provided, will set the tab's color to the given value. If omitted, will reset the tab's color."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SetColorSchemeAction": {
|
||||
"description": "Arguments corresponding to a Set Color Scheme Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "setColorScheme" },
|
||||
"colorScheme": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "the name of the scheme to apply to the active pane"
|
||||
"description": "The orientation to split the pane in, either vertical (think [|]), horizontal (think [-]), or auto (splits pane based on remaining space)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [ "colorScheme" ]
|
||||
},
|
||||
"WtAction": {
|
||||
"description": "Arguments corresponding to a wt Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "wt" },
|
||||
"commandline": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "a `wt` commandline to run in the current window"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [ "commandline" ]
|
||||
},
|
||||
"CloseOtherTabsAction": {
|
||||
"description": "Arguments for a closeOtherTabs action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "closeOtherTabs" },
|
||||
"index": {
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": null }
|
||||
],
|
||||
"default": "",
|
||||
"description": "Close the tabs other than the one at this index. If no index is provided, use the focused tab's index."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CloseTabsAfterAction": {
|
||||
"description": "Arguments for a closeTabsAfter action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "closeTabsAfter" },
|
||||
"index": {
|
||||
"oneOf": [
|
||||
{ "type": "integer" },
|
||||
{ "type": null }
|
||||
],
|
||||
"default": "",
|
||||
"description": "Close the tabs following the tab at this index. If no index is provided, use the focused tab's index."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"required": [ "split" ]
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
@@ -432,26 +238,17 @@
|
||||
"command": {
|
||||
"description": "The action executed when the associated key bindings are pressed.",
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/AdjustFontSizeAction" },
|
||||
{ "$ref": "#/definitions/CopyAction" },
|
||||
{ "$ref": "#/definitions/ShortcutActionName" },
|
||||
{ "$ref": "#/definitions/NewTabAction" },
|
||||
{ "$ref": "#/definitions/SwitchToTabAction" },
|
||||
{ "$ref": "#/definitions/MoveFocusAction" },
|
||||
{ "$ref": "#/definitions/ResizePaneAction" },
|
||||
{ "$ref": "#/definitions/SendInputAction" },
|
||||
{ "$ref": "#/definitions/SplitPaneAction" },
|
||||
{ "$ref": "#/definitions/OpenSettingsAction" },
|
||||
{ "$ref": "#/definitions/SetTabColorAction" },
|
||||
{ "$ref": "#/definitions/SetColorSchemeAction" },
|
||||
{ "$ref": "#/definitions/WtAction" },
|
||||
{ "$ref": "#/definitions/CloseOtherTabsAction" },
|
||||
{ "$ref": "#/definitions/CloseTabsAfterAction" },
|
||||
{ "type": "null" }
|
||||
{ "$ref": "#/definitions/SplitPaneAction" }
|
||||
]
|
||||
},
|
||||
"keys": {
|
||||
"description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key",
|
||||
"description": "Defines the key combinations used to call the command.",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeyChordSegment"
|
||||
@@ -476,14 +273,9 @@
|
||||
"additionalProperties": true,
|
||||
"description": "Properties that affect the entire window, regardless of the profile settings.",
|
||||
"properties": {
|
||||
"alwaysOnTop": {
|
||||
"default": false,
|
||||
"description": "When set to true, the window is created on top of all other windows. If multiple windows are all \"always on top\", the most recently focused one will be the topmost",
|
||||
"type": "boolean"
|
||||
},
|
||||
"alwaysShowTabs": {
|
||||
"default": true,
|
||||
"description": "When set to true, tabs are always displayed. When set to false and \"showTabsInTitlebar\" is set to false, tabs only appear after opening a new tab.",
|
||||
"description": "When set to true, tabs are always displayed. When set to false and showTabsInTitlebar is set to false, tabs only appear after opening a new tab.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyOnSelect": {
|
||||
@@ -491,39 +283,9 @@
|
||||
"description": "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.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyFormatting": {
|
||||
"default": true,
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
|
||||
"$ref": "#/definitions/CopyFormat"
|
||||
},
|
||||
"largePasteWarning": {
|
||||
"default": true,
|
||||
"description": "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.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"multiLinePasteWarning": {
|
||||
"default": true,
|
||||
"description": "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.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultProfile": {
|
||||
"description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\".",
|
||||
"type": "string"
|
||||
},
|
||||
"disabledProfileSources": {
|
||||
"description": "Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/DynamicProfileSource"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"experimental.rendering.forceFullRepaint": {
|
||||
"description": "When set to true, we will redraw the entire screen each frame. When set to false, we will render only the updates to the screen between frames.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental.rendering.software": {
|
||||
"description": "When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one.",
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/ProfileGuid",
|
||||
"description": "Sets the default profile. Opens by clicking the '+' icon or typing the key binding assigned to 'newTab'. The guid of the desired default profile is used as the value."
|
||||
},
|
||||
"initialCols": {
|
||||
"default": 120,
|
||||
@@ -534,7 +296,7 @@
|
||||
},
|
||||
"initialPosition": {
|
||||
"$ref": "#/definitions/Coordinates",
|
||||
"description": "The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If \"launchMode\" is set to maximized, the window will be maximized on the monitor specified by those coordinates."
|
||||
"description": "The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If launchMode is set to maximized, the window will be maximized on the monitor specified by those coordinates."
|
||||
},
|
||||
"initialRows": {
|
||||
"default": 30,
|
||||
@@ -543,16 +305,10 @@
|
||||
"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, full screen, or in a window.",
|
||||
"description": "Defines whether the Terminal will launch as maximized or not.",
|
||||
"enum": [
|
||||
"fullscreen",
|
||||
"maximized",
|
||||
"default"
|
||||
],
|
||||
@@ -560,11 +316,10 @@
|
||||
},
|
||||
"rowsToScroll": {
|
||||
"default": "system",
|
||||
"description": "This parameter once allowed you to override the systemwide \"choose how many lines to scroll at one time\" setting. It no longer does so.",
|
||||
"description": "The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or 'system'.",
|
||||
"maximum": 999,
|
||||
"minimum": 0,
|
||||
"type": [ "integer", "string" ],
|
||||
"deprecated": true
|
||||
"type": ["integer", "string"]
|
||||
},
|
||||
"keybindings": {
|
||||
"description": "Properties are specific to each custom key binding.",
|
||||
@@ -573,9 +328,9 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"theme": {
|
||||
"requestedTheme": {
|
||||
"default": "system",
|
||||
"description": "Sets the theme of the application. The special value \"system\" refers to the active Windows system theme.",
|
||||
"description": "Sets the theme of the application.",
|
||||
"enum": [
|
||||
"light",
|
||||
"dark",
|
||||
@@ -590,19 +345,18 @@
|
||||
},
|
||||
"showTerminalTitleInTitlebar": {
|
||||
"default": true,
|
||||
"description": "When set to true, titlebar displays the title of the selected tab. When set to false, titlebar displays \"Windows Terminal\".",
|
||||
"description": "When set to true, titlebar displays the title of the selected tab. When set to false, titlebar displays 'Windows Terminal'.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"snapToGridOnResize": {
|
||||
"default": false,
|
||||
"description": "When set to true, the window will snap to the nearest character boundary on resize. When false, the window will resize smoothly",
|
||||
"description": "When set to true, the window will snap to the nearest character boundary on resize. When false, the window will resize 'smoothly'",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tabWidthMode": {
|
||||
"default": "equal",
|
||||
"description": "Sets the width of the tabs. Possible values include:\n -\"equal\" sizes each tab to the same width\n -\"titleLength\" sizes each tab to the length of its title\n -\"compact\" sizes each tab to the length of its title when focused, and shrinks to the size of only the icon when the tab is unfocused.",
|
||||
"description": "Sets the width of the tabs.",
|
||||
"enum": [
|
||||
"compact",
|
||||
"equal",
|
||||
"titleLength"
|
||||
],
|
||||
@@ -615,13 +369,8 @@
|
||||
},
|
||||
"confirmCloseAllTabs": {
|
||||
"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"
|
||||
"description": " When set to `true` closing a window with multiple tabs open WILL require confirmation. When set to `false` closing a window with multiple tabs open WILL NOT require confirmation.",
|
||||
"type":"boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -653,12 +402,12 @@
|
||||
"background": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": "#0c0c0c",
|
||||
"description": "Sets the background color of the text. Overrides \"background\" from the color scheme. Uses hex color format: \"#rrggbb\".",
|
||||
"description": "Sets the background color of the profile. Overrides background set in color scheme if colorscheme is set. Uses hex color format: \"#rrggbb\".",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"backgroundImage": {
|
||||
"description": "Sets the file location of the image to draw over the window background.",
|
||||
"type": ["string", "null"]
|
||||
"description": "Sets the file location of the Image to draw over the window background.",
|
||||
"type": "string"
|
||||
},
|
||||
"backgroundImageAlignment": {
|
||||
"default": "center",
|
||||
@@ -673,14 +422,12 @@
|
||||
"topLeft",
|
||||
"topRight"
|
||||
],
|
||||
"description": "Sets how the background image aligns to the boundaries of the window. Possible values: \"center\", \"left\", \"top\", \"right\", \"bottom\", \"topLeft\", \"topRight\", \"bottomLeft\", \"bottomRight\"",
|
||||
"type": "string"
|
||||
},
|
||||
"backgroundImageOpacity": {
|
||||
"default": 1.0,
|
||||
"description": "Sets the transparency of the background image. Accepts floating point values from 0-1.",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0,
|
||||
"description": "(Not in SettingsSchema.md)",
|
||||
"maximum": 1,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"backgroundImageStretchMode": {
|
||||
@@ -696,7 +443,7 @@
|
||||
},
|
||||
"closeOnExit": {
|
||||
"default": "graceful",
|
||||
"description": "Sets how the profile reacts to termination or failure to launch. Possible values:\n -\"graceful\" (close when exit is typed or the process exits normally)\n -\"always\" (always close)\n -\"never\" (never close).\ntrue and false are accepted as synonyms for \"graceful\" and \"never\" respectively.",
|
||||
"description": "Sets how the profile reacts to termination or failure to launch. Possible values: \"graceful\" (close when exit is typed or the process exits normally), \"always\" (always close) and \"never\" (never close). true and false are accepted as synonyms for \"graceful\" and \"never\" respectively.",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
@@ -716,27 +463,109 @@
|
||||
"description": "Name of the terminal color scheme to use. Color schemes are defined under \"schemes\".",
|
||||
"type": "string"
|
||||
},
|
||||
"colorTable": {
|
||||
"description": "Array of colors used in the profile if colorscheme is not set. Colors use hex color format: \"#rrggbb\". Ordering is as follows: [black, red, green, yellow, blue, magenta, cyan, white, bright black, bright red, bright green, bright yellow, bright blue, bright magenta, bright cyan, bright white]",
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"background": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the background color of the color table."
|
||||
},
|
||||
"black": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI black."
|
||||
},
|
||||
"blue": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI blue."
|
||||
},
|
||||
"brightBlack": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI bright black."
|
||||
},
|
||||
"brightBlue": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI bright blue."
|
||||
},
|
||||
"brightCyan": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI bright cyan."
|
||||
},
|
||||
"brightGreen": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI bright green."
|
||||
},
|
||||
"brightPurple": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI bright purple."
|
||||
},
|
||||
"brightRed": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI bright red."
|
||||
},
|
||||
"brightWhite": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI bright white."
|
||||
},
|
||||
"brightYellow": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI bright yellow."
|
||||
},
|
||||
"cyan": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI cyan."
|
||||
},
|
||||
"foreground": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the foreground color of the color table."
|
||||
},
|
||||
"green": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI green."
|
||||
},
|
||||
"purple": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI purple."
|
||||
},
|
||||
"red": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI red."
|
||||
},
|
||||
"white": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI white."
|
||||
},
|
||||
"yellow": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the color used as ANSI yellow."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"commandline": {
|
||||
"description": "Executable used in the profile.",
|
||||
"type": "string"
|
||||
},
|
||||
"connectionType": {
|
||||
"$ref": "#/definitions/ProfileGuid",
|
||||
"description": "A GUID reference to a connection type. Currently undocumented as of 0.3, this is used for Azure Cloud Shell"
|
||||
},
|
||||
"cursorColor": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/Color" },
|
||||
{"type": "null"}
|
||||
],
|
||||
"description": "Sets the color of the cursor. Overrides the cursor color from the color scheme. Uses hex color format: \"#rrggbb\"."
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the cursor color of the profile. Overrides cursor color set in color scheme if colorscheme is set. Uses hex color format: \"#rrggbb\"."
|
||||
},
|
||||
"cursorHeight": {
|
||||
"description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 25-100.",
|
||||
"maximum": 100,
|
||||
"minimum": 25,
|
||||
"type": ["integer","null"],
|
||||
"default": 25
|
||||
"type": "integer"
|
||||
},
|
||||
"cursorShape": {
|
||||
"default": "bar",
|
||||
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
|
||||
"description": "Sets the cursor shape for the profile. Possible values: \"vintage\" ( ▃ ), \"bar\" ( ┃, default ), \"underscore\" ( ▁ ), \"filledBox\" ( █ ), \"emptyBox\" ( ▯ )",
|
||||
"enum": [
|
||||
"bar",
|
||||
"emptyBox",
|
||||
@@ -751,47 +580,20 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"fontFace": {
|
||||
"default": "Cascadia Mono",
|
||||
"default": "Cascadia Code",
|
||||
"description": "Name of the font face used in the profile.",
|
||||
"type": "string"
|
||||
},
|
||||
"fontSize": {
|
||||
"default": 12,
|
||||
"description": "Size of the font in points.",
|
||||
"description": "Sets the font size.",
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"fontWeight": {
|
||||
"default": "normal",
|
||||
"description": "Sets the weight (lightness or heaviness of the strokes) for the given font. Possible values:\n -\"thin\"\n -\"extra-light\"\n -\"light\"\n -\"semi-light\"\n -\"normal\" (default)\n -\"medium\"\n -\"semi-bold\"\n -\"bold\"\n -\"extra-bold\"\n -\"black\"\n -\"extra-black\" or the corresponding numeric representation of OpenType font weight.",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"thin",
|
||||
"extra-light",
|
||||
"light",
|
||||
"semi-light",
|
||||
"normal",
|
||||
"medium",
|
||||
"semi-bold",
|
||||
"bold",
|
||||
"extra-bold",
|
||||
"black",
|
||||
"extra-black"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"maximum": 990,
|
||||
"minimum": 100,
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"foreground": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": "#cccccc",
|
||||
"description": "Sets the text color. Overrides \"foreground\" from the color scheme. Uses hex color format: \"#rrggbb\".",
|
||||
"description": "Sets the foreground color of the profile. Overrides foreground set in color scheme if colorscheme is set. Uses hex color format: \"#rrggbb\".",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"guid": {
|
||||
@@ -811,7 +613,7 @@
|
||||
},
|
||||
"icon": {
|
||||
"description": "Image file location of the icon used in the profile. Displays within the tab and the dropdown menu.",
|
||||
"type": ["string", "null"]
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the profile. Displays in the dropdown menu.",
|
||||
@@ -820,16 +622,9 @@
|
||||
},
|
||||
"padding": {
|
||||
"default": "8, 8, 8, 8",
|
||||
"description": "Sets the padding around the text within the window. Can have three different formats:\n -\"#\" sets the same padding for all sides \n -\"#, #\" sets the same padding for left-right and top-bottom\n -\"#, #, #, #\" sets the padding individually for left, top, right, and bottom.",
|
||||
"oneOf": [
|
||||
{
|
||||
"pattern": "^-?[0-9]+(\\.[0-9]+)?( *, *-?[0-9]+(\\.[0-9]+)?|( *, *-?[0-9]+(\\.[0-9]+)?){3})?$",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
"description": "Sets the padding around the text within the window. Can have three different formats: \"#\" sets the same padding for all sides, \"#, #\" sets the same padding for left-right and top-bottom, and \"#, #, #, #\" sets the padding individually for left, top, right, and bottom.",
|
||||
"pattern": "^-?[0-9]+(\\.[0-9]+)?( *, *-?[0-9]+(\\.[0-9]+)?|( *, *-?[0-9]+(\\.[0-9]+)?){3})?$",
|
||||
"type": "string"
|
||||
},
|
||||
"scrollbarState": {
|
||||
"default": "visible",
|
||||
@@ -841,25 +636,17 @@
|
||||
"type": "string"
|
||||
},
|
||||
"selectionBackground": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/Color"},
|
||||
{ "type": "null" }
|
||||
],
|
||||
"description": "Sets the background color of selected text. Overrides selectionBackground set in the color scheme. Uses hex color format: \"#rrggbb\"."
|
||||
"$ref": "#/definitions/Color",
|
||||
"description": "Sets the selection background color of the profile. Overrides selection background set in color scheme if colorscheme is set. Uses hex color format: \"#rrggbb\"."
|
||||
},
|
||||
"snapOnInput": {
|
||||
"default": true,
|
||||
"description": "When set to true, the window will scroll to the command input line when typing. When set to false, the window will not scroll when you start typing.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"altGrAliasing": {
|
||||
"default": true,
|
||||
"description": "By default Windows treats Ctrl+Alt as an alias for AltGr. When altGrAliasing is set to false, this behavior will be disabled.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"source": {
|
||||
"description": "Stores the name of the profile generator that originated this profile.",
|
||||
"type": ["string", "null"]
|
||||
"type": "string"
|
||||
},
|
||||
"startingDirectory": {
|
||||
"description": "The directory the shell starts in when it is loaded.",
|
||||
@@ -867,12 +654,11 @@
|
||||
},
|
||||
"suppressApplicationTitle": {
|
||||
"description": "When set to true, tabTitle overrides the default title of the tab and any title change messages from the application will be suppressed. When set to false, tabTitle behaves as normal.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
"type": "boolean"
|
||||
},
|
||||
"tabTitle": {
|
||||
"description": "If set, will replace the name as the title to pass to the shell on startup. Some shells (like bash) may choose to ignore this initial value, while others (cmd, powershell) may use this value over the lifetime of the application.",
|
||||
"type": ["string", "null"]
|
||||
"type": "string"
|
||||
},
|
||||
"useAcrylic": {
|
||||
"default": false,
|
||||
@@ -1003,15 +789,37 @@
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/Globals" },
|
||||
"oneOf": [
|
||||
{
|
||||
"additionalItems": true,
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/Globals" },
|
||||
{
|
||||
"additionalItems": true,
|
||||
"properties": {
|
||||
"profiles": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/ProfileList" },
|
||||
{ "$ref": "#/definitions/ProfilesObject" }
|
||||
]
|
||||
},
|
||||
"schemes": { "$ref": "#/definitions/SchemeList" }
|
||||
},
|
||||
"required": [
|
||||
"profiles",
|
||||
"schemes",
|
||||
"defaultProfile"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"additionalItems": false,
|
||||
"properties": {
|
||||
"globals": { "$ref": "#/definitions/Globals" },
|
||||
"profiles": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/ProfileList" },
|
||||
{ "$ref": "#/definitions/ProfilesObject" }
|
||||
{ "$ref": "#/definitions/ProfileList" },
|
||||
{ "$ref": "#/definitions/ProfilesObject" }
|
||||
]
|
||||
},
|
||||
"schemes": { "$ref": "#/definitions/SchemeList" }
|
||||
@@ -1019,7 +827,7 @@
|
||||
"required": [
|
||||
"profiles",
|
||||
"schemes",
|
||||
"defaultProfile"
|
||||
"globals"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-07-31
|
||||
last updated: 2020-08-03
|
||||
issue id: #1337
|
||||
---
|
||||
# Per-Profile Tab Colors
|
||||
|
||||
## Abstract
|
||||
|
||||
This spec describes a way to specify tab colors in a profile in a way that will
|
||||
be forward compatible with theming the Terminal. This spec will be largely
|
||||
dedicated to the design of a single setting, but within the context of theming.
|
||||
|
||||
## Inspiration
|
||||
|
||||
Following the addition of the Tab Color Picker in [#3789], we've had numerous
|
||||
requests for the ability to set the color of a tab directly within a profile.
|
||||
While largely we're tracking theming in [#3327], including the specification of
|
||||
a tab color, the theming spec ([#5772] )is very large and will take a while to
|
||||
revise and approve. This spec is intended to pull a single point out from that
|
||||
spec to make it more easily reviewable, and implement it in a way that will
|
||||
continue working when we add support for themes in the future.
|
||||
|
||||
## Solution Design
|
||||
|
||||
To enable per-profile tab colors, we'll add a single setting: `tabColor`. For
|
||||
now<sup>[[1](#user-content-footnote-1)]</sup>, this setting will accept any
|
||||
`#rrggbb` color string.
|
||||
|
||||
Since each profile creates a `Pane` with a `TermControl`, we'll need to store
|
||||
this color not in the `Tab`, but somewhere below `Pane`, so that when you switch
|
||||
between Panes in a tab with different `tabColor`s, the color will update
|
||||
automatically. When a new `TermControl` is created, we'll store this color in the
|
||||
`TermControl`'s `Terminal` core. This is to support the future possibility of
|
||||
setting the tab color via VT sequences.
|
||||
|
||||
A Tab's color will be the result of layering a variety of sources, from the bottom up:
|
||||
|
||||
Color | | Set by
|
||||
-- | -- | --
|
||||
Runtime Color | _optional_ |Color Picker / `setTabColor` action
|
||||
Control Tab Color | _optional_ | Profile's `tabColor`, or a color set by VT
|
||||
Theme Tab Background | _optional_ | `tab.backgroundColor` in the theme
|
||||
Tab Default Color | **default** | TabView in XAML
|
||||
|
||||
Some examples:
|
||||
* **Scenario 1**: The user has set `"tabColor": "#ff0000"` in their profile.
|
||||
When they create tabs with that profile, instead of appearing in the default
|
||||
color for the TabView, they'll be `#ff0000`.
|
||||
* **Scenario 2**: The user has set `"tabColor": "#ff0000"` in their profile.
|
||||
When they try to set the color for that tab (with the color picker) to
|
||||
`#0000ff`, the tab's color is updated to reflect this new blue color. When
|
||||
they clear the runtime color (with the color picker), the tab will return to
|
||||
`#ff0000`.
|
||||
* **Scenario 3**: The user has two profiles with colors set, one to `"tabColor":
|
||||
"#ff0000"`, and the other with `"tabColor": "#00ff00"`. If they open these
|
||||
profiles in two panes side-by side, the tab's color will update to reflect the
|
||||
color from the currently-focused control.
|
||||
* **Scenario 4**: The user has two profiles with colors set, one to `"tabColor":
|
||||
"#ff0000"`, and the other with `"tabColor": "#00ff00"`. If they open these
|
||||
profiles in two panes side-by side, and try to set the color for that tab
|
||||
(with the color picker) to `#0000ff`, the tab's color is updated to reflect
|
||||
this new blue color. Regardless of which pane is focused, the tab will be
|
||||
blue.
|
||||
* **Scenario 5**: The user has set `"tabColor": "#ff0000"` in their profile
|
||||
("Profile A"), and `"tab.backgroundColor": "#00ff00"`in their theme. When they
|
||||
create tabs with "Profile A", the tabs will appear red. Other tabs (for
|
||||
profiles without `tabColor` set) will appear green, using the color from the
|
||||
theme.
|
||||
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
In general, this is going to look exactly like the colored tabs look now.
|
||||
|
||||

|
||||
|
||||
## Capabilities
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Accessibility</strong></td>
|
||||
<td>
|
||||
N/A
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Security</strong></td>
|
||||
<td>
|
||||
N/A
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Reliability</strong></td>
|
||||
<td>
|
||||
No expected change
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
This entire spec outlines how this feature is designed with a emphasis on future
|
||||
compatibility. As such, there are no expected regressions in the future when we
|
||||
do add support for themes.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Performance, Power, and Efficiency</strong></td>
|
||||
<td>
|
||||
No expected change
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Potential Issues
|
||||
|
||||
None expected.
|
||||
|
||||
## Footnotes
|
||||
|
||||
<a id="footnote-1"><a>[1]: When full theming support is added, themes will
|
||||
provide support for setting colors as one of a variety of values:
|
||||
|
||||
* An `#rrggbb` string
|
||||
* The system accent color
|
||||
* The current background color of the Terminal
|
||||
* A value from a given resource key from XAML
|
||||
|
||||
When support for these other types of "smart" colors is added, then the profile
|
||||
`tabColor` setting will also gracefully accept these values.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* It's not out of the realm of possibility that someone might want to color each
|
||||
_pane_'s color at runtime. In that case, the runtime color would be stored in
|
||||
the `Pane`, not the `Tab`.
|
||||
|
||||
|
||||
|
||||
<!-- Footnotes -->
|
||||
|
||||
[#1337]: https://github.com/microsoft/terminal/issues/1337
|
||||
[#3789]: https://github.com/microsoft/terminal/issues/3789
|
||||
[#3327]: https://github.com/microsoft/terminal/issues/3327
|
||||
[#5772]: https://github.com/microsoft/terminal/pull/5772
|
||||
|
Before Width: | Height: | Size: 816 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 34 KiB |
@@ -1,227 +0,0 @@
|
||||
---
|
||||
author: Leon Liang @leonMSFT
|
||||
created on: 2019-11-27
|
||||
last updated: 2020-06-16
|
||||
issue id: 1502
|
||||
---
|
||||
|
||||
# Advanced Tab Switcher
|
||||
|
||||
## Abstract
|
||||
|
||||
Currently the user is able to cycle through tabs on the tab bar. However, this horizontal cycling can be pretty inconvenient when the tab titles are long or when there are too many tabs on the tab bar. It could also get hard to see all your available tabs if the tab titles are long and your screen is small. In addition, there's a common use case to quickly switch between two tabs, e.g. when one tab is used as reference and the other is the actively worked-on tab. If the tabs are not right next to each other on the tab bar, it could be difficult to quickly swap between the two. Having the tabs displayed in Most Recently Used (MRU) order would help with this problem. It could also make the user experience better when there are a handful of tabs that are frequently used, but are nowhere near each other on the tab bar.
|
||||
|
||||
Having a tab switcher UI, like the ones in Visual Studio and Visual Studio Code, could help with the tab experience. Presenting the tabs vertically in their own little UI allows the user to see more of the tabs at once, compared to scanning the tab row horizontally and scrolling left/right to find the tab you want. The tab order in those tab switchers are also in MRU order by default.
|
||||
|
||||
To try to alleviate some of these user scenarios, we want to create a tab switcher similar to the ones found in VSCode and VS. This spec will cover the design of the switcher, and how a user would interact with the switcher. It would be primarily keyboard driven, and would give a pop-up display of a vertical list of tabs. The tab switcher would also be able to display the tabs in Most Recently Used (MRU) order.
|
||||
|
||||
## Inspiration
|
||||
|
||||
This was mainly inspired by the tab switcher that's found in Visual Studio Code and Visual Studio.
|
||||
|
||||
VS Code's tab switcher appears directly underneath the tab bar.
|
||||
|
||||

|
||||
|
||||
Visual Studio's tab switcher presents itself as a box in the middle of the editor.
|
||||
|
||||

|
||||
|
||||
In terms of navigating the switcher, both VSCode and Visual Studio behave very similarly. Both open with the press of <kbd>ctrl+tab</kbd> and dismiss on release of <kbd>ctrl</kbd>. They both also allow the user to select the tab with the mouse and with <kbd>enter</kbd>. <kbd>esc</kbd> and a mouse click outside of the switcher both dismiss the window as well.
|
||||
|
||||
I'm partial towards looking like VSCode's Tab Switcher - specifically because it seems like both their Command Palette and Tab Switcher use the same UI. You can observe this by first bringing up the command palette, then hitting the keybinding to bring up the tab switcher. You'll notice that they're both using the same centered drop-down from the tab row. In fact, hitting the Tab Switcher keybinding in VSCode while the Command Palette is open simply auto fills the search box with "edit active", signifying that the user wants to select one of the tabs to edit, effectively "swapping" to the tab that's highlighted.
|
||||
|
||||
Since Terminal now has a command palette, it would be amazing to reuse that UI and simply fill it with the names of a user's currently open tabs!
|
||||
|
||||
## Solution Design
|
||||
|
||||
To extend upon the command palette, we simply need to create and maintain two Vector<Commands>, where each command will simply dispatch a `SwitchToTab` `ShortcutAction`. One vector will have the commands in tab row order, and the other will be in MRU order. They'll both have to be maintained along with our existing vector of tabs.
|
||||
|
||||
These vectors of commands can then be set as the commands to pull from in the command palette, and as long as the tab titles are available in these commands, the command palette will be able to naturally filter through the tabs as a user types in its search bar. Just like the command palette, a user will be able to navigate through the list of tabs with the arrow keys and pointer interactions. As part of this implementation, I can supplement these actions with "tab switcher specific" navigation keybindings that would only work if the command palette is in tab switcher mode.
|
||||
|
||||
The `TabSwitcherControl` will use `TerminalPage`'s `ShortcutActionDispatch` to dispatch a `SwitchToTab` `ShortcutAction`. This will eventually cause `TerminalPage::_OnTabSelectionChanged` to be called. We can update the MRU in this function to be sure that changing tabs from the TabSwitcher, clicking on a tab, or nextTab/prevTab-ing will keep the MRU up-to-date. Adding or closing tabs are handled in `_OpenNewTab` and `_CloseFocusedTab`, which will need to be modified to update the command vectors.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
The Tab Switcher will reuse a lot of the XAML code that's used in the command palette. This means it'll show up as a drop-down from the horizontal center of the tab row. It'll appear as a single overlay over the whole Terminal window. There will also be a search box on top of the list of tabs. Here's a rough mockup of how the command palette/tab switcher looks like:
|
||||
|
||||

|
||||
|
||||
Each entry in the list will show the tab's titles and their assigned number for quick switching, and only one line will be highlighted to signify the tab that is currently selected. The top 9 tabs in the list are numbered for quick switching, and the rest of the tabs will simply have an empty space where a number would be.
|
||||
|
||||
The list would look (roughly) like this:
|
||||
```
|
||||
1 foo (highlighted)
|
||||
2 boo
|
||||
3 Windows
|
||||
4 /c/Users/booboo
|
||||
5 Git Moo
|
||||
6 shoo
|
||||
7 /c/
|
||||
8 /d/
|
||||
9 /e/
|
||||
/f/
|
||||
/g/
|
||||
/h/
|
||||
```
|
||||
|
||||
The highlighted line can move up or down, and if the user moves up while the highlighted line is already at the top of the list, the highlight will wrap around to the bottom of the list. Similarly, it will wrap to the top if the highlight is at the bottom of the list and the user moves down.
|
||||
|
||||
If there's more tabs than the UI can display, the list of tabs will scroll up/down as the user keeps iterating up/down. Even if some of the numbered tabs (the first 9 tabs) are not visible, the user can still press any number 1 through 9 to quick switch to that tab.
|
||||
|
||||
To give an example of what happens after scrolling past the end, imagine a user is starting from the state in the mock above. The user then iterates down past the end of the visible list four times. The below mock shows the result.
|
||||
|
||||
```
|
||||
5 Git Moo
|
||||
6 shoo
|
||||
7 /c/
|
||||
8 /d
|
||||
9 /e/
|
||||
/f/
|
||||
/g/
|
||||
/h/
|
||||
/i/
|
||||
/j/
|
||||
/k/
|
||||
/l/ (highlighted)
|
||||
```
|
||||
|
||||
The tabs designated by numbers 1 through 4 are no longer visible (but still quick-switchable), and the list now starts with "Git Moo", which is associated with number 5.
|
||||
|
||||
### Using the Switcher
|
||||
|
||||
#### Opening the Tab Switcher
|
||||
|
||||
The user can press a keybinding named `tabSwitcher` to bring up the command palette UI with a list of tab titles.
|
||||
The user can also bring up the command palette first, and type a "tab switcher" prefix like "@" into the search bar to switch into "tab switcher mode".
|
||||
The user will be able to change it to whatever they like.
|
||||
There will also be an optional `anchor` arg that may be provided to this keybinding.
|
||||
|
||||
#### Keeping it open
|
||||
|
||||
We use the term `anchor` to illustrate the idea that the UI stays visible as long as something is "anchoring" it down.
|
||||
|
||||
Here's an example of how to set the `anchor` key in the settings:
|
||||
```
|
||||
{"keys": ["ctrl+tab"], "command": {"action": "openTabSwitcher", "anchor": "ctrl" }}
|
||||
```
|
||||
|
||||
This user provided the `anchor` key arg, and set it to <kbd>ctrl</kbd>. So, the user would open the UI with <kbd>ctrl+tab</kbd>, and as long as the user is holding <kbd>ctrl</kbd> down, the UI won't dismiss. The moment the user releases <kbd>ctrl</kbd>, the UI dismisses. The `anchor` key needs to be one of the keys in the `openTabSwitcher` keybinding. If it isn't, we'll display a warning dialog in this case saying that the `anchor` key isn't actually part of the keybinding, and the user might run into some weird behavior.
|
||||
|
||||
If `openTabSwitcher` is not given an `anchor` key, the switcher will stay visible even after the release of the keybinding.
|
||||
|
||||
#### Switching through Tabs
|
||||
|
||||
The user will be able to navigate through the switcher with the following keybindings:
|
||||
|
||||
- Switching Down: <kbd>tab</kbd> or <kbd>downArrow</kbd>
|
||||
- Switching Up: <kbd>shift+tab</kbd> or <kbd>upArrow</kbd>
|
||||
|
||||
As the user is cycling through the tab list, the selected tab will be highlighted but the terminal won't actually switch focus to the selected tab. This also applies to pointer interaction. Hovering over an item with a mouse will highlight the item but not switch to the tab.
|
||||
|
||||
#### Closing the Switcher and Bringing a Tab into Focus
|
||||
|
||||
There are two _dismissal_ keybindings:
|
||||
|
||||
1. <kbd>enter</kbd> : brings the currently selected tab into focus and dismisses the UI.
|
||||
2. <kbd>esc</kbd> : dismisses the UI without changing tab focus.
|
||||
|
||||
The following are ways a user can dismiss the UI, _whether or not_ the `Anchor` key is provided to `openTabSwitcher`.
|
||||
|
||||
1. The user can press a number associated with a tab to instantly switch to the tab and dismiss the switcher.
|
||||
2. The user can click on a tab to instantly switch to the tab and dismiss the switcher.
|
||||
3. The user can click outside of the UI to dismiss the switcher without bringing the selected tab into focus.
|
||||
4. The user can press any of the dismissal keybindings.
|
||||
|
||||
If the `anchor` key is provided, then in addition to the above methods, the UI will dismiss upon the release of the `anchor` key.
|
||||
|
||||
Pressing the `openTabSwitcher` keychord again will not close the switcher, it'll do nothing.
|
||||
|
||||
### Most Recently Used Order
|
||||
|
||||
We'll provide a setting that will allow the list of tabs to be presented in either _in-order_ (how the tabs are ordered on the tab bar), or _Most Recently Used Order_ (MRU). MRU means that the tab that the terminal most recently visited will be on the top of the list, and the tab that the terminal has not visited for the longest time will be on the bottom.
|
||||
|
||||
There will be an argument for the `openTabSwitcher` action called `displayOrder`. This can be either `inOrder` or `mruOrder`. Making the setting an argument passed into `openTabSwitcher` would allow the user to have one keybinding to open an MRU Tab Switcher, and different one for the In-Order Tab Switcher. For example:
|
||||
```
|
||||
{"keys": ["ctrl+tab"], "command": {"action": "openTabSwitcher", "anchor":"ctrl", "displayOrder":"mruOrder"}}
|
||||
{"keys": ["ctrl+shift+p"], "command": {"action": "openTabSwitcher", "anchor":"ctrl", "displayOrder":"inOrder"}}
|
||||
```
|
||||
By default (when the arg isn't specified), `displayOrder` will be "mruOrder".
|
||||
|
||||
### Numbered Tabs
|
||||
|
||||
Similar to how the user can currently switch to a particular tab with a combination of keys such as <kbd>ctrl+shift+1</kbd>, we want to have the tab switcher provide a number to the first nine tabs (1-9) in the list for quick switching. If there are more than nine tabs in the list, then the rest of the tabs will not have a number assigned.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
- The tab switcher will be using WinUI, and so it'll be automatically linked to the UIA tree. This allows screen readers to find it, and so narrator will be able to navigate the switcher easily.
|
||||
- The UI is also fully keyboard-driven, with the option of using a mouse to interact with the UI.
|
||||
- When the tab switcher pops up, the focus immediately swaps to it.
|
||||
- For the sake of more contrast with the background, we could use a ThemeShadow to bring the UI closer to the user, making the focus clearer.
|
||||
|
||||
### Security
|
||||
|
||||
This shouldn't introduce any security issues.
|
||||
|
||||
### Reliability
|
||||
|
||||
How we're updating the MRU is something to watch out for since it triggers on a lot of tab interactions. However, I don't foresee the update taking long at all, and I can't imagine that users can create and delete tabs fast enough to matter.
|
||||
|
||||
### Compatibility
|
||||
|
||||
- The existing way of navigating horizontally through the tabs on the tab bar should not break.
|
||||
- These should also be separate keybindings from the keybindings associated with using the tab switcher.
|
||||
- When a user reorders their tabs on the tab bar, the MRU order remains unchanged. For example:
|
||||
- Tab Bar:`[cmd(focused), ps, wsl]` and MRU:`[cmd, ps, wsl]`
|
||||
- Reordered Tab Bar:`[wsl, cmd(focused), ps]` and MRU:`[cmd, ps, wsl]`
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
## Potential Issues
|
||||
|
||||
We'll need to be careful about how the UI is presented depending on different sizes of the terminal. We also should test how the UI looks as it's open and resizing is happening. Visual Studio's tab switcher is a fixed size, and is always in the middle. Even when the VS window is smaller than the tab switcher size, the tab switcher will show up larger than the VS window itself.
|
||||
|
||||

|
||||

|
||||
|
||||
Visual Studio Code only allows the user to shrink the window until it hits a minimum width and height. This minimum width and height gives its tab switcher enough space to show a meaningful amount of information.
|
||||
|
||||

|
||||
|
||||
Terminal can't really replicate Visual Studio's version of the tab switcher in this situation. The TabSwitcher needs to be contained within the Terminal. So, if the TabSwitcher is always centered and has a percentage padding from the borders of the Terminal, it'll shrink as Terminal shrinks. Since the Terminal also has a minimum width, the switcher should always have enough space to be usefully visible.
|
||||
|
||||
## Future considerations
|
||||
|
||||
### Pane Navigation
|
||||
|
||||
There was discussion in [#1502] that brought up the idea of pane navigation, inspired by tmux.
|
||||
|
||||

|
||||
|
||||
Tmux allows the user to navigate directly to a pane and even give a preview of the pane. This would be extremely useful since it would allow the user to see a tree of their open tabs and panes. Currently there's no way to see what panes are open in each tab, so if you're looking for a particular pane, you'd need to cycle through your tabs to find it. If something like pane profile names (not sure what information to present in the switcher for panes) were presented in the TabSwitcher, the user could see all the panes in one box.
|
||||
|
||||
To support pane navigation, the tab switcher can simply have another column to the right of the tab list to show a list of panes inside the selected tab. As the user iterates through the tab list, they can simply hit right to dig deeper into the tab's panes, and hit left to come back to the tab list. Each tab's list of panes will be MRU or in-order, depending on which `displayOrder` arg was provided to the `openTabSwitcher` keybinding.
|
||||
|
||||
Pane navigation is a clear next step to build on top of the tab switcher, but this spec will specifically deal with just tab navigation in order to keep the scope tight. The tab switcher implementation just needs to allow for pane navigation to be added in later.
|
||||
|
||||
### Tab Preview on Hover
|
||||
|
||||
With this feature, having a tab highlighted in the switcher would make the Terminal display that tab as if it switched to it. I believe currently there is no way to set focus to a tab in a "preview" mode. This is important because MRU updates whenever a tab is focused, but we don't want the MRU to update on a preview. Given that this feature is a "nice thing to have", I'll leave it for
|
||||
after the tab switcher has landed.
|
||||
|
||||
## Resources
|
||||
|
||||
Feature Request: An advanced tab switcher [#1502]
|
||||
Ctrl+Tab toggle between last two windows like Alt+Tab [#973]
|
||||
The Command Palette Thread [#2046]
|
||||
The Command Palette Spec [#5674]
|
||||
Feature Request: Search [#605]
|
||||
|
||||
<!-- Footnotes -->
|
||||
[#605]: https://github.com/microsoft/terminal/issues/605
|
||||
[#973]: https://github.com/microsoft/terminal/issues/973
|
||||
[#1502]: https://github.com/microsoft/terminal/issues/1502
|
||||
[#2046]: https://github.com/microsoft/terminal/issues/2046
|
||||
[#5674]: https://github.com/microsoft/terminal/pull/5674
|
||||
@@ -1,300 +0,0 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-5-13
|
||||
last updated: 2020-08-04
|
||||
issue id: 1571
|
||||
---
|
||||
|
||||
# New Tab Menu Customization
|
||||
|
||||
## Abstract
|
||||
|
||||
Many users have lots and _lots_ of profiles that they use. Some of these
|
||||
profiles the user might not use that frequently. When that happens, the new tab
|
||||
dropdown can become quite cluttered.
|
||||
|
||||
A common ask is for the ability to reorder and reorganize this dropdown. This
|
||||
spec provides a design for how the user might be able to specify the
|
||||
customization in their settings.
|
||||
|
||||
## Inspiration
|
||||
|
||||
Largely, this spec was inspired by discussion in
|
||||
[#1571](https://github.com/microsoft/terminal/issues/1571#issuecomment-519504048)
|
||||
and the _many_ linked threads.
|
||||
|
||||
## Solution Design
|
||||
|
||||
This design proposes adding a new setting `"newTabMenu"`. When unset, (the
|
||||
default), the new tab menu is populated with all the profiles, in the order they
|
||||
appear in the users settings file. When set, this enables the user to control
|
||||
the appearance of the new tab dropdown. Let's take a look at an example:
|
||||
|
||||
```json
|
||||
{
|
||||
"profiles":{ ... },
|
||||
"newTabMenu": [
|
||||
{ "type":"profile", "profile": "cmd" },
|
||||
{ "type":"profile", "profile": "Windows PowerShell" },
|
||||
{ "type":"separator" },
|
||||
{
|
||||
"type":"folder",
|
||||
"name": "ssh",
|
||||
"icon": "C:\\path\\to\\icon.png",
|
||||
"entries":[
|
||||
{ "type":"profile", "profile": "Host 1" },
|
||||
{ "type":"profile", "profile": "8.8.8.8" },
|
||||
{ "type":"profile", "profile": "Host 2" }
|
||||
]
|
||||
},
|
||||
{ "type":"separator" },
|
||||
{ "type":"profile", "profile": "Ubuntu-18.04" },
|
||||
{ "type":"profile", "profile": "Fedora" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If a user were to use this as their new tab menu, that they would get is a menu
|
||||
that looks like this:
|
||||
|
||||

|
||||
|
||||
_fig 1_: A _very rough_ mockup of what this feature might look like
|
||||
|
||||
There are five `type`s of objects in this menu:
|
||||
* `"type":"profile"`: This is a profile. Clicking on this entry will open a new
|
||||
tab, with that profile. The profile is identified with the `"profile"`
|
||||
parameter, which accepts either a profile `name` or GUID. The icon for this
|
||||
entry will be the profile's icon, and the text on the entry will be the
|
||||
profile's name.
|
||||
* `"type":"separator"`: This represents a XAML `MenuFlyoutSeparator`, enabling
|
||||
the user to visually space out entries.
|
||||
* `"type":"folder"`: This represents a nested menu of entries.
|
||||
- The `"name"` property provides a string of text to display for the group.
|
||||
- The `"icon"` property provides a path to a image to use as the icon. This
|
||||
property is optional.
|
||||
- The `"entries"` property specifies a list of menu entries that will appear
|
||||
nested under this entry. This can contain other `"type":"folder"` groups as
|
||||
well!
|
||||
* `"type":"action"`: This represents a menu entry that should execute a specific
|
||||
`ShortcutAction`.
|
||||
- the `id` property will specify the global action ID (see [#6899], [#7175])
|
||||
to identify the action to perform when the user selects the entry. Actions
|
||||
with invalid IDs will be ignored and omitted from the list.
|
||||
- The text for this entry will be the action's label (which is
|
||||
either provided as the `"name"` in the global list of actions, or the
|
||||
generated name if no `name` was provided)
|
||||
- The icon for this entry will similarly re-use the action's `icon`.
|
||||
* `"type":"remainingProfiles"`: This is a special type of entry that will be
|
||||
expanded to contain one `"type":"profile"` entry for every profile that was
|
||||
not already listed in the menu. This will allow users to add one entry for
|
||||
just "all the profiles they haven't manually added to the menu".
|
||||
- This type of entry can only be specified once - trying to add it to the menu
|
||||
twice will raise a warning, and ignore all but the first `remainingProfiles`
|
||||
entry.
|
||||
- This type of entry can also be set inside a `folder` entry, allowing users
|
||||
to highlight only a couple profiles in the top-level of the menu, but
|
||||
enabling all other profiles to also be accessible.
|
||||
- The "name" of these entries will simply be the name of the profile
|
||||
- The "icon" of these entries will simply be the profile's icon
|
||||
|
||||
The "default" new tab menu could be imagined as the following blob of json:
|
||||
|
||||
```json
|
||||
{
|
||||
"newTabMenu": [
|
||||
{ "type":"remainingProfiles" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Other considerations
|
||||
|
||||
Also considered during the investigation for this feature was re-using the list
|
||||
of profiles to expose the structure of the new tab menu. For example, doing
|
||||
something like:
|
||||
|
||||
```json
|
||||
"profiles": {
|
||||
"defaults": {},
|
||||
"list":
|
||||
[
|
||||
{ "name": "cmd" },
|
||||
{ "name": "powershell" },
|
||||
{ "type": "separator" },
|
||||
{
|
||||
"type": "folder" ,
|
||||
"profiles": [
|
||||
{ "name": "ubuntu" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This option was not pursued because we felt that it needlessly complicated the
|
||||
contents of the list of profiles objects. We'd rather have the `profiles` list
|
||||
exclusively contain `Profile` objects, and have other elements of the json
|
||||
_refer_ to those profiles. What if someone would like to have an action that
|
||||
opened a new tab with profile index 4, and then they set that action as entry 4
|
||||
in the profile's list? That would certainly be some sort of unexpected behavior.
|
||||
|
||||
Additionally, what if someone wants to have an entry that opens a tab with one
|
||||
pane with one profile in it, and another pane with different profile in it? Or
|
||||
what if they want the same profile to appear twice in the menu?
|
||||
|
||||
By overloading the structure of the `profiles` list, we're forcing all other
|
||||
consumers of the list of profiles to care about the structure of the elements of
|
||||
the list. These other consumers should only really care about the list of
|
||||
profiles, and not necessarily how they're structured in the new tab dropdown.
|
||||
Furthermore, it complicates the list of profiles, by adding actions intermixed
|
||||
with the profiles.
|
||||
|
||||
The design chosen in this spec more cleanly separates the responsibilities of
|
||||
the list of profiles and the contents of the new tab menu. This way, each object
|
||||
can be defined independent of the structure of the other.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
See the above _figure 1_.
|
||||
|
||||
The profile's `icon` will also appear as the icon on `profile` entries. If
|
||||
there's a keybinding bound to open a new tab with that profile, then that will
|
||||
also be added to the `MenuFlyoutItem` as the accelerator text, similar to the
|
||||
text we have nowadays.
|
||||
|
||||
Beneath the list of profiles will _always_ be the same "Settings", "Feedback"
|
||||
and "About" entries, separated by a `MenuFlyoutSeparator`. This is consistent
|
||||
with the UI as it exists with no customization. These entries cannot be removed
|
||||
with this feature, only the list of profiles customized.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
This menu will be added to the XAML tree in the same fashion as the current new
|
||||
tab flyout, so there should be no dramatic change here.
|
||||
|
||||
### Security
|
||||
|
||||
_(no change expected)_
|
||||
|
||||
### Reliability
|
||||
|
||||
_(no change expected)_
|
||||
|
||||
### Compatibility
|
||||
|
||||
_(no change expected)_
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
_(no change expected)_
|
||||
|
||||
## Potential Issues
|
||||
|
||||
Currently, the `openTab` and `splitPane` keybindings will accept a `index`
|
||||
parameter to say either:
|
||||
* "Create a new tab/pane with the N'th profile"
|
||||
* "Create a new tab/pane with the profile at index N in the new
|
||||
tab dropdown".
|
||||
|
||||
These two were previously synonymous, as the N'th profile was always the N'th in
|
||||
the dropdown. However, with this change, we'll be changing the meaning of that
|
||||
argument to mean explicitly the first option - "Open a tab/pane with the N'th
|
||||
profile".
|
||||
|
||||
A previous version of this spec considered changing the meaning of that
|
||||
parameter to mean "open the entry at index N", the second option. However, in
|
||||
[Command Palette, Addendum 1], we found that naming that command would become
|
||||
unnecessarily complex.
|
||||
|
||||
To cover that above scenario, we could consider adding an `index` parameter to
|
||||
the `openNewTabDropdown` action. If specified, that would open either the N'th
|
||||
action in the dropdown (ignoring separators), or open the dropdown with the n'th
|
||||
item selected.
|
||||
|
||||
The N'th entry in the menu won't always be a profile: it might be a folder with
|
||||
more options, or it might be an action (that might not be opening a new tab/pane
|
||||
at all).
|
||||
|
||||
Given all the above scenarios, `openNewTabDropdown` with an `"index":N`
|
||||
parameter will behave in the following ways. If the Nth top-level entry in the
|
||||
new tab menu is a:
|
||||
* `"type":"profile"`: perform the `newTab` or `splitPane` action with that profile.
|
||||
* `"type":"folder"`: Focus the first element in the sub menu, so the user could
|
||||
navigate it with the keyboard.
|
||||
* `"type":"separator"`: Ignore these when counting top-level entries.
|
||||
* `"type":"action"`: Perform the action.
|
||||
|
||||
So for example:
|
||||
|
||||
```
|
||||
New Tab Button ▽
|
||||
├─ Folder 1
|
||||
│ └─ Profile A
|
||||
│ └─ Action B
|
||||
├─ Separator
|
||||
├─ Folder 2
|
||||
│ └─ Profile C
|
||||
│ └─ Profile D
|
||||
├─ Action E
|
||||
└─ Profile F
|
||||
```
|
||||
|
||||
And assuming the user has bound:
|
||||
```json
|
||||
{
|
||||
"bindings":
|
||||
[
|
||||
{ "command": { "action": "openNewTabDropdown", "index": 0 }, "keys": "ctrl+shift+1" },
|
||||
{ "command": { "action": "openNewTabDropdown", "index": 1 }, "keys": "ctrl+shift+2" },
|
||||
{ "command": { "action": "openNewTabDropdown", "index": 2 }, "keys": "ctrl+shift+3" },
|
||||
{ "command": { "action": "openNewTabDropdown", "index": 3 }, "keys": "ctrl+shift+4" },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
* <kbd>ctrl+shift+1</kbd> focuses "Profile A", but the user needs to press
|
||||
enter/space to creates a new tab/split
|
||||
* <kbd>ctrl+shift+2</kbd> focuses "Profile C", but the user needs to press
|
||||
enter/space to creates a new tab/split
|
||||
* <kbd>ctrl+shift+3</kbd> performs Action E
|
||||
* <kbd>ctrl+shift+4</kbd> Creates a new tab/split with Profile F
|
||||
|
||||
## Future considerations
|
||||
|
||||
* The user could set a `"name"`/`"text"`, or `"icon"` property to these menu
|
||||
items manually, to override the value from the profile or action. These
|
||||
settings would be totally optional, but it's not unreasonable that someone
|
||||
might want this.
|
||||
* We may want to consider adding a default icon for all folders or actions in
|
||||
the menu. For example, a folder (like 📁) for `folder` entries, or something
|
||||
like ⚡ for actions. We'll leave these unset by default, and evaluate setting
|
||||
these icons by default in the future.
|
||||
* Something considered during review was a way to specify "All my WSL profiles".
|
||||
Maybe the user wants to have all their profiles generated by the WSL Distro
|
||||
Generator appear in a "WSL" folder. This would likely require a more elaborate
|
||||
filtering syntax, to be able to select only profiles where a certain property
|
||||
has a specific value. Consider the user who has multiple "SSH
|
||||
me@\<some host\>.com" profiles, and they want all their "SSH\*" profiles to
|
||||
appear in an "SSH" folder. This feels out-of-scope for this spec.
|
||||
* A similar structure could potentially also be used for customizing the context
|
||||
menu within a control, or the context menu for the tab. (see [#3337])
|
||||
- In both of those cases, it might be important to somehow refer to the
|
||||
context of the current tab or control in the json. Think for example about
|
||||
"Close tab" or "Close other tabs" - currently, those work by _knowing_ which
|
||||
tab the "action" is specified for, not by actually using a `closeTab` action.
|
||||
In the future, they might need to be implemented as something like
|
||||
- Close Tab: `{ "action": "closeTab", "index": "${selectedTab.index}" }`
|
||||
- Close Other Tabs: `{ "action": "closeTabs", "otherThan": "${selectedTab.index}" }`
|
||||
- Close Tabs to the Right: `{ "action": "closeTabs", "after": "${selectedTab.index}" }`
|
||||
|
||||
|
||||
<!-- Footnotes -->
|
||||
[#2046]: https://github.com/microsoft/terminal/issues/2046
|
||||
[Command Palette, Addendum 1]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md
|
||||
|
||||
[#3337]: https://github.com/microsoft/terminal/issues/3337
|
||||
[#6899]: https://github.com/microsoft/terminal/issues/6899
|
||||
[#7175]: https://github.com/microsoft/terminal/issues/7175
|
||||
|
Before Width: | Height: | Size: 46 KiB |