Compare commits

..

4 Commits

Author SHA1 Message Date
Dustin L. Howett
3dca4263d5 Migrate spelling-0.0.21 changes from main 2020-07-13 16:59:33 -07:00
Dustin L. Howett
462006ba51 Migrate spelling-0.0.19 changes from main 2020-07-13 16:59:33 -07:00
Michael Niksa
5b6d2e0708 change interface to not use Clusters. Just build a string and a clustermap vector. 2020-07-14 15:40:59 -07:00
Michael Niksa
ffe0e024b3 Go at the buffer a bit more directly and get some perf. 2020-07-14 15:40:55 -07:00
428 changed files with 10221 additions and 22344 deletions

View File

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

View File

@@ -48,6 +48,36 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## telnetpp
**Source**: https://github.com/KazDragon/telnetpp
### License
```
The MIT License (MIT)
Copyright (c) 2015-2017 Matthew Chaplain a.k.a KazDragon
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.
```
## chromium/base/numerics
**Source**: https://github.com/chromium/chromium/tree/master/base/numerics

View File

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

View File

@@ -160,11 +160,15 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalConnection", "src\cascadia\TerminalConnection\TerminalConnection.vcxproj", "{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalCore", "src\cascadia\TerminalCore\lib\TerminalCore-lib.vcxproj", "{CA5CAD1A-ABCD-429C-B551-8562EC954746}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalControl", "src\cascadia\TerminalControl\TerminalControl.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {CA5CAD1A-ABCD-429C-B551-8562EC954746}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
{1CF55140-EF6A-4736-A403-957E4F7430BB} = {1CF55140-EF6A-4736-A403-957E4F7430BB}
{48D21369-3D7B-4431-9967-24E81292CF63} = {48D21369-3D7B-4431-9967-24E81292CF63}
EndProjectSection
@@ -174,16 +178,20 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminal", "src\casc
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {CA5CAD1A-ABCD-429C-B551-8562EC954746}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia\TerminalApp\dll\TerminalApp.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia\TerminalApp\TerminalApp.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746}
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalSettings", "src\cascadia\TerminalSettings\TerminalSettings.vcxproj", "{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminalShellExt", "src\cascadia\ShellExtension\WindowsTerminalShellExt.vcxproj", "{F2ED628A-DB22-446F-A081-4CC845B51A2B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_TerminalCore", "src\cascadia\UnitTests_TerminalCore\UnitTests.vcxproj", "{2C2BEEF4-9333-4D05-B12A-1905CBF112F9}"
@@ -227,9 +235,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_TerminalApp", "sr
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\cascadia\TerminalApp\TerminalAppLib.vcxproj", "{CA5CAD1A-9A12-429C-B551-8562EC954746}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj", "{CA5CAD1A-9A12-429C-B551-8562EC954746}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_TerminalApp", "src\cascadia\LocalTests_TerminalApp\TerminalApp.LocalTests.vcxproj", "{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}"
@@ -1380,6 +1389,35 @@ Global
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x64.Build.0 = Release|x64
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x86.ActiveCfg = Release|Win32
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x86.Build.0 = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|Any CPU.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|DotNet_x64Test.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|DotNet_x86Test.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x64.ActiveCfg = AuditMode|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x64.Build.0 = AuditMode|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x86.ActiveCfg = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|Any CPU.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.Build.0 = Debug|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x64Test.ActiveCfg = Debug|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x64Test.Build.0 = Debug|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x86Test.Build.0 = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x64.ActiveCfg = Debug|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x64.Build.0 = Debug|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x86.ActiveCfg = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x86.Build.0 = Debug|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|Any CPU.ActiveCfg = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM64.ActiveCfg = Release|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM64.Build.0 = Release|ARM64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x64Test.ActiveCfg = Release|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x64Test.Build.0 = Release|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x86Test.Build.0 = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x64.ActiveCfg = Release|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x64.Build.0 = Release|x64
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.ActiveCfg = Release|Win32
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.Build.0 = Release|Win32
{F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|Any CPU.ActiveCfg = Release|Win32
{F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
@@ -2035,6 +2073,7 @@ Global
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202}
{F2ED628A-DB22-446F-A081-4CC845B51A2B} = {59840756-302F-44DF-AA47-441A9D673202}
{2C2BEEF4-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}

View File

@@ -60,22 +60,6 @@ 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
@@ -179,7 +163,6 @@ If you would like to ask a question that you feel doesn't warrant an issue (yet)
* Mike Griese, Developer: [@zadjii](https://twitter.com/zadjii)
* Carlos Zamora, Developer: [@cazamor_msft](https://twitter.com/cazamor_msft)
* Leon Liang, Developer: [@leonmsft](https://twitter.com/leonmsft)
* Pankaj Bhojwani, Developer
## Developer Guidance

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,6 @@ trigger:
branches:
include:
- master
- feature/*
paths:
exclude:
- doc/*
@@ -14,7 +13,6 @@ pr:
branches:
include:
- master
- feature/*
paths:
exclude:
- doc/*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>4</VersionMinor>
<VersionMinor>2</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

BIN
dep/llvm/clang-format.exe Normal file

Binary file not shown.

View File

@@ -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:
![image](https://user-images.githubusercontent.com/18356694/89556758-79d27e80-d7d7-11ea-84e2-10710e09ef4a.png)
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

View File

@@ -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.4.0-prerelease.200506002
- `$NewVersionNumber` is the version number you want to migrate to, e.g. 2.5.0-prerelease.200812002
- `$OldVersionNumber` is the version number currently used, e.g. 2.5.0-prerelease.200609001
- `$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.4.0-prerelease.200506002/2.5.0-prerelease.200812002/g'`
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.5.0-prerelease.200609001/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:

View File

@@ -1 +1,210 @@
⚠ This document has moved to [the Customize Settings section of the Windows Terminal documentation](https://docs.microsoft.com/windows/terminal/customize-settings/global-settings).
# Settings.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 | `false` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. |
| `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"` |
| `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> |
| `wordDelimiters` | Optional | String | <code>&nbsp;&#x2f;&#x5c;&#x28;&#x29;&#x22;&#x27;&#x2d;&#x3a;&#x2c;&#x2e;&#x3b;&#x3c;&#x3e;&#x7e;&#x21;&#x40;&#x23;&#x24;&#x25;&#x5e;&#x26;&#x2a;&#x7c;&#x2b;&#x3d;&#x5b;&#x5d;&#x7b;&#x7d;&#x7e;&#x3f;│</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 |
| -------- | --------- | ---- | ------- | ----------- |
| `guid` | _Required_ | String | | Unique identifier of the profile. Written in registry format: `"{00000000-0000-0000-0000-000000000000}"`. |
| `name` | _Required_ | String | | Name of the profile. Displays in the dropdown menu. <br>Additionally, this value will be used 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. This "title" behavior can be overridden by using `tabTitle`. |
| `acrylicOpacity` | Optional | Number | `0.5` | When `useAcrylic` is set to `true`, it sets the transparency of the window for the profile. Accepts floating point values from 0-1. |
| `antialiasingMode` | Optional | String | `"grayscale"` | Controls how text is antialiased in the renderer. Possible values are "grayscale", "cleartype" and "aliased". Note that changing this setting will require starting a new terminal instance. |
| `background` | Optional | String | | Sets the background color of the profile. Overrides `background` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `backgroundImage` | Optional | String | | Sets the file location of the Image to draw over the window background. |
| `backgroundImageAlignment` | Optional | String | `center` | Sets how the background image aligns to the boundaries of the window. Possible values: `"center"`, `"left"`, `"top"`, `"right"`, `"bottom"`, `"topLeft"`, `"topRight"`, `"bottomLeft"`, `"bottomRight"` |
| `backgroundImageOpacity` | Optional | Number | `1.0` | Sets the transparency of the background image. Accepts floating point values from 0-1. |
| `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`. |
| `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"` ( &#x2583; ), `"bar"` ( &#x2503; ), `"underscore"` ( &#x2581; ), `"filledBox"` ( &#x2588; ), `"emptyBox"` ( &#x25AF; ) |
| `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. |
| `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"` |
| `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. |
| `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 |
| -------- | ---- | ----------- | ----------- |
| `name` | _Required_ | String | Name of the color scheme. |
| `foreground` | _Required_ | String | Sets the foreground color of the color scheme. |
| `background` | _Required_ | String | Sets the background color of the color scheme. |
| `selectionBackground` | Optional | String | Sets the selection background color of the color scheme. |
| `cursorColor` | Optional | String | Sets the cursor color of the color scheme. |
| `black` | _Required_ | String | Sets the color used as ANSI black. |
| `blue` | _Required_ | String | Sets the color used as ANSI blue. |
| `brightBlack` | _Required_ | String | Sets the color used as ANSI bright black. |
| `brightBlue` | _Required_ | String | Sets the color used as ANSI bright blue. |
| `brightCyan` | _Required_ | String | Sets the color used as ANSI bright cyan. |
| `brightGreen` | _Required_ | String | Sets the color used as ANSI bright green. |
| `brightPurple` | _Required_ | String | Sets the color used as ANSI bright purple. |
| `brightRed` | _Required_ | String | Sets the color used as ANSI bright red. |
| `brightWhite` | _Required_ | String | Sets the color used as ANSI bright white. |
| `brightYellow` | _Required_ | String | Sets the color used as ANSI bright yellow. |
| `cyan` | _Required_ | String | Sets the color used as ANSI cyan. |
| `green` | _Required_ | String | Sets the color used as ANSI green. |
| `purple` | _Required_ | String | Sets the color used as ANSI purple. |
| `red` | _Required_ | String | Sets the color used as ANSI red. |
| `white` | _Required_ | String | Sets the color used as ANSI white. |
| `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. |
| `action` | Optional | String | Adds additional functionality to certain commands. |
### Implemented Commands and Actions
Commands listed below are per the implementation in [`src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp`](https://github.com/microsoft/terminal/blob/master/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp).
Keybindings can be structured in the following manners:
For commands without arguments:
<br>
`{ "command": "commandName", "keys": [ "modifiers+key" ] }`
For commands with arguments:
<br>
`{ "command": { "action": "commandName", "argument": "value" }, "keys": ["modifiers+key"] }`
| 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. | `singleLine` | boolean | When `true`, the copied content will be copied as a single line. When `false`, newlines persist from the selected text. |
| `duplicateTab` | Make a copy and open the current tab. | | | |
| `find` | Open the search dialog box. | | | |
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| `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. | | | |
| `openNewTabDropdown` | Open the dropdown menu. | | | |
| `openSettings` | Open the settings file. | | | |
| `paste` | Insert the content that was copied onto the clipboard. | | | |
| `prevTab` | Open the tab to the left of the current one. | | | |
| `resetFontSize` | Reset the text size to the default value. | | | |
| `resizePane` | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
| `scrollDown` | Move the screen down. | | | |
| `scrollUp` | Move the screen up. | | | |
| `scrollUpPage` | Move the screen up a whole page. | | | |
| `scrollDownPage` | Move the screen down a whole page. | | | |
| `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. |
| `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. | | | |
### Accepted Modifiers and Keys
#### Modifiers
`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` |
| 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:
| URI Scheme | Corresponding Physical Path | Use / description |
| --- | --- | ---|
| `ms-appdata:///Local/` | `%localappdata%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\` | Per-machine files |
| `ms-appdata:///Roaming/` | `%localappdata%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\RoamingState\` | Common files |
> ⚠ 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.
You can refer to you own icons if you wish, e.g.:
```json
"icon" : "C:\\Users\\richturn\\OneDrive\\WindowsTerminal\\icon-ubuntu-32.png",
```
> 👉 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`.
For example:
```json
"backgroundImage": "C:\\Users\\richturn\\OneDrive\\WindowsTerminal\\bg-ubuntu-256.png",
"backgroundImageAlignment": "bottomRight",
"backgroundImageOpacity": 0.1,
"backgroundImageStretchMode": "none"
```
> 👉 Tip: You can easily roam your collection of images and icons across all your machines by storing your icons and images in OneDrive (as shown above).
With these settings, your Terminal's Ubuntu profile would look similar to this:
![Custom icon and background image](../images/custom-icon-and-background-image.jpg)

View File

@@ -4,9 +4,9 @@
"title": "Microsoft's Windows Terminal Settings Profile Schema",
"definitions": {
"KeyChordSegment": {
"pattern": "^(?<modifier>(ctrl|alt|shift)(?:\\+(ctrl|alt|shift)(?<!\\2))?(?:\\+(ctrl|alt|shift)(?<!\\2|\\3))?\\+)?(?<key>[^\\s+]|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": "#",
@@ -34,42 +34,35 @@
"ShortcutActionName": {
"enum": [
"adjustFontSize",
"closeOtherTabs",
"closePane",
"closeTab",
"closeTabsAfter",
"closeWindow",
"commandPalette",
"copy",
"duplicateTab",
"find",
"moveFocus",
"newTab",
"nextTab",
"openNewTabDropdown",
"openSettings",
"openTabColorPicker",
"paste",
"prevTab",
"renameTab",
"resetFontSize",
"resizePane",
"scrollDown",
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"sendInput",
"setColorScheme",
"setTabColor",
"splitPane",
"switchToTab",
"tabSearch",
"toggleAlwaysOnTop",
"toggleFocusMode",
"toggleFullscreen",
"togglePaneZoom",
"toggleAlwaysOnTop",
"toggleRetroEffect",
"wt",
"find",
"setTabColor",
"openTabColorPicker",
"renameTab",
"commandPalette",
"unbound"
],
"type": "string"
@@ -91,40 +84,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": {
@@ -190,18 +149,6 @@
"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"
}
]
}
}
}
@@ -270,28 +217,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": [
@@ -355,78 +280,6 @@
}
]
},
"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"
}
}
}
],
"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."
}
}
}
]
},
"Keybinding": {
"additionalProperties": false,
"properties": {
@@ -440,14 +293,9 @@
{ "$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" }
]
},
@@ -494,8 +342,8 @@
},
"copyFormatting": {
"default": true,
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
"$ref": "#/definitions/CopyFormat"
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard.",
"type": "boolean"
},
"largePasteWarning": {
"default": true,
@@ -544,16 +392,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"
],
@@ -618,11 +460,6 @@
"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": false,
"description": "When set to \"true\", the \"nextTab\" and \"prevTab\" commands will use the tab switcher UI.",
"type": "boolean"
}
},
"required": [
@@ -822,15 +659,8 @@
"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"
}
]
"pattern": "^-?[0-9]+(\\.[0-9]+)?( *, *-?[0-9]+(\\.[0-9]+)?|( *, *-?[0-9]+(\\.[0-9]+)?){3})?$",
"type": "string"
},
"scrollbarState": {
"default": "visible",

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@@ -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.
![preview](profile-tabColor-000.gif)
## 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

View File

@@ -1,112 +0,0 @@
---
author: Kayla Cinnamon @cinnamon-msft
created on: 2020-07-13
last updated: 2020-08-11
issue id: #1564
---
# Settings UI Design
## Abstract
This design document describes how each page of the settings UI will be laid out along with design mockups to display how the UI will appear. The mock ups are for appearance purposes and some layouts and naming may be different in this doc. This doc should be considered the final say.
## UI Design
### Overall navigation with Startup page
This is the list of the top-level navigation items that will appear on the left nav bar:
- General
- Startup
- Interaction
- Rendering
- Appearance
- Global
- Color schemes
- Themes*
- Profiles
- Defaults
- Enumerate profiles
- Add new
- Keyboard
- Mouse*
- Command Palette*
- Marketplace*
\* Themes, mouse, command palette, and marketplace will be added once they're implemented.
![Overall navigation](./navigation-2.png)
### Profile appearance page
This page requires special design because it includes the TerminalControl window to preview appearance changes. This preview window will appear on the following pages:
- Appearance - Color Schemes
- Profiles - Appearance
![Appearance page](./appearance.png)
### Keyboard page
The keyboard page will list the enabled key bindings and provide a way for users to add and remove them.
![Keyboard page](./keyboard.png)
When someone hovers over one of the items in the table, the Edit and Delete buttons will appear. Below is what the modal looks like if they were to click Edit on a command that does not have any arguments/actions. In the future, we would want this text box to be able to listen for key combinations. This would add a "listen" button to the UI.
![Keyboard page modal](./keyboard-modal.png)
If the command they select has additional arguments/actions, the modal will dynamically size as arguments/actions are added.
![Keyboard page modal add new arguments](./keyboard-modal-add.png)
![Keyboard page modal arguments](./keyboard-modal-args.png)
## Settings layout
Below is the list of all settings on their respective pages in the settings UI. The title row aligns with the navigation view on the left of the UI. Bolded headers in those columns align with top nav on the page.
| General - Startup | General - Interaction | General - Rendering | Appearance - Global | Appearance - Color Schemes | Profiles - Global | Profiles - Enumerate profiles | Profiles - Add new |
| ---------------- | --------------------- | ------------------- | ------------------- | -------------------------- | ----------------- | ----------------------------- | ------------------ |
| Default profile (dropdown) | Copy after selection is made (checkbox) | Software rendering (checkbox) | Theme (radio) | Name (text box) | **General** | **General** | **General** | **General** |
| Launch on startup (checkbox) | Copy formatting (checkbox) | Screen redrawing (checkbox) | Show/Hide the title bar (checkbox) | Cursor color (color picker) | Command line (text box) | Scrollbar visibility (radio) | Scrollbar visibility (radio) |
| Launch size (radio) | Word delimiters (text box) | | Show terminal title in title bar (checkbox) | Selection background (color picker) | Starting directory (browse button) | Command line (browse button) | Command line (browse button) |
| Launch position (text box) | Window resize behavior (checkbox) | | Always show tabs (checkbox) | Background (color picker) | Icon (browse button) | Starting directory (browse button) | Starting directory (browse button) |
| Columns on first launch (number picker) | | | Tab width mode (radio) | Foreground (color picker) | Tab title (text box) | Name (text box) | Name (text box) |
| Rows on first launch (number picker) | | | Hide close all tabs popup (checkbox) | Black (color picker) | Scrollbar visibility (radio) | Icon (browse button) | Icon (browse button) |
| Automatically create new profiles when new shells are installed (checkbox) | | | | Blue (color picker) | **Appearance** | Tab title (text box) | Tab title (text box) |
| | | | | Cyan (color picker) | Font face (text box) | **Appearance** | **Appearance** |
| | | | | Green (color picker) | Font size (number picker) | Retro terminal effects (checkbox) | Retro terminal effects (checkbox) |
| | | | | Purple (color picker) | Font weight (dropdown) | Font face (text box) | Font face (text box) |
| | | | | Red (color picker) | Padding (text box) | Font size (number picker) | Font size (number picker) |
| | | | | White (color picker) | Cursor shape (radio) | Font weight (dropdown) | Font weight (dropdown) |
| | | | | Yellow (color picker) | Cursor color (color picker) | Padding (text box) | Padding (text box) |
| | | | | Bright black (color picker) | Cursor height (number picker) | Cursor shape (radio) | Cursor shape (radio) |
| | | | | Bright blue (color picker) | Color scheme (dropdown) | Cursor color (color picker) | Cursor color (color picker) |
| | | | | Bright cyan (color picker) | Foreground color (color picker) | Cursor height (number picker) | Cursor height (number picker) |
| | | | | Bright green (color picker) | Background color (color picker) | Color scheme (dropdown) | Color scheme (dropdown) |
| | | | | Bright purple (color picker) | Selection background color (color picker) | Foreground color (color picker) | Foreground color (color picker) |
| | | | | Bright red (color picker) | Enable acrylic (checkbox) | Background color (color picker) | Background color (color picker) |
| | | | | Bright white (color picker) | Acrylic opacity (number picker) | Selection background color (color picker) | Selection background color (color picker) |
| | | | | Bright yellow (color picker) | Background image (browse button) | Enable acrylic (checkbox) | Enable acrylic (checkbox) |
| | | | | | Background image stretch mode (radio) | Acrylic opacity (number picker) | Acrylic opacity (number picker) |
| | | | | | Background image alignment (dropdown) | Background image (browse button) | Background image (browse button) |
| | | | | | Background image opacity (number picker) | Background image stretch mode (radio) | Background image stretch mode (radio) |
| | | | | | Retro terminal effects (checkbox) | Background image alignment (dropdown) | Background image alignment (dropdown) |
| | | | | | **Advanced** | Background image opacity (number picker) | Background image opacity (number picker) |
| | | | | | Hide profile from dropdown (checkbox) | **Advanced** | **Advanced** |
| | | | | | Suppress title changes (checkbox) | GUID (text box) | GUID (text box) |
| | | | | | Antialiasing text (radio) | Hide profile from dropdown (checkbox) | Hide profile from dropdown (checkbox) |
| | | | | | AltGr aliasing (checkbox) | Suppress title changes (checkbox) | Suppress title changes (checkbox) |
| | | | | | Scroll to input when typing (checkbox) | Antialiasing text (radio) | Antialiasing text (radio) |
| | | | | | History size (number picker) | AltGr aliasing (checkbox) | AltGr aliasing (checkbox) |
| | | | | | How the profile closes (radio) | Scroll to input when typing (checkbox) | Scroll to input when typing (checkbox) |
| | | | | | | History size (number picker) | History size (number picker) |
| | | | | | | How the profile closes (radio) | How the profile closes (radio) |
## Potential Issues
## Future considerations
## Resources

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -1,122 +0,0 @@
---
author: Kayla Cinnamon @cinnamon-msft
created on: 2020-06-29
last updated: 2020-08-10
issue id: #1564
---
# Settings UI Implementation
## Abstract
This spec describes the basic functionality of the settings UI, including disabling it, the navigation items, launch methods, and editing of settings. The specific layout of each page will defined in later design reviews.
## Inspiration
We have been wanting a settings UI since the dawn of Terminal time, so we need to define how it will interact with the application and how users should expect to interact with it.
## Solution Design
The settings UI will be the default experience. We will provide users an option to skip the settings UI and edit the raw JSON file.
### Ability to disable displaying the settings UI
Some users don't want a UI for the settings. We can update the `openSettings` key binding with a `settingsUI` option.
If people still like the UI but want to access the JSON file, we can provide an "Open the JSON file" button at the bottom of the navigation menu.
### Launch method: launch in a new tab
Clicking the settings button in the dropdown menu will open the settings UI in a new tab. This helps us take steps toward supporting non-terminal content in a tab. Users will be able to see their visual changes by using the preview window inside the settings UI on relevant pages.
#### We also considered: launch in a new window
Clicking the settings button in the dropdown menu will open the settings UI in a new window. This allows the user to edit their settings and see the Terminal live update with their changes.
In the Windows taskbar, the icon will appear as if Terminal has multiple windows open.
### Editing and saving settings: implement a save button
Users will only see their settings changes take place once they click "Save". Clicking "Save" will write to the settings.json file. This aligns with the functionality that exists today by editing the settings.json file in a text editor and saving it.
We will also be adding a TerminalControl inside the settings UI to preview what the changes will look like before actually saving them to the settings.json file.
#### We also considered: automatically save settings
As users edit fields in the settings UI, they are automatically saved and written to the JSON file. This allows the user to see their settings changes taking place in real time.
## UI/UX Design
Layout of all of the settings per page can be found in the [design doc](./design.md).
### Top-level navigation: more descriptive navigation
The navigation menu is broken up into more digestible sections. This aligns more closely to other terminals. The following are the proposed navigation items:
- General
- Startup
- Interaction
- Rendering
- Appearance
- Global
- Color schemes
- Themes*
- Profiles
- Defaults
- Enumerate profiles
- Add new
- Keyboard
- Mouse*
- Command Palette*
- Marketplace*
\* Themes, mouse, command palette, and marketplace will be added once they're implemented.
![Settings UI navigation 2](./navigation-2.png)
#### We also considered: align with JSON
The settings UI could have top-level navigation that aligns with the overall structure of the settings.json file. The following are the proposed navigation items:
- Globals
- Profiles
- Color schemes
- Bindings
For Bindings, it would have key bindings, mouse bindings, and command palette inside it.
![Settings UI navigation 1](./navigation.png)
## Capabilities
### Accessibility
This will have to undergo full accessibility testing because it is a new UI element. All items inside the settings UI should be accessible by a screen reader and the keyboard. Additionally, all of the settings UI will have to be localized.
### Security
This does not impact security.
### Reliability
This will not improve reliability.
### Compatibility
This will change the default experience to open the UI, rather than the JSON file in a text editor. This behavior can be reverted with the setting listed [above](#ability-to-disable-displaying-the-settings-ui).
### Performance, Power, and Efficiency
This does not affect performance, power, nor efficiency.
## Potential Issues
## Future considerations
- We will have to have design reviews for all of the content pages.
- The `hidden` property will need special consideration. Ideally, all profiles will appear in the settings regardless if `hidden` is set to `true`.
- We should have undo functionality. In a text editor, you can type `Ctrl+Z` however the settings UI is a bit more complex.
- Once we have a marketplace for themes and extensions, this should be added to the top-level navigation.
- As we add more features, the top-level navigation is subject to change in favor of improved usability.
## Resources

View File

@@ -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](Menu-Customization-000.png)
_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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,228 +0,0 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-07-13
last updated: 2020-07-22
issue id: 6899
---
# Action IDs
## Abstract
This document is intended to serve as an addition to the [Command Palette Spec],
as well as the [New Tab Menu Customization Spec].
As we come to rely more on actions being a mechanism by which the user defines
"do something in the Terminal", we'll want to make it even easier for users to
re-use the actions that they've already defined, as to reduce duplicated json as
much as possible. This spec proposes a mechanism by which actions could be
uniquely identifiable, so that the user could refer to bindings in other
contexts without needing to replicate an entire json blob.
## Solution Design
This spec was largely inspired by the following diagram from @DHowett:
![figure 1](data-mockup.png)
The goal is to introduce an `id` parameter by which actions could be uniquely
refered to. If we'd ever like to use an action outside the list of `actions`, we
can simply refer to the action's ID, allowing the user to only define the action
_once_.
We'll start by renaming `bindings` to `actions`. `bindings` was suggested as a
rename for `keybindings` in [#6532], as a way to make the name more generic.
Discussion with the team lead to the understanding that the name `actions` would
be even better, as a way of making the meaning of the "list of actions" more
obvious.
When we're parsing `actions`, we'll make three passes:
* The first pass will scan the list for objects with an `id` property. We'll
attempt to parse those entries into `ActionAndArgs` which we'll store in the
global `id->ActionAndArgs` map. If any entry doesn't have an `id` set, we'll
skip it in this phase. If an entry doesn't have a `command` set, we'll ignore
it in this pass.
* The second pass will scan for _keybindings_. Any entries with `keys` set will
create a `KeyChord->ActionAndArgs` entry in the keybindings map. If the entry
has an `id` set, then we'll simply re-use the action we've already parsed for
the `id`, from the action map. If there isn't an `id`, then we'll parse the
action manually at this time. Entries without a `keys` set will be ignored in
this pass.
* The final pass will be to generate _commands_. Similar to the keybindings
pass, we'll attempt to lookup actions for entries with an `id` set. If there
isn't an `id`, then we'll parse the action manually at this time. We'll then
get the name for the entry, either from the `name` property if it's set, or
the action's `GenerateName` method.
For a visual representation, let's assume the user has the following in their
`actions`:
![figure 2](data-mockup-actions.png)
We'll first parse the `actions` to generate the mapping of `id`->`Actions`:
![figure 3](data-mockup-actions-and-ids.png)
Then, we'll parse the `actions` to generate the mapping of keys to actions, with
some actions already being defined in the map of `id`->`Actions`:
![figure 4](data-mockup-actions-and-ids-and-keys.png)
When layering `actions`, if a later settings file contains an action with the
same `id`, it will replace the current value. In this way, users can redefine
actions, or remove default ones (with something like `{ "id":
"Terminal.OpenTab", "command":null }`
We'd maintain a large list of default actions, each with unique `id`s set. These
are all given `id`'s with a `Terminal.` prefix, to easily identify them as
built-in, default actions. Not all of these actions will be given keys, but they
will all be given `id`s.
> 👉 NOTE: The IDs for the default actions will need to be manually created, not
> autogenerated. These `id`s are not strings displayed in the user interface, so
> localization is not a concern.
As we add additional menus to the Terminal, like the customization for the new
tab dropdown, or the tab context menu, or the `TermControl` context menu, they
could all refer to these actions by `id`, rather than duplicating the same json.
### Existing Scenarios
Keybindings will still be stored as a `keys->Action` mapping, so the user will
still be able to override default keybindings exactly the same as before.
Similarly, commands in the Command Palette will continue using their existing
`name->Action` mapping they're currently using. For a binding like
```json
{ "keys": "ctrl+alt+x", "id": "Terminal.OpenDefaultSettings" },
```
* We'll bind whatever action is defined as `Terminal.OpenDefaultSettings` to
<kbd>ctrl+alt+x</kbd>.
* We'll use whatever action is defined as `Terminal.OpenDefaultSettings` to
generate a name for the command palette.
### Future Context Menus
In [New Tab Menu Customization Spec], we discuss allowing the user to bind
actions to the new tab menu. In that spec, they can do so with something like
the following:
```json
{
"newTabMenu": [
{ "type":"action", "command": { "action": "adjustFontSize", "delta": 1 }, }
{ "type":"action", "command": { "action": "adjustFontSize", "delta": -1 }, }
{ "type":"action", "command": "resetFontSize", }
{ "type":"profile", "profile": "cmd" },
{ "type":"profile", "profile": "Windows PowerShell" },
{ "type":"separator" },
{
"type":"folder",
"name": "Settings...",
"icon": "C:\\path\\to\\icon.png",
"entries":[
{ "type":"action", "command": "openSettings" },
{ "type":"action", "command": { "action": "openSettings", "target": "defaultsFile" } },
]
}
]
}
```
In this example, the user has also exposed the "Increase font size", "Decrease
font size", and "Reset font size" actions, as well as the settings files in a
submenu. With this proposal, the above could instead be re-written as:
```json
{
"newTabMenu": [
{ "type":"action", "id": "Terminal.IncreaseFontSize" },
{ "type":"action", "id": "Terminal.DecreaseFontSize" },
{ "type":"action", "id": "Terminal.ResetFontSize" },
{ "type":"profile", "profile": "cmd" },
{ "type":"profile", "profile": "Windows PowerShell" },
{ "type":"separator" },
{
"type":"folder",
"name": "Settings...",
"icon": "C:\\path\\to\\icon.png",
"entries":[
{ "type":"action", "id": "Terminal.OpenDefaultSettings" },
{ "type":"action", "id": "Terminal.OpenSettings" },
]
}
]
}
```
In this example, the actions are looked up from the global map using the `id`
provided, enabling the user to re-use their existing definitions. If the user
re-defined the `Terminal.IncreaseFontSize` action to mean something else, then
the action in the new tab menu will also be automatically updated.
Furthermore, when additional menus are added (such as the tab context menu, or
the `TermControl` context menu), these could also leverage a similar syntax to
the above to allow re-use of the `id` parameter.
Discussion with the team also suggested that users shouldn't be able to define
actions in these menus _at all_. The actions should exclusively be defined in
`actions`, and other menus should only be able to refer to these actions by
`id`.
## UI/UX Design
There's not a whole lot of UI for this feature specifically. This is largely
behind-the-scenes refactoring of how actions can be defined.
## Capabilities
### Accessibility
_(not applicable)_
### Security
_(no change expected)_
### Reliability
_(no change expected)_
### Compatibility
_(no change expected)_
### Performance, Power, and Efficiency
_(no change expected)_
## Potential Issues
This won't necessarily play well with iterable commands in the Command Palette,
but that's okay. For iterable commands, users will still need to define the
actions manually.
## Future considerations
* See the following issues for other places where this might be useful:
- [#1912] - Context Menu for Tabs
* See also [#5524], [#5025], [#5633]
- [#3337] - Right-click menu inside TerminalControl (w/ Copy & Paste?)
* See also [#5633] and [#5025], both those actions seem reasonable in either
the tab context menu or the control context menu.
<!-- Footnotes -->
[Command Palette Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Command%20Palette.md
[New Tab Menu Customization Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md
[#1571]: https://github.com/microsoft/terminal/issues/1571
[#1912]: https://github.com/microsoft/terminal/issues/1912
[#3337]: https://github.com/microsoft/terminal/issues/3337
[#5025]: https://github.com/microsoft/terminal/issues/5025
[#5524]: https://github.com/microsoft/terminal/issues/5524
[#5633]: https://github.com/microsoft/terminal/issues/5633
[#6532]: https://github.com/microsoft/terminal/issues/6532
[#6899]: https://github.com/microsoft/terminal/issues/6899

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,7 +1,13 @@
# Adding profiles for third-party tools
This doc will hopefully provide a useful guide for adding profiles for common third-party tools to your
[settings.json](https://docs.microsoft.com/en-us/windows/terminal/customize-settings/profile-settings) file.
This doc will hopefully provide a useful guide for adding profiles for common
third-party tools to your
[settings.json](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md)
file.
All of these profiles are provided _without_ their `guid` set. If you'd like to
set any of these profiles as your _default_ profile, you'll need to make sure to
[generate a unique guid](https://www.guidgenerator.com/) for them manually.
## Anaconda
@@ -9,10 +15,10 @@ Assuming that you've installed Anaconda into `%USERPROFILE%\Anaconda3`:
```json
{
"commandline": "cmd.exe /k \"%USERPROFILE%\\Anaconda3\\Scripts\\activate.bat %USERPROFILE%\\Anaconda3\"",
"icon": "%USERPROFILE%\\Anaconda3\\Menu\\anaconda-navigator.ico",
"name": "Anaconda3",
"startingDirectory": "%USERPROFILE%"
"commandline" : "cmd.exe /k \"%USERPROFILE%\\Anaconda3\\Scripts\\activate.bat %USERPROFILE%\\Anaconda3\"",
"icon" : "%USERPROFILE%/Anaconda3/Menu/anaconda-navigator.ico",
"name" : "Anaconda3",
"startingDirectory" : "%USERPROFILE%"
}
```
@@ -22,23 +28,23 @@ Assuming that you've installed cmder into `%CMDER_ROOT%`:
```json
{
"commandline": "cmd.exe /k \"%CMDER_ROOT%\\vendor\\init.bat\"",
"name": "cmder",
"icon": "%CMDER_ROOT%\\icons\\cmder.ico",
"startingDirectory": "%USERPROFILE%"
"commandline" : "cmd.exe /k \"%CMDER_ROOT%\\vendor\\init.bat\"",
"name" : "cmder",
"icon" : "%CMDER_ROOT%/icons/cmder.ico",
"startingDirectory" : "%USERPROFILE%"
}
```
## Cygwin
Assuming that you've installed Cygwin into `C:\Cygwin`:
Assuming that you've installed Cygwin into `C:/Cygwin`:
```json
{
"name": "Cygwin",
"commandline": "C:\\Cygwin\\bin\\bash --login -i",
"icon": "C:\\Cygwin\\Cygwin.ico",
"startingDirectory": "C:\\Cygwin\\bin"
"name" : "Cygwin",
"commandline" : "C:/Cygwin/bin/bash --login -i",
"icon" : "C:/Cygwin/Cygwin.ico",
"startingDirectory" : "C:/Cygwin/bin"
}
```
@@ -52,49 +58,49 @@ Assuming that you've installed Far into `c:\Program Files\Far Manager`:
```json
{
"name": "Far",
"commandline": "\"c:\\program files\\far manager\\far.exe\"",
"startingDirectory": "%USERPROFILE%",
"useAcrylic": false
"name" : "Far",
"commandline" : "\"c:\\program files\\far manager\\far.exe\"",
"startingDirectory" : "%USERPROFILE%",
"useAcrylic" : false
},
```
## Git Bash
Assuming that you've installed Git Bash into `C:\\Program Files\\Git`:
Assuming that you've installed Git Bash into `C:/Program Files/Git`:
```json
{
"name": "Git Bash",
"commandline": "C:\\Program Files\\Git\\bin\\bash.exe -li",
"icon": "C:\\Program Files\\Git\\mingw64\\share\\git\\git-for-windows.ico",
"startingDirectory": "%USERPROFILE%"
"name" : "Git Bash",
"commandline" : "C:/Program Files/Git/bin/bash.exe -li",
"icon" : "C:/Program Files/Git/mingw64/share/git/git-for-windows.ico",
"startingDirectory" : "%USERPROFILE%"
}
````
## Git Bash (WOW64)
Assuming that you've installed Git Bash into `C:\\Program Files (x86)\\Git`:
Assuming that you've installed Git Bash into `C:/Program Files (x86)/Git`:
```json
{
"name": "Git Bash",
"commandline": "%ProgramFiles(x86)%\\Git\\bin\\bash.exe -li",
"icon": "%ProgramFiles(x86)%\\Git\\mingw32\\share\\git\\git-for-windows.ico",
"startingDirectory": "%USERPROFILE%"
"name" : "Git Bash",
"commandline" : "%ProgramFiles(x86)%/Git/bin/bash.exe -li",
"icon" : "%ProgramFiles(x86)%/Git/mingw32/share/git/git-for-windows.ico",
"startingDirectory" : "%USERPROFILE%"
}
```
## MSYS2
Assuming that you've installed MSYS2 into `C:\\msys64`:
Assuming that you've installed MSYS2 into `C:/msys64`:
```json
{
"name": "MSYS2",
"commandline": "C:\\msys64\\msys2_shell.cmd -defterm -no-start -mingw64",
"icon": "C:\\msys64\\msys2.ico",
"startingDirectory": "C:\\msys64\\home\\user"
"name" : "MSYS2",
"commandline" : "C:/msys64/msys2_shell.cmd -defterm -no-start -mingw64",
"icon": "C:/msys64/msys2.ico",
"startingDirectory" : "C:/msys64/home/user"
}
````
@@ -104,9 +110,9 @@ Assuming that you've installed VS 2019 Professional:
```json
{
"name": "Developer Command Prompt for VS 2019",
"commandline": "cmd.exe /k \"C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/Tools/VsDevCmd.bat\"",
"startingDirectory": "%USERPROFILE%"
"name" : "Developer Command Prompt for VS 2019",
"commandline" : "cmd.exe /k \"C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/Tools/VsDevCmd.bat\"",
"startingDirectory" : "%USERPROFILE%"
}
```

View File

@@ -1 +1,201 @@
⚠ This document has moved to [Using command-line arguments for Windows Terminal](https://docs.microsoft.com/windows/terminal/command-line-arguments).
---
author: Mike Griese @zadjii-msft
created on: 2020-01-16
last updated: 2020-01-17
---
# Using the `wt.exe` Commandline
As of [#4023], the Windows Terminal now supports accepting arguments on the
commandline, to enable launching the Terminal in a non-default configuration.
This document serves as a reference for all the parameters you can currently
pass, and gives some examples of how to use the `wt` commandline.
> NOTE: If you're running the Terminal built straight from the repo, you'll need
> to use `wtd.exe` and `wtd` instead of `wt.exe` and `wt`.
1. [Commandline Reference](#Reference)
1. [Commandline Examples](#Examples)
## Reference
### Options
#### `--help,-h,-?,/?,`
Display the help message.
## Subcommands
#### `new-tab`
`new-tab [terminal_parameters]`
Opens a new tab with the given customizations. On its _first_ invocation, also
opens a new window. Subsequent `new-tab` commands will all open new tabs in the
same window. <sup>[[1](#footnote-1)]</sup>
**Parameters**:
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `split-pane`
`split-pane [-H,--horizontal|-V,--vertical] [terminal_parameters]`
Creates a new pane in the currently focused tab by splitting the given pane
vertically or horizontally. <sup>[[1](#footnote-1)]</sup>
**Parameters**:
* `-H,--horizontal`, `-V,--vertical`: Used to indicate which direction to split
the pane. `-V` is "vertically" (think `[|]`), and `-H` is "horizontally"
(think `[-]`). If omitted, defaults to "auto", which splits the current pane
in whatever the larger dimension is. If both `-H` and `-V` are provided,
defaults to vertical.
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `focus-tab`
`focus-tab [--target,-t tab-index]|[--next,-n]|[--previous,-p]`
Moves focus to a given tab.
**Parameters**:
* `--target,-t tab-index`: moves focus to the tab at index `tab-index`. If
omitted, defaults to `0` (the first tab). Will display an error if combined
with either of `--next` or `--previous`.
* `-n,--next`: Move focus to the next tab. Will display an error if combined
with either of `--previous` or `--target`.
* `-p,--previous`: Move focus to the previous tab. Will display an error if
combined with either of `--next` or `--target`.
#### `[terminal_parameters]`
Some of the preceding commands are used to create a new terminal instance.
These commands are listed above as accepting `[terminal_parameters]` as a
parameter. For these commands, `[terminal_parameters]` can be any of the
following:
`[--profile,-p profile-name] [--startingDirectory,-d starting-directory] [commandline]`
* `--profile,-p profile-name`: Use the given profile to open the new tab/pane,
where `profile-name` is the `name` or `guid` of a profile. If `profile-name`
does not match _any_ profiles, uses the default.
* `--startingDirectory,-d starting-directory`: Overrides the value of
`startingDirectory` of the specified profile, to start in `starting-directory`
instead.
* `commandline`: A commandline to replace the default commandline of the
selected profile. If the user wants to use a `;` in this commandline, it
should be escaped as `\;`.
### Notes
* <span id="footnote-1"></span> [1]: If you try to run a `wt` commandline while running in a Windows Terminal window, the commandline will _always_ create a new window by default. Being able to run `wt` commandlines in the _current_ window is planned in the future - for more information, refer to [#4472].
## Examples
### Open Windows Terminal in the current directory
```powershell
wt -d .
```
This will launch a new Windows Terminal window in the current working directory.
It will use your default profile, but instead of using the `startingDirectory`
property from that it will use the current path. This is especially useful for
launching the Windows Terminal in a directory you currently have open in an
`explorer.exe` window.
### Opening with multiple panes
If you want to open with multiple panes in the same tab all at once, you can use
the `split-pane` command to create new panes.
Consider the following commandline:
```powershell
wt ; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe
```
This creates a new Windows Terminal window with one tab, and 3 panes:
* `wt`: Creates the new tab with the default profile
* `split-pane -p "Windows PowerShell"`: This will create a new pane, split from
the parent with the default profile. This pane will open with the "Windows
PowerShell" profile
* `split-pane -H wsl.exe`: This will create a third pane, split _horizontally_
from the "Windows PowerShell" pane. It will be running the default profile,
and will use `wsl.exe` as the commandline (instead of the default profile's
`commandline`).
### Using multiple commands from PowerShell
The Windows Terminal uses the semicolon character `;` as a delimiter for
separating subcommands in the `wt` commandline. Unfortunately, `powershell` also
uses `;` as a command separator. To work around this you can use the following
tricks to help run multiple wt sub commands from powershell. In all the
following examples, we'll be creating a new Terminal window with three panes -
one running `cmd`, one with `powershell`, and a last one running `wsl`.
In each of the following examples, we're using the `Start-Process` command to
run `wt`. For more information on why we're using `Start-Process`, see ["Using
`start`"](#using-start) below.
#### Single quoted parameters (if you aren't calculating anything):
In this example, we'll wrap all the parameters to `wt` in single quotes (`'`)
```PowerShell
start wt 'new-tab "cmd"; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe'
```
#### Escaped quotes (if you need variables):
If you'd like to pass a value contained in a variable to the `wt` commandline,
instead use the following syntax:
```PowerShell
$ThirdPane = "wsl.exe"
start wt "new-tab cmd; split-pane -p `"Windows PowerShell`" ; split-pane -H $ThirdPane"
```
Note the usage of `` ` `` to escape the double-quotes (`"`) around "Windows
Powershell" in the `-p` parameter to the `split-pane` sub-command.
#### Using `start`
All the above examples explicitly used `start` to launch the Terminal.
In the following examples, we're going to not use `start` to run the
commandline. Instead, we'll try two other methods of escaping the commandline:
* Only escaping the semicolons so that `powershell` will ignore them and pass
them straight to `wt`.
* Using `--%`, so powershell will treat the rest of the commandline as arguments
to the application.
```PowerShell
wt new-tab "cmd" `; split-pane -p "Windows PowerShell" `; split-pane -H wsl.exe
```
```Powershell
wt --% new-tab cmd ; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe
```
In both these examples, the newly created Windows Terminal window will create
the window by correctly parsing all the provided commandline arguments.
However, these methods are _not_ recommended currently, as Powershell will wait
for the newly-created Terminal window to be closed before returning control to
Powershell. By default, Powershell will always wait for Windows Store
applications (like the Windows Terminal) to close before returning to the
prompt. Note that this is different than the behavior of `cmd`, which will return
to the prompt immediately. See
[Powershell/PowerShell#9970](https://github.com/PowerShell/PowerShell/issues/9970)
for more details on this bug.
[#4023]: https://github.com/microsoft/terminal/pull/4023
[#4472]: https://github.com/microsoft/terminal/issues/4472

View File

@@ -1 +1,483 @@
⚠ This document has moved to [the Customize Settings section of the Windows Terminal documentation](https://docs.microsoft.com/windows/terminal/customize-settings/global-settings).
# Editing Windows Terminal JSON Settings
One way (currently the only way) to configure Windows Terminal is by editing the
`settings.json` settings file. At the time of writing you can open the settings
file in your default editor by selecting `Settings` from the WT pull down menu.
The settings are stored in the file `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json`.
As of [#2515](https://github.com/microsoft/terminal/pull/2515), the settings are
split into _two_ files: a hardcoded `defaults.json`, and `settings.json`, which
contains the user settings. Users should only be concerned with the contents of
the `settings.json`, which contains their customizations. The `defaults.json`
file is only provided as a reference of what the default settings are. For more
details on how these two files work, see [Settings
Layering](#settings-layering). To view the default settings file, click on the
"Settings" button while holding the <kbd>Alt</kbd> key.
Details of specific settings can be found [here](../cascadia/SettingsSchema.md).
A general introduction is provided below.
The settings are grouped under four headings:
1. Global: Settings that apply to the whole application e.g. Default profile, initial size etc.
2. Key Bindings: Actually a sub field of the global settings, but worth discussing separately
3. Profiles: A group of settings to be applied to a tab when it is opened using that profile. E.g. shell to use, cursor shape etc.
4. Schemes: Sets of colors for background, text etc. that can be used by profiles
## Global Settings
These settings define startup defaults, and application-wide settings that might
not affect a particular terminal instance.
* Theme
* Title Bar options
* Initial size
* Default profile used when the Windows Terminal is started
Example settings include
```json
{
"defaultProfile" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
"initialCols" : 120,
"initialRows" : 50,
"theme" : "system",
"keybindings" : []
...
}
```
These global properties should exist in the root json object.
## Key Bindings
This is an array of key chords and shortcuts to invoke various commands.
Each command can have more than one key binding.
> 👉 **Note**: Key bindings is a subfield of the global settings and
> key bindings apply to all profiles in the same manner.
For example, here's a sample of the default keybindings:
```json
{
"keybindings":
[
{ "command": "closePane", "keys": ["ctrl+shift+w"] },
{ "command": "copy", "keys": ["ctrl+shift+c"] },
{ "command": "newTab", "keys": ["ctrl+shift+t"] },
// etc.
]
}
```
You can also use a single key chord string as the value of `"keys"`.
It will be treated as a chord of length one.
This will allow you to simplify the above snippet as follows:
```json
{
"keybindings":
[
{ "command": "closePane", "keys": "ctrl+shift+w" },
{ "command": "copy", "keys": "ctrl+shift+c" },
{ "command": "newTab", "keys": "ctrl+shift+t" },
// etc.
]
}
```
A list of default key bindings is available [here](https://github.com/microsoft/terminal/blob/master/src/cascadia/TerminalApp/defaults.json#L204).
### Unbinding keys
If you ever come across a key binding that you're unhappy with, it's possible to
easily change the keybindings. For example, vim uses <kbd>Ctrl+^</kbd> as a
binding for "switch to previous buffer", which conflicts with the Terminal's
default keybinding for "open a new tab with the sixth profile". If you'd like to
unbind that keybinding, and allow the keystroke to fall through to vim, you can
add the following to your keybindings:
```json
{
"command" : null, "keys" : ["ctrl+shift+6"]
},
```
This will _unbind_ <kbd>Ctrl+Shift+6</kbd>, allowing vim to use the keystroke
instead of the terminal.
### Binding multiple keys
You can have multiple key chords bound to the same action. To do this, simply
add multiple bindings for the same action. For example:
```json
"keybindings" :
[
{ "command": "copy", "keys": "ctrl+shift+c" },
{ "command": "copy", "keys": "ctrl+c" },
{ "command": "copy", "keys": "enter" }
]
```
In this snippet, all three of <kbd>ctrl+shift+c</kbd>, <kbd>ctrl+c</kbd> and <kbd>enter</kbd> are bound to `copy`.
## Profiles
A profile contains the settings applied when a new WT tab is opened. Each
profile is identified by a GUID and contains a number of other fields.
> 👉 **Note**: The `guid` property is the unique identifier for a profile. If
> multiple profiles all have the same `guid` value, you may see unexpected
> behavior.
* Which command to execute on startup - this can include arguments.
* Starting directory
* Which color scheme to use (see Schemes below)
* Font face and size
* Various settings to control appearance. E.g. Opacity, icon, cursor appearance, display name etc.
* Other behavioral settings. E.g. Close on exit, snap on input, .....
Example settings include
```json
"closeOnExit" : true,
"colorScheme" : "Campbell",
"commandline" : "wsl.exe -d Debian",
"cursorColor" : "#FFFFFF",
"cursorShape" : "bar",
"fontFace" : "Hack",
"fontSize" : 9,
"guid" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
"name" : "Debian",
"startingDirectory" : "%USERPROFILE%\\wslhome"
....
```
> 👉 **Note**: To use backslashes in any path field, you'll need to escape them following JSON escaping rules (like shown above). As an alternative, you can use forward slashes ("%USERPROFILE%/wslhome").
The profile GUID is used to reference the default profile in the global settings.
The values for background image stretch mode are documented [here](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.media.stretch).
### Hiding a profile
If you want to remove a profile from the list of profiles in the new tab
dropdown, but keep the profile around in your `settings.json` file, you can add
the property `"hidden": true` to the profile's json. This can also be used to
remove the default `cmd` and PowerShell profiles, if the user does not wish to
see them.
## Color Schemes
Each scheme defines the color values to be used for various terminal escape sequences.
Each schema is identified by the name field. Examples include
```json
"name" : "Campbell",
"background" : "#0C0C0C",
"black" : "#0C0C0C",
"blue" : "#0037DA",
"foreground" : "#F2F2F2",
"green" : "#13A10E",
"red" : "#C50F1F",
"white" : "#CCCCCC",
"yellow" : "#C19C00"
...
```
The schema name can then be referenced in one or more profiles.
## Settings layering
The runtime settings are actually constructed from _three_ sources:
* The default settings, which are hardcoded into the application, and available
in `defaults.json`. This includes the default keybindings, color schemes, and
profiles for both Windows PowerShell and Command Prompt (`cmd.exe`).
* Dynamic Profiles, which are generated at runtime. These include Powershell
Core, the Azure Cloud Shell connector, and profiles for and WSL distros.
* The user settings from `settings.json`.
Settings from each of these sources are "layered" upon the settings from
previous sources. In this manner, the user settings in `settings.json` can
contain _only the changes from the default settings_. For example, if a user
would like to only change the color scheme of the default `cmd` profile to
"Solarized Dark", you could change your cmd profile to the following:
```js
{
// Make changes here to the cmd.exe profile
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"colorScheme": "Solarized Dark"
}
```
Here, we know we're changing the `cmd` profile, because the `guid`
`"{0caa0dad-35be-5f56-a8ff-afceeeaa6101}"` is `cmd`'s unique GUID. Any profiles
with that GUID will all be treated as the same object. Any changes in that
profile will overwrite those from the defaults.
Similarly, you can overwrite settings from a color scheme by defining a color
scheme in `settings.json` with the same name as a default color scheme.
If you'd like to unbind a keystroke that's bound to an action in the default
keybindings, you can set the `"command"` to `"unbound"` or `null`. This will
allow the keystroke to fallthrough to the commandline application instead of
performing the default action.
### Dynamic Profiles
When dynamic profiles are created at runtime, they'll be added to the
`settings.json` file. You can identify these profiles by the presence of a
`"source"` property. These profiles are tied to their source - if you uninstall
a linux distro, then the profile will remain in your `settings.json` file, but
the profile will be hidden.
The Windows Terminal uses the `guid` property of these dynamically-generated
profiles to uniquely identify them. If you try to change the `guid` of a
dynamically-generated profile, the Terminal will automatically recreate a new
entry for that profile.
If you'd like to disable a particular dynamic profile source, you can add that
`source` to the global `"disabledProfileSources"` array. For example, if you'd
like to hide all the WSL profiles, you could add the following setting:
```json
"disabledProfileSources": ["Windows.Terminal.WSL"],
...
```
> 👉 **NOTE**: On launch, if a dynamic profile generator is enabled, it will
> always add new profiles it detects to your list of profiles. If you delete a
> dynamically generated profile from your list of profiles, it will just get
> re-added the next time the Terminal is launched! To remove a dynamic profile
> from your list of profiles, make sure to set `"hidden": true` in the profile.
### Default settings
In [#2325](https://github.com/microsoft/terminal/issues/2325), we introduced the
concept of "Default Profile Settings". These are settings that will apply to all
of your profiles by default. Profiles can still override these settings
individually. With default profile settings, you can easily make changes to all
your profiles at once. For example, given the following settings:
```json
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"profiles":
[
{
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe",
"fontFace": "Cascadia Code",
"fontSize": 14
},
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "cmd",
"commandline": "cmd.exe",
"fontFace": "Cascadia Code",
"fontSize": 14
},
{
"commandline" : "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
"name" : "cmder",
"startingDirectory" : "%USERPROFILE%",
"fontFace": "Cascadia Code",
"fontSize": 14
}
],
```
All three of these profiles are using "Cascadia Code" as their `"fontFace"`, and
14 as their `fontSize`. With default profile settings, you can easily set these
properties for all your profiles, like so:
```json
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"profiles": {
"defaults":
{
"fontFace": "Cascadia Code",
"fontSize": 14
},
"list": [
{
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe"
},
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "cmd",
"commandline": "cmd.exe"
},
{
"commandline" : "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
"name" : "cmder",
"startingDirectory" : "%USERPROFILE%"
}
]
},
```
Note that the `profiles` property has changed in this example from a _list_ of
profiles, to an _object_ with two properties:
* a `list` that contains the list of all the profiles
* the new `defaults` object, which contains all the settings that should apply to
every profile.
What if I wanted a profile to have a different value for a property other than
the default? Simply set the property in the profile's entry to override the
value from `defaults`. Let's say you want the `cmd` profile to have _"Consolas"_
as the font, but the rest of your profiles to still have _"Cascadia Code"_. You
could achieve that with the following:
```json
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"profiles": {
"defaults":
{
"fontFace": "Cascadia Code",
"fontSize": 14
},
"list": [
{
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe"
},
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "cmd",
"commandline": "cmd.exe",
"fontFace": "Consolas"
},
{
"commandline" : "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
"name" : "cmder",
"startingDirectory" : "%USERPROFILE%"
}
]
},
```
In the above settings, the `"fontFace"` in the `cmd.exe` profile overrides the
`"fontFace"` from the `defaults`.
## Configuration Examples
### Add a custom background to the WSL Debian terminal profile
1. Download the [Debian JPG logo](https://www.debian.org/logos/openlogo-100.jpg)
2. Put the image in the
`$env:LocalAppData\Packages\Microsoft.WindowsTerminal_<randomString>\LocalState\`
directory (same directory as your `settings.json` file).
__NOTE__: You can put the image anywhere you like, the above suggestion happens to be convenient.
3. Open your WT json properties file.
4. Under the Debian Linux profile, add the following fields:
```json
"backgroundImage": "ms-appdata:///Local/openlogo-100.jpg",
"backgroundImageOpacity": 1,
"backgroundImageStretchMode" : "none",
"backgroundImageAlignment" : "topRight",
```
5. Make sure that `useAcrylic` is `false`.
6. Save the file.
7. Jump over to WT and verify your changes.
Notes:
1. You will need to experiment with different color settings
and schemes to make your terminal text visible on top of your image
2. If you store the image in the UWP directory (the same directory as your settings.json file),
then you should use the URI style path name given in the above example.
More information about UWP URI schemes [here](https://docs.microsoft.com/en-us/windows/uwp/app-resources/uri-schemes).
3. Instead of using a UWP URI you can use a:
1. URL such as
`http://open.esa.int/files/2017/03/Mayer_and_Bond_craters_seen_by_SMART-1-350x346.jpg`
2. Local file location such as `C:\Users\Public\Pictures\openlogo.jpg`
### Adding Copy and Paste Keybindings
As of [#1093](https://github.com/microsoft/terminal/pull/1093) (first available
in Windows Terminal v0.3), the Windows Terminal now supports copy and paste
keyboard shortcuts. However, if you installed and ran the terminal before that,
you won't automatically get the new keybindings added to your settings. If you'd
like to add shortcuts for copy and paste, you can do so by inserting the
following objects into your `globals.keybindings` array:
```json
{ "command": "copy", "keys": ["ctrl+shift+c"] },
{ "command": "paste", "keys": ["ctrl+shift+v"] }
```
> 👉 **Note**: you can also add a keybinding for the `copy` command with the argument `"trimWhitespace": true`. This removes newlines as the text is copied to your clipboard.
This will add copy and paste on <kbd>ctrl+shift+c</kbd>
and <kbd>ctrl+shift+v</kbd> respectively.
You can set the keybindings to whatever you'd like. If you prefer
<kbd>ctrl+c</kbd> to copy, then set the `keys` to `"ctrl+c"`.
You can even set multiple keybindings for a single action if you'd like. For example:
```json
{
"command" : "paste",
"keys" :
[
"ctrl+shift+v"
]
},
{
"command" : "paste",
"keys" :
[
"shift+insert"
]
}
```
will bind both <kbd>ctrl+shift+v</kbd> and
<kbd>shift+Insert</kbd> to `paste`.
> 👉 **Note**: If you set your copy keybinding to `"ctrl+c"`, you'll only be able to send
an interrupt to the commandline application using <kbd>Ctrl+C</kbd> when there's
no text selection. Additionally, if you set `paste` to `"ctrl+v"`, commandline
applications won't be able to read a ctrl+v from the input. For these reasons,
we suggest `"ctrl+shift+c"` and `"ctrl+shift+v"`
### Setting the `startingDirectory` of WSL Profiles to `~`
By default, the `startingDirectory` of a profile is `%USERPROFILE%`
(`C:\Users\<YourUsername>`). This is a Windows path. However, for WSL, you might
want to use the WSL home path instead. At the time of writing (26decf1 / Nov.
1st, 2019), `startingDirectory` only accepts a Windows-style path, so setting it
to start within the WSL distro can be a little tricky.
Fortunately, with Windows 1903, the filesystems of WSL distros can easily be
addressed using the `\\wsl$\` prefix. For any WSL distro whose name is
`DistroName`, you can use `\\wsl$\DistroName` as a Windows path that points to
the root of that distro's filesystem.
For example, the following works as a profile to launch the "Ubuntu-18.04"
distro in it's home path:
```json
{
"name": "Ubuntu-18.04",
"commandline" : "wsl -d Ubuntu-18.04",
"startingDirectory" : "//wsl$/Ubuntu-18.04/home/<Your Ubuntu Username>",
}
```

View File

@@ -1 +1,91 @@
⚠ Our user-facing documentation has moved to the [Windows Terminal documentation page](https://docs.microsoft.com/windows/terminal/).
# Windows Terminal User Documentation
NOTE: At the time of writing Windows Terminal is still under active development and many things will
change. If you notice an error in the docs, please raise an issue. Or better yet, please file a PR with an appropriate update!
## Installing Windows Terminal
### From Source Code
To compile Windows Terminal yourself using the source code, follow the instructions in the [README](/README.md#developer-guidance).
### From the Microsoft Store
1. Make sure you have upgraded to the current Windows 10 release (at least build `1903`). To determine your build number, see [winver](https://docs.microsoft.com/en-us/windows/client-management/windows-version-search).
2. Open the Windows Terminal listing in the [Microsoft Store](https://aka.ms/install-terminal).
3. Review the minimum system requirements to confirm you can successfully install Windows Terminal.
4. Click `Get` to begin the installation process.
## Starting Windows Terminal
1. Locate the _Windows Terminal_ app in your Start menu.
2. Click _Windows Terminal_ to launch the app. If you need administrative privileges, right-click the entry and click `Run as administrator`. Alternatively, you can highlight the app and press `Ctrl`+`Shift`+`Enter`.
NOTE: The default shell is PowerShell; you can change this using the _Running a Different Shell_ procedure.
### Command line options
Windows Terminal has implemented a rich set of command-line options in part as response to issue [#607](https://github.com/microsoft/terminal/issues/607). See [UsingCommandlineArguments.md](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingCommandlineArguments.md) for details.
## Multiple Tabs
Additional shells can be started by hitting the `+` button from the tab bar -- a new instance of the
default shell is displayed (default shortcut: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>1</kbd>).
## Running a Different Shell
Note: This section assumes you already have _Windows Subsystem for Linux_ (WSL) installed. For more information, see [the installation guide](https://docs.microsoft.com/en-us/windows/wsl/install-win10).
Windows Terminal uses PowerShell as its default shell. You can also use Windows Terminal to launch other shells, such as `cmd.exe` or WSL's `bash`:
1. In the tab bar, click the `⌵` button to view the available shells.
2. Choose your shell from the dropdown list. The new shell session will open in a new tab.
To customize the shell list, see the _Configuring Windows Terminal_ section below.
## Starting a new PowerShell tab with admin privilege
There is no current plan to support this feature for security reasons. See issue [#632](https://github.com/microsoft/terminal/issues/632)
## Selecting and Copying Text in Windows Terminal
As in ConHost, a selection can be made by left-clicking and dragging the mouse across the terminal. This is a line selection by default, meaning that the selection will wrap to the end of the line and the beginning of the next one. You can select in block mode by holding down the <kbd>Alt</kbd> key when starting a selection.
To copy the text to your clipboard, you can right-click the terminal when a selection is active. As of [#1224](https://github.com/microsoft/terminal/pull/1224) (first available in Windows Terminal v0.4), the Windows Terminal now supports HTML copy. The HTML is automatically copied to your clipboard along with the regular text in any copy operation.
If there is not an active selection, a right-click will paste the text content from your clipboard to the terminal.
Copy and paste operations can also be keybound. For more information on how to bind keys, see [Using Json Settings](UsingJsonSettings.md#adding-copy-and-paste-keybindings).
> 👉 **Note**: If you have the `copyOnSelect` global setting enabled, a selection will persist and immediately copy the selected text to your clipboard. Right-clicking will always paste your clipboard data.
## Add a "Open Windows Terminal Here" to File Explorer
Not currently supported "out of the box" (See issue [#1060](https://github.com/microsoft/terminal/issues/1060)). However, you can open Windows Terminal in current directory by typing `wt -d .` in the Explorer address bar.
## Configuring Windows Terminal
All Windows Terminal settings are currently managed using the `settings.json` file, located within `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState`.
To open the settings file from Windows Terminal:
1. Click the `⌵` button in the top bar.
2. From the dropdown list, click `Settings`. You can also use a shortcut: <kbd>Ctrl</kbd>+<kbd>,</kbd>.
3. Your default `json` editor will open the settings file.
For an introduction to the various settings, see [Using Json Settings](UsingJsonSettings.md). The list of valid settings can be found in the [settings.json documentation](../cascadia/SettingsSchema.md) section.
## Tips and Tricks
1. In PowerShell you can discover if the Windows Terminal is being used by checking for the existence of the environment variable `WT_SESSION`.
Under pwsh you can also use
`(Get-Process -Id $pid).Parent.ProcessName -eq 'WindowsTerminal'`
(ref [https://twitter.com/r_keith_hill/status/1142871145852440576](https://twitter.com/r_keith_hill/status/1142871145852440576))
2. Terminal zoom can be changed by holding <kbd>Ctrl</kbd> and scrolling with mouse.
3. Background opacity can be changed by holding <kbd>Ctrl</kbd>+<kbd>Shift</kbd> and scrolling with mouse. Note that acrylic transparency is limited by the OS only to focused windows.
4. Open Windows Terminal in current directory by typing `wt -d .` in the address bar.
5. Pin the Windows Terminal to the taskbar. Now it can be launched using the Windows shortcut <kbd>Win</kbd>+<kbd>Number</kbd> (e.g. <kbd>Win</kbd>+<kbd>1</kbd> or any other number based on the position in the taskbar!). Press <kbd>Win</kbd>+<kbd>Shift</kbd>+<kbd>Number</kbd> to always launch a new window.
6. Please add more Tips and Tricks.

View File

@@ -1,7 +0,0 @@
### Notes for Future Maintainers
This manifest anchors our usage of rgb.txt from the X11 distribution.
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
Please update the provenance information in that file when ingesting an updated version of the dependent library.
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.

View File

@@ -1,13 +0,0 @@
{"Registrations":[
{
"component": {
"type": "git",
"git": {
"repositoryUrl": "https://gitlab.freedesktop.org/xorg/app/rgb.git",
"commitHash": "97820e748eb496a1f6d3fc3bf89688f0ce1f64f9"
}
}
}
],
"Version": 1
}

Binary file not shown.

Binary file not shown.

View File

@@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
### Fonts Included
* Cascadia Code, Cascadia Mono (2009.21)
* from microsoft/cascadia-code@32f84124db1970fa5d032f0fe9019e6922961beb
* Cascadia Code, Cascadia Mono (2007.01)
* from microsoft/cascadia-code@311cc603f30635da704b6a7d13050e245e61667b

View File

@@ -108,7 +108,12 @@ TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column,
{
THROW_HR_IF(E_INVALIDARG, column >= _cchRowWidth);
const auto runPos = FindAttrIndex(column, pApplies);
return _list.at(runPos).GetAttributes();
return GetAttrByIndex(runPos);
}
TextAttribute ATTR_ROW::GetAttrByIndex(const size_t index) const
{
return _list.at(index).GetAttributes();
}
// Routine Description:
@@ -175,23 +180,6 @@ size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
return runPos - _list.cbegin();
}
// Routine Description:
// - Finds the hyperlink IDs present in this row and returns them
// Return value:
// - An unordered set containing the hyperlink IDs present in this row
std::unordered_set<uint16_t> ATTR_ROW::GetHyperlinks()
{
std::unordered_set<uint16_t> ids;
for (const auto& run : _list)
{
if (run.GetAttributes().IsHyperlink())
{
ids.emplace(run.GetAttributes().GetHyperlinkId());
}
}
return ids;
}
// Routine Description:
// - Sets the attributes (colors) of all character positions from the given position through the end of the row.
// Arguments:
@@ -240,7 +228,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
// Return Value:
// - STATUS_NO_MEMORY if there wasn't enough memory to insert the runs
// otherwise STATUS_SUCCESS if we were successful.
[[nodiscard]] HRESULT ATTR_ROW::InsertAttrRuns(const gsl::span<const TextAttributeRun> newAttrs,
[[nodiscard]] HRESULT ATTR_ROW::InsertAttrRuns(const std::basic_string_view<TextAttributeRun> newAttrs,
const size_t iStart,
const size_t iEnd,
const size_t cBufferWidth)
@@ -267,7 +255,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
if (newAttrs.size() == 1)
{
// Get the new color attribute we're trying to apply
const TextAttribute NewAttr = til::at(newAttrs, 0).GetAttributes();
const TextAttribute NewAttr = newAttrs.at(0).GetAttributes();
// If the existing run was only 1 element...
// ...and the new color is the same as the old, we don't have to do anything and can exit quick.
@@ -389,7 +377,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
if (iStart == 0 && iEnd == iLastBufferCol)
{
// Just dump what we're given over what we have and call it a day.
_list.assign(newAttrs.begin(), newAttrs.end());
_list.assign(newAttrs.cbegin(), newAttrs.cend());
return S_OK;
}

View File

@@ -36,19 +36,19 @@ public:
TextAttribute GetAttrByColumn(const size_t column,
size_t* const pApplies) const;
TextAttribute GetAttrByIndex(const size_t index) const;
size_t GetNumberOfRuns() const noexcept;
size_t FindAttrIndex(const size_t index,
size_t* const pApplies) const;
std::unordered_set<uint16_t> GetHyperlinks();
bool SetAttrToEnd(const UINT iStart, const TextAttribute attr);
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept;
void Resize(const size_t newWidth);
[[nodiscard]] HRESULT InsertAttrRuns(const gsl::span<const TextAttributeRun> newAttrs,
[[nodiscard]] HRESULT InsertAttrRuns(const std::basic_string_view<TextAttributeRun> newAttrs,
const size_t iStart,
const size_t iEnd,
const size_t cBufferWidth);

View File

@@ -112,9 +112,9 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const
// - This is an iterator over legacy colors only. The text is not modified.
// Arguments:
// - legacyAttrs - One legacy color item per cell
OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs) noexcept :
OutputCellIterator::OutputCellIterator(const std::basic_string_view<WORD> legacyAttrs) noexcept :
_mode(Mode::LegacyAttr),
_currentView(s_GenerateViewLegacyAttr(til::at(legacyAttrs, 0))),
_currentView(s_GenerateViewLegacyAttr(legacyAttrs.at(0))),
_run(legacyAttrs),
_attr(InvalidTextAttribute),
_distance(0),
@@ -127,9 +127,9 @@ OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs)
// - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute.
// Arguments:
// - charInfos - Multiple cell with unicode text and legacy color data.
OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept :
OutputCellIterator::OutputCellIterator(const std::basic_string_view<CHAR_INFO> charInfos) noexcept :
_mode(Mode::CharInfo),
_currentView(s_GenerateView(til::at(charInfos, 0))),
_currentView(s_GenerateView(charInfos.at(0))),
_run(charInfos),
_attr(InvalidTextAttribute),
_distance(0),
@@ -142,9 +142,9 @@ OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfo
// - This is an iterator over existing OutputCells with full text and color data.
// Arguments:
// - cells - Multiple cells in a run
OutputCellIterator::OutputCellIterator(const gsl::span<const OutputCell> cells) :
OutputCellIterator::OutputCellIterator(const std::basic_string_view<OutputCell> cells) :
_mode(Mode::Cell),
_currentView(s_GenerateView(til::at(cells, 0))),
_currentView(s_GenerateView(cells.at(0))),
_run(cells),
_attr(InvalidTextAttribute),
_distance(0),
@@ -180,15 +180,15 @@ OutputCellIterator::operator bool() const noexcept
}
case Mode::Cell:
{
return _pos < std::get<gsl::span<const OutputCell>>(_run).size();
return _pos < std::get<std::basic_string_view<OutputCell>>(_run).length();
}
case Mode::CharInfo:
{
return _pos < std::get<gsl::span<const CHAR_INFO>>(_run).size();
return _pos < std::get<std::basic_string_view<CHAR_INFO>>(_run).length();
}
case Mode::LegacyAttr:
{
return _pos < std::get<gsl::span<const WORD>>(_run).size();
return _pos < std::get<std::basic_string_view<WORD>>(_run).length();
}
default:
FAIL_FAST_HR(E_NOTIMPL);
@@ -265,7 +265,7 @@ OutputCellIterator& OutputCellIterator::operator++()
_pos++;
if (operator bool())
{
_currentView = s_GenerateView(til::at(std::get<gsl::span<const OutputCell>>(_run), _pos));
_currentView = s_GenerateView(std::get<std::basic_string_view<OutputCell>>(_run).at(_pos));
}
break;
}
@@ -275,7 +275,7 @@ OutputCellIterator& OutputCellIterator::operator++()
_pos++;
if (operator bool())
{
_currentView = s_GenerateView(til::at(std::get<gsl::span<const CHAR_INFO>>(_run), _pos));
_currentView = s_GenerateView(std::get<std::basic_string_view<CHAR_INFO>>(_run).at(_pos));
}
break;
}
@@ -285,7 +285,7 @@ OutputCellIterator& OutputCellIterator::operator++()
_pos++;
if (operator bool())
{
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<gsl::span<const WORD>>(_run), _pos));
_currentView = s_GenerateViewLegacyAttr(std::get<std::basic_string_view<WORD>>(_run).at(_pos));
}
break;
}

View File

@@ -39,9 +39,9 @@ public:
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const std::wstring_view utf16Text);
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute);
OutputCellIterator(const gsl::span<const WORD> legacyAttributes) noexcept;
OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept;
OutputCellIterator(const gsl::span<const OutputCell> cells);
OutputCellIterator(const std::basic_string_view<WORD> legacyAttributes) noexcept;
OutputCellIterator(const std::basic_string_view<CHAR_INFO> charInfos) noexcept;
OutputCellIterator(const std::basic_string_view<OutputCell> cells);
~OutputCellIterator() = default;
OutputCellIterator& operator=(const OutputCellIterator& it) = default;
@@ -86,13 +86,13 @@ private:
};
Mode _mode;
gsl::span<const WORD> _legacyAttrs;
std::basic_string_view<WORD> _legacyAttrs;
std::variant<
std::wstring_view,
gsl::span<const WORD>,
gsl::span<const CHAR_INFO>,
gsl::span<const OutputCell>,
std::basic_string_view<WORD>,
std::basic_string_view<CHAR_INFO>,
std::basic_string_view<OutputCell>,
std::monostate>
_run;

View File

@@ -50,7 +50,7 @@ gsl::span<OutputCell> OutputCellRect::GetRow(const size_t row)
// - Read-only iterator of OutputCells
OutputCellIterator OutputCellRect::GetRowIter(const size_t row) const
{
const gsl::span<const OutputCell> view(_FindRowOffset(row), _cols);
const std::basic_string_view<OutputCell> view(_FindRowOffset(row), _cols);
return OutputCellIterator(view);
}

View File

@@ -160,98 +160,66 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
// If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row.
const auto finalColumnInRow = limitRight.value_or(_charRow.size() - 1);
if (it)
while (it && currentIndex <= finalColumnInRow)
{
// Accumulate usages of the same color so we can spend less time in InsertAttrRuns rewriting it.
auto currentColor = it->TextAttr();
size_t colorUses = 0;
size_t colorStarts = index;
while (it && currentIndex <= finalColumnInRow)
// Fill the color if the behavior isn't set to keeping the current color.
if (it->TextAttrBehavior() != TextAttributeBehavior::Current)
{
// Fill the color if the behavior isn't set to keeping the current color.
if (it->TextAttrBehavior() != TextAttributeBehavior::Current)
const TextAttributeRun attrRun{ 1, it->TextAttr() };
LOG_IF_FAILED(_attrRow.InsertAttrRuns({ &attrRun, 1 },
currentIndex,
currentIndex,
_charRow.size()));
}
// Fill the text if the behavior isn't set to saying there's only a color stored in this iterator.
if (it->TextAttrBehavior() != TextAttributeBehavior::StoredOnly)
{
const bool fillingLastColumn = currentIndex == finalColumnInRow;
// TODO: MSFT: 19452170 - We need to ensure when writing any trailing byte that the one to the left
// is a matching leading byte. Likewise, if we're writing a leading byte, we need to make sure we still have space in this loop
// for the trailing byte coming up before writing it.
// If we're trying to fill the first cell with a trailing byte, pad it out instead by clearing it.
// Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
{
// If the color of this cell is the same as the run we're currently on,
// just increment the counter.
if (currentColor == it->TextAttr())
{
++colorUses;
}
else
{
// Otherwise, commit this color into the run and save off the new one.
const TextAttributeRun run{ colorUses, currentColor };
// Now commit the new color runs into the attr row.
LOG_IF_FAILED(_attrRow.InsertAttrRuns({ &run, 1 },
colorStarts,
currentIndex - 1,
_charRow.size()));
currentColor = it->TextAttr();
colorUses = 1;
colorStarts = currentIndex;
}
_charRow.ClearCell(currentIndex);
}
// Fill the text if the behavior isn't set to saying there's only a color stored in this iterator.
if (it->TextAttrBehavior() != TextAttributeBehavior::StoredOnly)
// If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
// Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
{
const bool fillingLastColumn = currentIndex == finalColumnInRow;
// TODO: MSFT: 19452170 - We need to ensure when writing any trailing byte that the one to the left
// is a matching leading byte. Likewise, if we're writing a leading byte, we need to make sure we still have space in this loop
// for the trailing byte coming up before writing it.
// If we're trying to fill the first cell with a trailing byte, pad it out instead by clearing it.
// Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
{
_charRow.ClearCell(currentIndex);
}
// If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
// Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
{
_charRow.ClearCell(currentIndex);
_charRow.SetDoubleBytePadded(true);
}
// Otherwise, copy the data given and increment the iterator.
else
{
_charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
_charRow.GlyphAt(currentIndex) = it->Chars();
++it;
}
// If we're asked to (un)set the wrap status and we just filled the last column with some text...
// NOTE:
// - wrap = std::nullopt --> don't change the wrap value
// - wrap = true --> we're filling cells as a steam, consider this a wrap
// - wrap = false --> we're filling cells as a block, unwrap
if (wrap.has_value() && fillingLastColumn)
{
// set wrap status on the row to parameter's value.
_charRow.SetWrapForced(wrap.value());
}
_charRow.ClearCell(currentIndex);
_charRow.SetDoubleBytePadded(true);
}
// Otherwise, copy the data given and increment the iterator.
else
{
_charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
_charRow.GlyphAt(currentIndex) = it->Chars();
++it;
}
// Move to the next cell for the next time through the loop.
++currentIndex;
// If we're asked to (un)set the wrap status and we just filled the last column with some text...
// NOTE:
// - wrap = std::nullopt --> don't change the wrap value
// - wrap = true --> we're filling cells as a steam, consider this a wrap
// - wrap = false --> we're filling cells as a block, unwrap
if (wrap.has_value() && fillingLastColumn)
{
// set wrap status on the row to parameter's value.
_charRow.SetWrapForced(wrap.value());
}
}
else
{
++it;
}
// Now commit the final color into the attr row
if (colorUses)
{
const TextAttributeRun run{ colorUses, currentColor };
LOG_IF_FAILED(_attrRow.InsertAttrRuns({ &run, 1 },
colorStarts,
currentIndex - 1,
_charRow.size()));
}
// Move to the next cell for the next time through the loop.
++currentIndex;
}
return it;

View File

@@ -88,18 +88,16 @@ bool TextAttribute::IsLegacy() const noexcept
// - defaultFgColor: the default foreground color rgb value.
// - defaultBgColor: the default background color rgb value.
// - reverseScreenMode: true if the screen mode is reversed.
// - blinkingIsFaint: true if blinking should be interpreted as faint.
// Return Value:
// - the foreground and background colors that should be displayed.
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const std::basic_string_view<COLORREF> colorTable,
const COLORREF defaultFgColor,
const COLORREF defaultBgColor,
const bool reverseScreenMode,
const bool blinkingIsFaint) const noexcept
const bool reverseScreenMode) const noexcept
{
auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold());
auto bg = _background.GetColor(colorTable, defaultBgColor);
if (IsFaint() || (IsBlinking() && blinkingIsFaint))
if (IsFaint())
{
fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two.
}
@@ -114,17 +112,6 @@ std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<
return { fg, bg };
}
// Method description:
// - Tells us whether the text is a hyperlink or not
// Return value:
// - True if it is a hyperlink, false otherwise
bool TextAttribute::IsHyperlink() const noexcept
{
// All non-hyperlink text have a default hyperlinkId of 0 while
// all hyperlink text have a non-zero hyperlinkId
return _hyperlinkId != 0;
}
TextColor TextAttribute::GetForeground() const noexcept
{
return _foreground;
@@ -135,15 +122,6 @@ TextColor TextAttribute::GetBackground() const noexcept
return _background;
}
// Method description:
// - Retrieves the hyperlink ID of the text
// Return value:
// - The hyperlink ID
uint16_t TextAttribute::GetHyperlinkId() const noexcept
{
return _hyperlinkId;
}
void TextAttribute::SetForeground(const TextColor foreground) noexcept
{
_foreground = foreground;
@@ -196,15 +174,6 @@ void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground)
}
}
// Method description:
// - Sets the hyperlink ID of the text
// Arguments:
// - id - the id we wish to set
void TextAttribute::SetHyperlinkId(uint16_t id) noexcept
{
_hyperlinkId = id;
}
bool TextAttribute::IsLeadingByte() const noexcept
{
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
@@ -277,12 +246,8 @@ bool TextAttribute::IsCrossedOut() const noexcept
bool TextAttribute::IsUnderlined() const noexcept
{
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Underlined);
}
bool TextAttribute::IsDoublyUnderlined() const noexcept
{
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::DoublyUnderlined);
// TODO:GH#2915 Treat underline separately from LVB_UNDERSCORE
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_UNDERSCORE);
}
bool TextAttribute::IsOverlined() const noexcept
@@ -305,7 +270,7 @@ void TextAttribute::SetFaint(bool isFaint) noexcept
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Faint, isFaint);
}
void TextAttribute::SetItalic(bool isItalic) noexcept
void TextAttribute::SetItalics(bool isItalic) noexcept
{
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Italics, isItalic);
}
@@ -325,17 +290,13 @@ void TextAttribute::SetCrossedOut(bool isCrossedOut) noexcept
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::CrossedOut, isCrossedOut);
}
void TextAttribute::SetUnderlined(bool isUnderlined) noexcept
void TextAttribute::SetUnderline(bool isUnderlined) noexcept
{
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Underlined, isUnderlined);
// TODO:GH#2915 Treat underline separately from LVB_UNDERSCORE
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_UNDERSCORE, isUnderlined);
}
void TextAttribute::SetDoublyUnderlined(bool isDoublyUnderlined) noexcept
{
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::DoublyUnderlined, isDoublyUnderlined);
}
void TextAttribute::SetOverlined(bool isOverlined) noexcept
void TextAttribute::SetOverline(bool isOverlined) noexcept
{
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL, isOverlined);
}
@@ -367,14 +328,6 @@ void TextAttribute::SetDefaultBackground() noexcept
_background = TextColor();
}
// Method description:
// - Resets only the meta and extended attributes
void TextAttribute::SetDefaultMetaAttrs() noexcept
{
_extendedAttrs = ExtendedAttributes::Normal;
_wAttrLegacy = 0;
}
// Method Description:
// - Returns true if this attribute indicates its background is the "default"
// background. Its _rgbBackground will contain the actual value of the
@@ -395,6 +348,6 @@ bool TextAttribute::BackgroundIsDefault() const noexcept
// requires for most erasing and filling operations.
void TextAttribute::SetStandardErase() noexcept
{
SetDefaultMetaAttrs();
_hyperlinkId = 0;
_extendedAttrs = ExtendedAttributes::Normal;
_wAttrLegacy = 0;
}

View File

@@ -36,8 +36,7 @@ public:
_wAttrLegacy{ 0 },
_foreground{},
_background{},
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
_extendedAttrs{ ExtendedAttributes::Normal }
{
}
@@ -45,8 +44,7 @@ public:
_wAttrLegacy{ gsl::narrow_cast<WORD>(wLegacyAttr & META_ATTRS) },
_foreground{ s_LegacyIndexOrDefault(wLegacyAttr & FG_ATTRS, s_legacyDefaultForeground) },
_background{ s_LegacyIndexOrDefault((wLegacyAttr & BG_ATTRS) >> 4, s_legacyDefaultBackground) },
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
_extendedAttrs{ ExtendedAttributes::Normal }
{
// If we're given lead/trailing byte information with the legacy color, strip it.
WI_ClearAllFlags(_wAttrLegacy, COMMON_LVB_SBCSDBCS);
@@ -57,8 +55,7 @@ public:
_wAttrLegacy{ 0 },
_foreground{ rgbForeground },
_background{ rgbBackground },
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
_extendedAttrs{ ExtendedAttributes::Normal }
{
}
@@ -66,11 +63,10 @@ public:
static TextAttribute StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept;
WORD GetLegacyAttributes() const noexcept;
std::pair<COLORREF, COLORREF> CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
std::pair<COLORREF, COLORREF> CalculateRgbColors(const std::basic_string_view<COLORREF> colorTable,
const COLORREF defaultFgColor,
const COLORREF defaultBgColor,
const bool reverseScreenMode = false,
const bool blinkingIsFaint = false) const noexcept;
const bool reverseScreenMode = false) const noexcept;
bool IsLeadingByte() const noexcept;
bool IsTrailingByte() const noexcept;
@@ -99,28 +95,23 @@ public:
bool IsInvisible() const noexcept;
bool IsCrossedOut() const noexcept;
bool IsUnderlined() const noexcept;
bool IsDoublyUnderlined() const noexcept;
bool IsOverlined() const noexcept;
bool IsReverseVideo() const noexcept;
void SetBold(bool isBold) noexcept;
void SetFaint(bool isFaint) noexcept;
void SetItalic(bool isItalic) noexcept;
void SetItalics(bool isItalic) noexcept;
void SetBlinking(bool isBlinking) noexcept;
void SetInvisible(bool isInvisible) noexcept;
void SetCrossedOut(bool isCrossedOut) noexcept;
void SetUnderlined(bool isUnderlined) noexcept;
void SetDoublyUnderlined(bool isDoublyUnderlined) noexcept;
void SetOverlined(bool isOverlined) noexcept;
void SetUnderline(bool isUnderlined) noexcept;
void SetOverline(bool isOverlined) noexcept;
void SetReverseVideo(bool isReversed) noexcept;
ExtendedAttributes GetExtendedAttributes() const noexcept;
bool IsHyperlink() const noexcept;
TextColor GetForeground() const noexcept;
TextColor GetBackground() const noexcept;
uint16_t GetHyperlinkId() const noexcept;
void SetForeground(const TextColor foreground) noexcept;
void SetBackground(const TextColor background) noexcept;
void SetForeground(const COLORREF rgbForeground) noexcept;
@@ -130,11 +121,9 @@ public:
void SetIndexedForeground256(const BYTE fgIndex) noexcept;
void SetIndexedBackground256(const BYTE bgIndex) noexcept;
void SetColor(const COLORREF rgbColor, const bool fIsForeground) noexcept;
void SetHyperlinkId(uint16_t id) noexcept;
void SetDefaultForeground() noexcept;
void SetDefaultBackground() noexcept;
void SetDefaultMetaAttrs() noexcept;
bool BackgroundIsDefault() const noexcept;
@@ -152,14 +141,11 @@ public:
return !IsAnyGridLineEnabled() && // grid lines have a visual representation
// crossed out, doubly and singly underlined have a visual representation
WI_AreAllFlagsClear(_extendedAttrs, ExtendedAttributes::CrossedOut | ExtendedAttributes::DoublyUnderlined | ExtendedAttributes::Underlined) &&
// hyperlinks have a visual representation
!IsHyperlink() &&
// all other attributes do not have a visual representation
(_wAttrLegacy & META_ATTRS) == (other._wAttrLegacy & META_ATTRS) &&
((checkForeground && _foreground == other._foreground) ||
(!checkForeground && _background == other._background)) &&
_extendedAttrs == other._extendedAttrs &&
IsHyperlink() == other.IsHyperlink();
_extendedAttrs == other._extendedAttrs;
}
constexpr bool IsAnyGridLineEnabled() const noexcept
@@ -181,8 +167,6 @@ private:
TextColor _background;
ExtendedAttributes _extendedAttrs;
uint16_t _hyperlinkId;
#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class TextAttributeTests;
@@ -196,7 +180,7 @@ private:
// 4 for _foreground
// 4 for _background
// 1 for _extendedAttrs
static_assert(sizeof(TextAttribute) <= 13 * sizeof(BYTE), "We should only need 13B for an entire TextAttribute. We may need to increment this in the future as we add additional attributes");
static_assert(sizeof(TextAttribute) <= 11 * sizeof(BYTE), "We should only need 11B for an entire TextColor. Any more than that is just waste");
enum class TextAttributeBehavior
{
@@ -210,8 +194,7 @@ constexpr bool operator==(const TextAttribute& a, const TextAttribute& b) noexce
return a._wAttrLegacy == b._wAttrLegacy &&
a._foreground == b._foreground &&
a._background == b._background &&
a._extendedAttrs == b._extendedAttrs &&
a._hyperlinkId == b._hyperlinkId;
a._extendedAttrs == b._extendedAttrs;
}
constexpr bool operator!=(const TextAttribute& a, const TextAttribute& b) noexcept
@@ -235,12 +218,11 @@ namespace WEX
static WEX::Common::NoThrowString ToString(const TextAttribute& attr)
{
return WEX::Common::NoThrowString().Format(
L"{FG:%s,BG:%s,bold:%d,wLegacy:(0x%04x),ext:(0x%02x)}",
L"{FG:%s,BG:%s,bold:%d,wLegacy:(0x%04x)}",
VerifyOutputTraits<TextColor>::ToString(attr._foreground).GetBuffer(),
VerifyOutputTraits<TextColor>::ToString(attr._background).GetBuffer(),
attr.IsBold(),
attr._wAttrLegacy,
static_cast<DWORD>(attr._extendedAttrs));
attr._wAttrLegacy);
}
};
}

View File

@@ -138,7 +138,7 @@ void TextColor::SetDefault() noexcept
// - brighten: if true, we'll brighten a dark color table index.
// Return Value:
// - a COLORREF containing the real value of this TextColor.
COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
COLORREF TextColor::GetColor(std::basic_string_view<COLORREF> colorTable,
const COLORREF defaultColor,
bool brighten) const noexcept
{
@@ -158,9 +158,9 @@ COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
// If we find a match, return instead the bright version of this color
for (size_t i = 0; i < 8; i++)
{
if (til::at(colorTable, i) == defaultColor)
if (colorTable.at(i) == defaultColor)
{
return til::at(colorTable, i + 8);
return colorTable.at(i + 8);
}
}
}
@@ -173,11 +173,11 @@ COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
}
else if (IsIndex16() && brighten)
{
return til::at(colorTable, _index | 8);
return colorTable.at(_index | 8);
}
else
{
return til::at(colorTable, _index);
return colorTable.at(_index);
}
}

View File

@@ -88,7 +88,7 @@ public:
void SetIndex(const BYTE index, const bool isIndex256) noexcept;
void SetDefault() noexcept;
COLORREF GetColor(gsl::span<const COLORREF> colorTable,
COLORREF GetColor(std::basic_string_view<COLORREF> colorTable,
const COLORREF defaultColor,
const bool brighten = false) const noexcept;

View File

@@ -35,7 +35,7 @@ void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
// - erases key and its associated data from the storage
// Arguments:
// - key - the key to remove
void UnicodeStorage::Erase(const key_type key) noexcept
void UnicodeStorage::Erase(const key_type key)
{
_map.erase(key);
}

View File

@@ -53,7 +53,7 @@ public:
void StoreGlyph(const key_type key, const mapped_type& glyph);
void Erase(const key_type key) noexcept;
void Erase(const key_type key);
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);

View File

@@ -34,8 +34,7 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
_storage{},
_unicodeStorage{},
_renderTarget{ renderTarget },
_size{},
_currentHyperlinkId{ 1 }
_size{}
{
// initialize ROWs
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
@@ -552,10 +551,7 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
// to the logical position 0 in the window (cursor coordinates and all other coordinates).
_renderTarget.TriggerCircling();
// Prune hyperlinks to delete obsolete references
_PruneHyperlinks();
// Second, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed.
// First, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed.
auto fillAttributes = _currentAttributes;
if (inVtMode)
{
@@ -791,7 +787,7 @@ const Cursor& TextBuffer::GetCursor() const noexcept
return _currentAttributes;
}
void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept
void TextBuffer::SetCurrentAttributes(const TextAttribute currentAttributes) noexcept
{
_currentAttributes = currentAttributes;
}
@@ -996,29 +992,19 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view
// so the words in the example include ["word ", "other "]
// NOTE: the start anchor (this one) is inclusive, whereas the end anchor (GetWordEnd) is exclusive
#pragma warning(suppress : 26496)
// GH#7664: Treat EndExclusive as EndInclusive so
// that it actually points to a space in the buffer
auto copy{ target };
const auto bufferSize{ GetSize() };
if (target == bufferSize.Origin())
// can't expand left
if (target.X == GetSize().Left())
{
// can't expand left
return target;
}
else if (target == bufferSize.EndExclusive())
{
// treat EndExclusive as EndInclusive
copy = { bufferSize.RightInclusive(), bufferSize.BottomInclusive() };
}
if (accessibilityMode)
{
return _GetWordStartForAccessibility(copy, wordDelimiters);
return _GetWordStartForAccessibility(target, wordDelimiters);
}
else
{
return _GetWordStartForSelection(copy, wordDelimiters);
return _GetWordStartForSelection(target, wordDelimiters);
}
}
@@ -1118,16 +1104,9 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w
// so the words in the example include ["word ", "other "]
// NOTE: the end anchor (this one) is exclusive, whereas the start anchor (GetWordStart) is inclusive
// Already at the end. Can't move forward.
if (target == GetSize().EndExclusive())
{
return target;
}
if (accessibilityMode)
{
const auto lastCharPos{ GetLastNonSpaceCharacter() };
return _GetWordEndForAccessibility(target, wordDelimiters, lastCharPos);
return _GetWordEndForAccessibility(target, wordDelimiters);
}
else
{
@@ -1140,20 +1119,13 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w
// Arguments:
// - target - a COORD on the word you are currently on
// - wordDelimiters - what characters are we considering for the separation of words
// - lastCharPos - the position of the last nonspace character in the text buffer (to improve performance)
// Return Value:
// - The COORD for the first character of the next readable "word". If no next word, return one past the end of the buffer
const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const
const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const
{
const auto bufferSize = GetSize();
COORD result = target;
// Check if we're already on/past the last RegularChar
if (bufferSize.CompareInBounds(result, lastCharPos, true) >= 0)
{
return bufferSize.EndExclusive();
}
// ignore right boundary. Continue through readable text found
while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar)
{
@@ -1163,12 +1135,6 @@ const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const st
}
}
// we are already on/past the last RegularChar
if (bufferSize.CompareInBounds(result, lastCharPos, true) >= 0)
{
return bufferSize.EndExclusive();
}
// make sure we expand to the beginning of the NEXT word
while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
{
@@ -1219,46 +1185,6 @@ const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::w
return result;
}
void TextBuffer::_PruneHyperlinks()
{
// Check the old first row for hyperlink references
// If there are any, search the entire buffer for the same reference
// If the buffer does not contain the same reference, we can remove that hyperlink from our map
// This way, obsolete hyperlink references are cleared from our hyperlink map instead of hanging around
// Get all the hyperlink references in the row we're erasing
auto firstRowRefs = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
if (!firstRowRefs.empty())
{
const auto total = TotalRowCount();
// Loop through all the rows in the buffer except the first row -
// we have found all hyperlink references in the first row and put them in refs,
// now we need to search the rest of the buffer (i.e. all the rows except the first)
// to see if those references are anywhere else
for (size_t i = 1; i != total; ++i)
{
const auto nextRowRefs = GetRowByOffset(i).GetAttrRow().GetHyperlinks();
for (auto id : nextRowRefs)
{
if (firstRowRefs.find(id) != firstRowRefs.end())
{
firstRowRefs.erase(id);
}
}
if (firstRowRefs.empty())
{
// No more hyperlink references left to search for, terminate early
break;
}
}
}
// Now delete obsolete references from our map
for (auto hyperlinkReference : firstRowRefs)
{
RemoveHyperlinkFromMap(hyperlinkReference);
}
}
// Method Description:
// - Update pos to be the position of the first character of the next word. This is used for accessibility
// Arguments:
@@ -1270,16 +1196,38 @@ void TextBuffer::_PruneHyperlinks()
// - pos - The COORD for the first character on the "word" (inclusive)
bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const
{
// move to the beginning of the next word
// NOTE: _GetWordEnd...() returns the exclusive position of the "end of the word"
// This is also the inclusive start of the next word.
auto copy{ _GetWordEndForAccessibility(pos, wordDelimiters, lastCharPos) };
auto copy = pos;
const auto bufferSize = GetSize();
if (copy == GetSize().EndExclusive())
// started on a word, continue until the end of the word
while (_GetDelimiterClassAt(copy, wordDelimiters) == DelimiterClass::RegularChar)
{
if (!bufferSize.IncrementInBounds(copy))
{
// last char in buffer is a RegularChar
// thus there is no next word
return false;
}
}
// we are already on/past the last RegularChar
if (bufferSize.CompareInBounds(copy, lastCharPos) >= 0)
{
return false;
}
// on whitespace, continue until the beginning of the next word
while (_GetDelimiterClassAt(copy, wordDelimiters) != DelimiterClass::RegularChar)
{
if (!bufferSize.IncrementInBounds(copy))
{
// last char in buffer is a DelimiterChar or ControlChar
// there is no next word
return false;
}
}
// successful move, copy result out
pos = copy;
return true;
}
@@ -1294,17 +1242,33 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite
// - pos - The COORD for the first character on the "word" (inclusive)
bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const
{
// move to the beginning of the current word
auto copy{ GetWordStart(pos, wordDelimiters, true) };
auto copy = pos;
auto bufferSize = GetSize();
if (!GetSize().DecrementInBounds(copy, true))
// started on whitespace/delimiter, continue until the end of the previous word
while (_GetDelimiterClassAt(copy, wordDelimiters) != DelimiterClass::RegularChar)
{
// can't move behind current word
return false;
if (!bufferSize.DecrementInBounds(copy))
{
// first char in buffer is a DelimiterChar or ControlChar
// there is no previous word
return false;
}
}
// move to the beginning of the previous word
pos = GetWordStart(copy, wordDelimiters, true);
// on a word, continue until the beginning of the word
while (_GetDelimiterClassAt(copy, wordDelimiters) == DelimiterClass::RegularChar)
{
if (!bufferSize.DecrementInBounds(copy))
{
// first char in buffer is a RegularChar
// there is no previous word
return false;
}
}
// successful move, copy result out
pos = copy;
return true;
}
@@ -1317,13 +1281,8 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
const til::point TextBuffer::GetGlyphStart(const til::point pos) const
{
COORD resultPos = pos;
const auto bufferSize = GetSize();
if (resultPos == bufferSize.EndExclusive())
{
bufferSize.DecrementInBounds(resultPos, true);
}
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
{
bufferSize.DecrementInBounds(resultPos, true);
@@ -1364,15 +1323,9 @@ const til::point TextBuffer::GetGlyphEnd(const til::point pos) const
bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) const
{
COORD resultPos = pos;
const auto bufferSize = GetSize();
if (resultPos == GetSize().EndExclusive())
{
// we're already at the end
return false;
}
// try to move. If we can't, we're done.
const auto bufferSize = GetSize();
const bool success = bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
{
@@ -1387,19 +1340,20 @@ bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) con
// - Update pos to be the beginning of the previous glyph/character. This is used for accessibility
// Arguments:
// - pos - a COORD on the word you are currently on
// - allowBottomExclusive - allow the nonexistent end-of-buffer cell to be encountered
// Return Value:
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
// - pos - The COORD for the first cell of the previous glyph (inclusive)
bool TextBuffer::MoveToPreviousGlyph(til::point& pos) const
bool TextBuffer::MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive) const
{
COORD resultPos = pos;
// try to move. If we can't, we're done.
const auto bufferSize = GetSize();
const bool success = bufferSize.DecrementInBounds(resultPos, true);
const bool success = bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
{
bufferSize.DecrementInBounds(resultPos, true);
bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
}
pos = resultPos;
@@ -2188,7 +2142,6 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// Finish copying remaining parameters from the old text buffer to the new one
newBuffer.CopyProperties(oldBuffer);
newBuffer.CopyHyperlinkMaps(oldBuffer);
// If we found where to put the cursor while placing characters into the buffer,
// just put the cursor there. Otherwise we have to advance manually.
@@ -2254,109 +2207,3 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
return hr;
}
// Method Description:
// - Adds or updates a hyperlink in our hyperlink table
// Arguments:
// - The hyperlink URI, the hyperlink id (could be new or old)
void TextBuffer::AddHyperlinkToMap(std::wstring_view uri, uint16_t id)
{
_hyperlinkMap[id] = uri;
}
// Method Description:
// - Retrieves the URI associated with a particular hyperlink ID
// Arguments:
// - The hyperlink ID
// Return Value:
// - The URI
std::wstring TextBuffer::GetHyperlinkUriFromId(uint16_t id) const
{
return _hyperlinkMap.at(id);
}
// Method description:
// - Provides the hyperlink ID to be assigned as a text attribute, based on the optional custom id provided
// Arguments:
// - The user-defined id
// Return value:
// - The internal hyperlink ID
uint16_t TextBuffer::GetHyperlinkId(std::wstring_view uri, std::wstring_view id)
{
uint16_t numericId = 0;
if (id.empty())
{
// no custom id specified, return our internal count
numericId = _currentHyperlinkId;
++_currentHyperlinkId;
}
else
{
// assign _currentHyperlinkId if the custom id does not already exist
std::wstring newId{ id };
// hash the URL and add it to the custom ID - GH#7698
newId += L"%" + std::to_wstring(std::hash<std::wstring_view>{}(uri));
const auto result = _hyperlinkCustomIdMap.emplace(newId, _currentHyperlinkId);
if (result.second)
{
// the custom id did not already exist
++_currentHyperlinkId;
}
numericId = (*(result.first)).second;
}
// _currentHyperlinkId could overflow, make sure its not 0
if (_currentHyperlinkId == 0)
{
++_currentHyperlinkId;
}
return numericId;
}
// Method Description:
// - Removes a hyperlink from the hyperlink map and the associated
// user defined id from the custom id map (if there is one)
// Arguments:
// - The ID of the hyperlink to be removed
void TextBuffer::RemoveHyperlinkFromMap(uint16_t id)
{
_hyperlinkMap.erase(id);
for (const auto& customIdPair : _hyperlinkCustomIdMap)
{
if (customIdPair.second == id)
{
_hyperlinkCustomIdMap.erase(customIdPair.first);
break;
}
}
}
// Method Description:
// - Obtains the custom ID, if there was one, associated with the
// uint16_t id of a hyperlink
// Arguments:
// - The uint16_t id of the hyperlink
// Return Value:
// - The custom ID if there was one, empty string otherwise
std::wstring TextBuffer::GetCustomIdFromId(uint16_t id) const
{
for (auto customIdPair : _hyperlinkCustomIdMap)
{
if (customIdPair.second == id)
{
return customIdPair.first;
}
}
return {};
}
// Method Description:
// - Copies the hyperlink/customID maps of the old buffer into this one,
// also copies currentHyperlinkId
// Arguments:
// - The other buffer
void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other)
{
_hyperlinkMap = other._hyperlinkMap;
_hyperlinkCustomIdMap = other._hyperlinkCustomIdMap;
_currentHyperlinkId = other._currentHyperlinkId;
}

View File

@@ -118,7 +118,7 @@ public:
[[nodiscard]] TextAttribute GetCurrentAttributes() const noexcept;
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
void SetCurrentAttributes(const TextAttribute currentAttributes) noexcept;
void Reset();
@@ -137,17 +137,10 @@ public:
const til::point GetGlyphStart(const til::point pos) const;
const til::point GetGlyphEnd(const til::point pos) const;
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
bool MoveToPreviousGlyph(til::point& pos) const;
bool MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive = false) const;
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
void AddHyperlinkToMap(std::wstring_view uri, uint16_t id);
std::wstring GetHyperlinkUriFromId(uint16_t id) const;
uint16_t GetHyperlinkId(std::wstring_view uri, std::wstring_view id);
void RemoveHyperlinkFromMap(uint16_t id);
std::wstring GetCustomIdFromId(uint16_t id) const;
void CopyHyperlinkMaps(const TextBuffer& OtherBuffer);
class TextAndColor
{
public:
@@ -195,10 +188,6 @@ private:
// storage location for glyphs that can't fit into the buffer normally
UnicodeStorage _unicodeStorage;
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
uint16_t _currentHyperlinkId;
void _RefreshRowIDs(std::optional<SHORT> newRowWidth);
Microsoft::Console::Render::IRenderTarget& _renderTarget;
@@ -224,11 +213,9 @@ private:
const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const;
const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const;
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
void _PruneHyperlinks();
#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class UiaTextRangeTests;

View File

@@ -27,7 +27,7 @@ class TextAttributeTests
COLORREF _colorTable[COLOR_TABLE_SIZE];
COLORREF _defaultFg = RGB(1, 2, 3);
COLORREF _defaultBg = RGB(4, 5, 6);
gsl::span<const COLORREF> _GetTableView();
std::basic_string_view<COLORREF> _GetTableView();
};
bool TextAttributeTests::ClassSetup()
@@ -51,9 +51,9 @@ bool TextAttributeTests::ClassSetup()
return true;
}
gsl::span<const COLORREF> TextAttributeTests::_GetTableView()
std::basic_string_view<COLORREF> TextAttributeTests::_GetTableView()
{
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
return std::basic_string_view<COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
}
void TextAttributeTests::TestRoundtripLegacy()

View File

@@ -27,7 +27,7 @@ class TextColorTests
COLORREF _colorTable[COLOR_TABLE_SIZE];
COLORREF _defaultFg = RGB(1, 2, 3);
COLORREF _defaultBg = RGB(4, 5, 6);
gsl::span<const COLORREF> _GetTableView();
std::basic_string_view<COLORREF> _GetTableView();
};
bool TextColorTests::ClassSetup()
@@ -51,9 +51,9 @@ bool TextColorTests::ClassSetup()
return true;
}
gsl::span<const COLORREF> TextColorTests::_GetTableView()
std::basic_string_view<COLORREF> TextColorTests::_GetTableView()
{
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
return std::basic_string_view<COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
}
void TextColorTests::TestDefaultColor()

View File

@@ -9,7 +9,6 @@
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp::implementation;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
@@ -74,24 +73,24 @@ namespace TerminalAppLocalTests
const auto scheme0 = ColorScheme::FromJson(scheme0Json);
VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme0Json));
VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme1Json));
VERIFY_IS_TRUE(scheme0->ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme0->ShouldBeLayered(scheme3Json));
VERIFY_IS_TRUE(scheme0.ShouldBeLayered(scheme0Json));
VERIFY_IS_FALSE(scheme0.ShouldBeLayered(scheme1Json));
VERIFY_IS_TRUE(scheme0.ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme0.ShouldBeLayered(scheme3Json));
const auto scheme1 = ColorScheme::FromJson(scheme1Json);
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme0Json));
VERIFY_IS_TRUE(scheme1->ShouldBeLayered(scheme1Json));
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme1->ShouldBeLayered(scheme3Json));
VERIFY_IS_FALSE(scheme1.ShouldBeLayered(scheme0Json));
VERIFY_IS_TRUE(scheme1.ShouldBeLayered(scheme1Json));
VERIFY_IS_FALSE(scheme1.ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme1.ShouldBeLayered(scheme3Json));
const auto scheme3 = ColorScheme::FromJson(scheme3Json);
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme0Json));
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme1Json));
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme3->ShouldBeLayered(scheme3Json));
VERIFY_IS_FALSE(scheme3.ShouldBeLayered(scheme0Json));
VERIFY_IS_FALSE(scheme3.ShouldBeLayered(scheme1Json));
VERIFY_IS_FALSE(scheme3.ShouldBeLayered(scheme2Json));
VERIFY_IS_FALSE(scheme3.ShouldBeLayered(scheme3Json));
}
void ColorSchemeTests::LayerColorSchemeProperties()
@@ -131,38 +130,38 @@ namespace TerminalAppLocalTests
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
auto scheme0 = ColorScheme::FromJson(scheme0Json);
VERIFY_ARE_EQUAL(L"scheme0", scheme0->_schemeName);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 0), scheme0->_selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 1), scheme0->_cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0->_table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 1), scheme0->_table[XTERM_BLUE_ATTR]);
VERIFY_ARE_EQUAL(L"scheme0", scheme0._schemeName);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 1), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 1, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 1), scheme0._table[XTERM_BLUE_ATTR]);
Log::Comment(NoThrowString().Format(
L"Layering scheme1 on top of scheme0"));
scheme0->LayerJson(scheme1Json);
scheme0.LayerJson(scheme1Json);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0->_defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 0), scheme0->_selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 4, 0, 4), scheme0->_cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 2, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0->_table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0->_table[XTERM_BLUE_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 4, 0, 4), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 2, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 1, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0._table[XTERM_BLUE_ATTR]);
Log::Comment(NoThrowString().Format(
L"Layering scheme2Json on top of (scheme0+scheme1)"));
scheme0->LayerJson(scheme2Json);
scheme0.LayerJson(scheme2Json);
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 0), scheme0->_selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 6, 0, 6), scheme0->_cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 3, 0, 0), scheme0->_table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 3, 0), scheme0->_table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0->_table[XTERM_BLUE_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 0), scheme0._selectionBackground);
VERIFY_ARE_EQUAL(ARGB(0, 6, 0, 6), scheme0._cursorColor);
VERIFY_ARE_EQUAL(ARGB(0, 3, 0, 0), scheme0._table[XTERM_RED_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 3, 0), scheme0._table[XTERM_GREEN_ATTR]);
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 2), scheme0._table[XTERM_BLUE_ATTR]);
}
void ColorSchemeTests::LayerColorSchemesOnArray()
@@ -193,102 +192,94 @@ namespace TerminalAppLocalTests
const auto scheme2Json = VerifyParseSucceeded(scheme2String);
const auto scheme3Json = VerifyParseSucceeded(scheme3String);
auto settings = winrt::make_self<winrt::TerminalApp::implementation::CascadiaSettings>();
CascadiaSettings settings;
VERIFY_ARE_EQUAL(0u, settings->_globals->ColorSchemes().Size());
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(0u, settings._globals.GetColorSchemes().size());
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
settings->_LayerOrCreateColorScheme(scheme0Json);
settings._LayerOrCreateColorScheme(scheme0Json);
{
for (auto kv : settings->_globals->ColorSchemes())
for (auto& kv : settings._globals._colorSchemes)
{
Log::Comment(NoThrowString().Format(
L"kv:%s->%s", kv.Key().data(), kv.Value().Name().data()));
L"kv:%s->%s", kv.first.data(), kv.second.GetName().data()));
}
VERIFY_ARE_EQUAL(1u, settings->_globals->ColorSchemes().Size());
VERIFY_ARE_EQUAL(1u, settings._globals.GetColorSchemes().size());
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_defaultBackground);
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
}
settings->_LayerOrCreateColorScheme(scheme1Json);
settings._LayerOrCreateColorScheme(scheme1Json);
{
VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size());
VERIFY_ARE_EQUAL(2u, settings._globals.GetColorSchemes().size());
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0->_defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_defaultBackground);
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
}
settings->_LayerOrCreateColorScheme(scheme2Json);
settings._LayerOrCreateColorScheme(scheme2Json);
{
VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size());
VERIFY_ARE_EQUAL(2u, settings._globals.GetColorSchemes().size());
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_defaultBackground);
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
}
settings->_LayerOrCreateColorScheme(scheme3Json);
settings._LayerOrCreateColorScheme(scheme3Json);
{
VERIFY_ARE_EQUAL(3u, settings->_globals->ColorSchemes().Size());
VERIFY_ARE_EQUAL(3u, settings._globals.GetColorSchemes().size());
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme0"));
auto scheme0Proj = settings->_globals->ColorSchemes().Lookup(L"scheme0");
auto scheme0 = winrt::get_self<ColorScheme>(scheme0Proj);
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L"scheme1"));
auto scheme1Proj = settings->_globals->ColorSchemes().Lookup(L"scheme1");
auto scheme1 = winrt::get_self<ColorScheme>(scheme1Proj);
VERIFY_IS_TRUE(settings->_globals->ColorSchemes().HasKey(L""));
auto scheme2Proj = settings->_globals->ColorSchemes().Lookup(L"");
auto scheme2 = winrt::get_self<ColorScheme>(scheme2Proj);
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme0") != settings._globals._colorSchemes.end());
auto scheme0 = settings._globals._colorSchemes.find(L"scheme0")->second;
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"scheme1") != settings._globals._colorSchemes.end());
auto scheme1 = settings._globals._colorSchemes.find(L"scheme1")->second;
VERIFY_IS_TRUE(settings._globals._colorSchemes.find(L"") != settings._globals._colorSchemes.end());
auto scheme2 = settings._globals._colorSchemes.find(L"")->second;
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings->_FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0->_defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1->_defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 6, 6, 6), scheme2->_defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2->_defaultBackground);
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingColorScheme(scheme2Json));
VERIFY_IS_NULL(settings._FindMatchingColorScheme(scheme3Json));
VERIFY_ARE_EQUAL(ARGB(0, 4, 4, 4), scheme0._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 5, 5, 5), scheme0._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), scheme1._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), scheme1._defaultBackground);
VERIFY_ARE_EQUAL(ARGB(0, 6, 6, 6), scheme2._defaultForeground);
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2._defaultBackground);
}
}
}

View File

@@ -10,8 +10,7 @@
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Microsoft::Terminal::Settings;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
@@ -62,25 +61,25 @@ namespace TerminalAppLocalTests
const auto commands1Json = VerifyParseSucceeded(commands1String);
const auto commands2Json = VerifyParseSucceeded(commands2String);
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(1u, commands.Size());
VERIFY_ARE_EQUAL(1u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(2u, commands.Size());
VERIFY_ARE_EQUAL(2u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
}
VERIFY_ARE_EQUAL(4u, commands.Size());
VERIFY_ARE_EQUAL(4u, commands.size());
}
void CommandTests::LayerCommand()
@@ -96,13 +95,13 @@ namespace TerminalAppLocalTests
const auto commands2Json = VerifyParseSucceeded(commands2String);
const auto commands3Json = VerifyParseSucceeded(commands3String);
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
auto command = commands.Lookup(L"action0");
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
@@ -112,8 +111,8 @@ namespace TerminalAppLocalTests
{
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
auto command = commands.Lookup(L"action0");
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action());
@@ -122,8 +121,8 @@ namespace TerminalAppLocalTests
{
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
auto command = commands.Lookup(L"action0");
VERIFY_ARE_EQUAL(1u, commands.size());
auto command = commands.at(L"action0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
@@ -134,7 +133,7 @@ namespace TerminalAppLocalTests
// This last command should "unbind" the action.
auto warnings = implementation::Command::LayerJson(commands, commands3Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(0u, commands.Size());
VERIFY_ARE_EQUAL(0u, commands.size());
}
}
@@ -148,20 +147,22 @@ namespace TerminalAppLocalTests
{ "name": "command0", "command": { "action": "splitPane", "split": null } },
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
{ "name": "command3", "command": { "action": "splitPane", "split": "none" } },
{ "name": "command4", "command": { "action": "splitPane" } },
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } }
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
{ "name": "command6", "command": { "action": "splitPane", "split": "foo" } }
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(5u, commands.Size());
VERIFY_ARE_EQUAL(7u, commands.size());
{
auto command = commands.Lookup(L"command0");
auto command = commands.at(L"command0");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
@@ -171,7 +172,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.Lookup(L"command1");
auto command = commands.at(L"command1");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
@@ -181,7 +182,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
}
{
auto command = commands.Lookup(L"command2");
auto command = commands.at(L"command2");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
@@ -191,7 +192,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
}
{
auto command = commands.Lookup(L"command4");
auto command = commands.at(L"command3");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
@@ -201,7 +202,27 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.Lookup(L"command5");
auto command = commands.at(L"command4");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command5");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command6");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
@@ -218,17 +239,17 @@ namespace TerminalAppLocalTests
const std::string commands0String{ R"([ { "name": { "key": "DuplicateTabCommandKey"}, "command": "copy" } ])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
{
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
VERIFY_ARE_EQUAL(1u, commands.size());
// NOTE: We're relying on DuplicateTabCommandKey being defined as
// "Duplicate Tab" here. If that string changes in our resources,
// this test will break.
auto command = commands.Lookup(L"Duplicate tab");
auto command = commands.at(L"Duplicate tab");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
@@ -239,14 +260,6 @@ namespace TerminalAppLocalTests
void CommandTests::TestAutogeneratedName()
{
// Tests run in Helix can't report Skipped until GH#7286 is resolved.
// Set ignore flag to make Helix run completely overlook it.
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Ignore", L"True")
END_TEST_METHOD_PROPERTIES()
// This test to be corrected as a part of GH#7281
// This test ensures that we'll correctly create commands for actions
// that don't have given names, pursuant to the spec in GH#6532.
@@ -266,18 +279,18 @@ namespace TerminalAppLocalTests
const auto commands0Json = VerifyParseSucceeded(commands0String);
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
// There are only 3 commands here: all of the `"none"`, `"auto"`,
// `"foo"`, `null`, and <no args> bindings all generate the same action,
// which will generate just a single name for all of them.
VERIFY_ARE_EQUAL(3u, commands.Size());
VERIFY_ARE_EQUAL(3u, commands.size());
{
auto command = commands.Lookup(L"Split pane");
auto command = commands.at(L"Split pane");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
@@ -287,7 +300,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.Lookup(L"Split pane, split: vertical");
auto command = commands.at(L"Split pane, direction: vertical");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
@@ -297,7 +310,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
}
{
auto command = commands.Lookup(L"Split pane, split: horizontal");
auto command = commands.at(L"Split pane, direction: horizontal");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
@@ -316,14 +329,14 @@ namespace TerminalAppLocalTests
const auto commands0Json = VerifyParseSucceeded(commands0String);
IMap<winrt::hstring, winrt::TerminalApp::Command> commands = winrt::single_threaded_map<winrt::hstring, winrt::TerminalApp::Command>();
VERIFY_ARE_EQUAL(0u, commands.Size());
std::unordered_map<winrt::hstring, Command> commands;
VERIFY_ARE_EQUAL(0u, commands.size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(1u, commands.Size());
VERIFY_ARE_EQUAL(1u, commands.size());
{
auto command = commands.Lookup(L"Split pane");
auto command = commands.at(L"Split pane");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());

View File

@@ -4,9 +4,7 @@
#include "pch.h"
#include <WexTestClass.h>
#include "../TerminalApp/TerminalPage.h"
#include "../TerminalApp/AppCommandlineArgs.h"
#include "../TerminalApp/ActionArgs.h"
using namespace WEX::Logging;
using namespace WEX::Common;
@@ -54,10 +52,6 @@ namespace TerminalAppLocalTests
TEST_METHOD(CheckTypos);
TEST_METHOD(TestSimpleExecuteCommandlineAction);
TEST_METHOD(TestMultipleCommandExecuteCommandlineAction);
TEST_METHOD(TestInvalidExecuteCommandlineAction);
private:
void _buildCommandlinesHelper(AppCommandlineArgs& appArgs,
const size_t expectedSubcommands,
@@ -1073,66 +1067,4 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"C:\\", myArgs.TerminalArgs().StartingDirectory());
}
}
void CommandlineTest::TestSimpleExecuteCommandlineAction()
{
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
args->Commandline(L"new-tab");
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
VERIFY_ARE_EQUAL(1u, actions.size());
auto actionAndArgs = actions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
}
void CommandlineTest::TestMultipleCommandExecuteCommandlineAction()
{
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
args->Commandline(L"new-tab ; split-pane");
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
VERIFY_ARE_EQUAL(2u, actions.size());
{
auto actionAndArgs = actions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
}
{
auto actionAndArgs = actions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
}
}
void CommandlineTest::TestInvalidExecuteCommandlineAction()
{
auto args = winrt::make_self<implementation::ExecuteCommandlineArgs>();
// -H and -V cannot be combined.
args->Commandline(L"split-pane -H -V");
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
VERIFY_ARE_EQUAL(0u, actions.size());
}
}

View File

@@ -5,14 +5,13 @@
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/CascadiaSettings.h"
#include "../KeyMapping.h"
#include "JsonTestClass.h"
#include "TestUtils.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Microsoft::Terminal::Settings;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
@@ -67,18 +66,18 @@ namespace TerminalAppLocalTests
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto keymap = winrt::make_self<winrt::TerminalApp::implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
auto appKeyBindings = winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(2u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(4u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(4u, appKeyBindings->_keyShortcuts.size());
}
void KeyBindingsTests::LayerKeybindings()
@@ -91,18 +90,18 @@ namespace TerminalAppLocalTests
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto keymap = winrt::make_self<winrt::TerminalApp::implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
auto appKeyBindings = winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(2u, appKeyBindings->_keyShortcuts.size());
}
void KeyBindingsTests::UnbindKeybindings()
@@ -121,52 +120,52 @@ namespace TerminalAppLocalTests
const auto bindings4Json = VerifyParseSucceeded(bindings4String);
const auto bindings5Json = VerifyParseSucceeded(bindings5String);
auto keymap = winrt::make_self<winrt::TerminalApp::implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
auto appKeyBindings = winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
keymap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using `\"unbound\"` to unbind the key"));
keymap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using `null` to unbind the key"));
// First add back a good binding
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
// Then try layering in the bad setting
keymap->LayerJson(bindings3Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings3Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using an unrecognized command to unbind the key"));
// First add back a good binding
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
// Then try layering in the bad setting
keymap->LayerJson(bindings4Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings4Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key using a straight up invalid value to unbind the key"));
// First add back a good binding
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
// Then try layering in the bad setting
keymap->LayerJson(bindings5Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings5Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
Log::Comment(NoThrowString().Format(
L"Try unbinding a key that wasn't bound at all"));
keymap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
}
void KeyBindingsTests::TestArbitraryArgs()
@@ -190,17 +189,17 @@ namespace TerminalAppLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto keymap = winrt::make_self<winrt::TerminalApp::implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(10u, keymap->_keyShortcuts.size());
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(10u, appKeyBindings->_keyShortcuts.size());
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` without args parses as Copy(SingleLine=false)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
@@ -211,7 +210,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
@@ -222,7 +221,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ false, true, true, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
@@ -233,7 +232,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` without args parses as NewTab(Index=null)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('T') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -245,7 +244,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` parses args correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('T') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -259,7 +258,7 @@ namespace TerminalAppLocalTests
L"Verify that `newTab` with an index greater than the legacy "
L"args afforded parses correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('Y') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -273,7 +272,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `copy` ignores args it doesn't understand"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -285,7 +284,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `copy` null as it's `args` parses as the default option"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -297,7 +296,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `adjustFontSize` with a positive delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -309,7 +308,7 @@ namespace TerminalAppLocalTests
Log::Comment(NoThrowString().Format(
L"Verify that `adjustFontSize` with a negative delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -324,21 +323,23 @@ namespace TerminalAppLocalTests
{ "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": null } },
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal" } },
{ "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "none" } },
{ "keys": ["ctrl+g"], "command": { "action": "splitPane" } },
{ "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } }
{ "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } },
{ "keys": ["ctrl+i"], "command": { "action": "splitPane", "split": "foo" } }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto keymap = winrt::make_self<winrt::TerminalApp::implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(5u, keymap->_keyShortcuts.size());
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(7u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -347,7 +348,7 @@ namespace TerminalAppLocalTests
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -356,16 +357,25 @@ namespace TerminalAppLocalTests
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -374,7 +384,16 @@ namespace TerminalAppLocalTests
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -388,20 +407,21 @@ namespace TerminalAppLocalTests
const std::string bindings0String{ R"([
{ "keys": ["ctrl+c"], "command": { "action": "setTabColor", "color": null } },
{ "keys": ["ctrl+d"], "command": { "action": "setTabColor", "color": "#123456" } },
{ "keys": ["ctrl+e"], "command": { "action": "setTabColor", "color": "thisStringObviouslyWontWork" } },
{ "keys": ["ctrl+f"], "command": "setTabColor" },
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto keymap = winrt::make_self<winrt::TerminalApp::implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, keymap->_keyShortcuts.size());
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(4u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -410,7 +430,7 @@ namespace TerminalAppLocalTests
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -419,9 +439,18 @@ namespace TerminalAppLocalTests
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
VERIFY_ARE_EQUAL(static_cast<uint32_t>(til::color(0x563412)), realArgs.TabColor().Value());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
@@ -438,15 +467,15 @@ namespace TerminalAppLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto keymap = winrt::make_self<winrt::TerminalApp::implementation::KeyMapping>();
VERIFY_IS_NOT_NULL(keymap);
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
keymap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*keymap, kc);
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value

View File

@@ -9,7 +9,6 @@
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
@@ -68,27 +67,27 @@ namespace TerminalAppLocalTests
const auto profile2Json = VerifyParseSucceeded(profile2String);
const auto profile3Json = VerifyParseSucceeded(profile3String);
const auto profile0 = implementation::Profile::FromJson(profile0Json);
const auto profile0 = Profile::FromJson(profile0Json);
VERIFY_IS_FALSE(profile0->ShouldBeLayered(profile1Json));
VERIFY_IS_TRUE(profile0->ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile0->ShouldBeLayered(profile3Json));
VERIFY_IS_FALSE(profile0.ShouldBeLayered(profile1Json));
VERIFY_IS_TRUE(profile0.ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile0.ShouldBeLayered(profile3Json));
const auto profile1 = implementation::Profile::FromJson(profile1Json);
const auto profile1 = Profile::FromJson(profile1Json);
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile0Json));
VERIFY_IS_FALSE(profile1.ShouldBeLayered(profile0Json));
// A profile _can_ be layered with itself, though what's the point?
VERIFY_IS_TRUE(profile1->ShouldBeLayered(profile1Json));
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile1->ShouldBeLayered(profile3Json));
VERIFY_IS_TRUE(profile1.ShouldBeLayered(profile1Json));
VERIFY_IS_FALSE(profile1.ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile1.ShouldBeLayered(profile3Json));
const auto profile3 = implementation::Profile::FromJson(profile3Json);
const auto profile3 = Profile::FromJson(profile3Json);
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile0Json));
VERIFY_IS_FALSE(profile3.ShouldBeLayered(profile0Json));
// A profile _can_ be layered with itself, though what's the point?
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile1Json));
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile3->ShouldBeLayered(profile3Json));
VERIFY_IS_FALSE(profile3.ShouldBeLayered(profile1Json));
VERIFY_IS_FALSE(profile3.ShouldBeLayered(profile2Json));
VERIFY_IS_FALSE(profile3.ShouldBeLayered(profile3Json));
}
void ProfileTests::LayerProfileProperties()
@@ -117,55 +116,55 @@ namespace TerminalAppLocalTests
const auto profile1Json = VerifyParseSucceeded(profile1String);
const auto profile2Json = VerifyParseSucceeded(profile2String);
auto profile0 = implementation::Profile::FromJson(profile0Json);
VERIFY_IS_NOT_NULL(profile0->Foreground());
VERIFY_ARE_EQUAL(til::color(0, 0, 0), til::color{ profile0->Foreground().Value() });
auto profile0 = Profile::FromJson(profile0Json);
VERIFY_IS_TRUE(profile0._defaultForeground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 0, 0, 0), profile0._defaultForeground.value());
VERIFY_IS_NOT_NULL(profile0->Background());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
VERIFY_IS_TRUE(profile0._defaultBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._defaultBackground.value());
VERIFY_IS_NOT_NULL(profile0->SelectionBackground());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->SelectionBackground().Value() });
VERIFY_IS_TRUE(profile0._selectionBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._selectionBackground.value());
VERIFY_ARE_EQUAL(L"profile0", profile0->Name());
VERIFY_ARE_EQUAL(L"profile0", profile0._name);
VERIFY_IS_TRUE(profile0->StartingDirectory().empty());
VERIFY_IS_FALSE(profile0._startingDirectory.has_value());
Log::Comment(NoThrowString().Format(
L"Layering profile1 on top of profile0"));
profile0->LayerJson(profile1Json);
profile0.LayerJson(profile1Json);
VERIFY_IS_NOT_NULL(profile0->Foreground());
VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile0->Foreground().Value() });
VERIFY_IS_TRUE(profile0._defaultForeground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), profile0._defaultForeground.value());
VERIFY_IS_NOT_NULL(profile0->Background());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
VERIFY_IS_TRUE(profile0._defaultBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._defaultBackground.value());
VERIFY_IS_NOT_NULL(profile0->Background());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
VERIFY_IS_TRUE(profile0._selectionBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._selectionBackground.value());
VERIFY_ARE_EQUAL(L"profile1", profile0->Name());
VERIFY_ARE_EQUAL(L"profile1", profile0._name);
VERIFY_IS_FALSE(profile0->StartingDirectory().empty());
VERIFY_ARE_EQUAL(L"C:/", profile0->StartingDirectory());
VERIFY_IS_TRUE(profile0._startingDirectory.has_value());
VERIFY_ARE_EQUAL(L"C:/", profile0._startingDirectory.value());
Log::Comment(NoThrowString().Format(
L"Layering profile2 on top of (profile0+profile1)"));
profile0->LayerJson(profile2Json);
profile0.LayerJson(profile2Json);
VERIFY_IS_NOT_NULL(profile0->Foreground());
VERIFY_ARE_EQUAL(til::color(3, 3, 3), til::color{ profile0->Foreground().Value() });
VERIFY_IS_TRUE(profile0._defaultForeground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 3, 3, 3), profile0._defaultForeground.value());
VERIFY_IS_NOT_NULL(profile0->Background());
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
VERIFY_IS_TRUE(profile0._defaultBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 1, 1, 1), profile0._defaultBackground.value());
VERIFY_IS_NOT_NULL(profile0->SelectionBackground());
VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile0->SelectionBackground().Value() });
VERIFY_IS_TRUE(profile0._selectionBackground.has_value());
VERIFY_ARE_EQUAL(ARGB(0, 2, 2, 2), profile0._selectionBackground.value());
VERIFY_ARE_EQUAL(L"profile2", profile0->Name());
VERIFY_ARE_EQUAL(L"profile2", profile0._name);
VERIFY_IS_FALSE(profile0->StartingDirectory().empty());
VERIFY_ARE_EQUAL(L"C:/", profile0->StartingDirectory());
VERIFY_IS_TRUE(profile0._startingDirectory.has_value());
VERIFY_ARE_EQUAL(L"C:/", profile0._startingDirectory.value());
}
void ProfileTests::LayerProfileIcon()
@@ -195,33 +194,33 @@ namespace TerminalAppLocalTests
const auto profile2Json = VerifyParseSucceeded(profile2String);
const auto profile3Json = VerifyParseSucceeded(profile3String);
auto profile0 = implementation::Profile::FromJson(profile0Json);
VERIFY_IS_FALSE(profile0->IconPath().empty());
VERIFY_ARE_EQUAL(L"not-null.png", profile0->IconPath());
auto profile0 = Profile::FromJson(profile0Json);
VERIFY_IS_TRUE(profile0._icon.has_value());
VERIFY_ARE_EQUAL(L"not-null.png", profile0._icon.value());
Log::Comment(NoThrowString().Format(
L"Verify that layering an object the key set to null will clear the key"));
profile0->LayerJson(profile1Json);
VERIFY_IS_TRUE(profile0->IconPath().empty());
profile0.LayerJson(profile1Json);
VERIFY_IS_FALSE(profile0._icon.has_value());
profile0->LayerJson(profile2Json);
VERIFY_IS_TRUE(profile0->IconPath().empty());
profile0.LayerJson(profile2Json);
VERIFY_IS_FALSE(profile0._icon.has_value());
profile0->LayerJson(profile3Json);
VERIFY_IS_FALSE(profile0->IconPath().empty());
VERIFY_ARE_EQUAL(L"another-real.png", profile0->IconPath());
profile0.LayerJson(profile3Json);
VERIFY_IS_TRUE(profile0._icon.has_value());
VERIFY_ARE_EQUAL(L"another-real.png", profile0._icon.value());
Log::Comment(NoThrowString().Format(
L"Verify that layering an object _without_ the key will not clear the key"));
profile0->LayerJson(profile2Json);
VERIFY_IS_FALSE(profile0->IconPath().empty());
VERIFY_ARE_EQUAL(L"another-real.png", profile0->IconPath());
profile0.LayerJson(profile2Json);
VERIFY_IS_TRUE(profile0._icon.has_value());
VERIFY_ARE_EQUAL(L"another-real.png", profile0._icon.value());
auto profile1 = implementation::Profile::FromJson(profile1Json);
VERIFY_IS_TRUE(profile1->IconPath().empty());
profile1->LayerJson(profile3Json);
VERIFY_IS_FALSE(profile1->IconPath().empty());
VERIFY_ARE_EQUAL(L"another-real.png", profile1->IconPath());
auto profile1 = Profile::FromJson(profile1Json);
VERIFY_IS_FALSE(profile1._icon.has_value());
profile1.LayerJson(profile3Json);
VERIFY_IS_TRUE(profile1._icon.has_value());
VERIFY_ARE_EQUAL(L"another-real.png", profile1._icon.value());
}
void ProfileTests::LayerProfilesOnArray()
@@ -253,57 +252,57 @@ namespace TerminalAppLocalTests
const auto profile3Json = VerifyParseSucceeded(profile3String);
const auto profile4Json = VerifyParseSucceeded(profile4String);
auto settings = winrt::make_self<winrt::TerminalApp::implementation::CascadiaSettings>();
CascadiaSettings settings;
VERIFY_ARE_EQUAL(0u, settings->_profiles.Size());
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(0u, settings._profiles.size());
VERIFY_IS_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile4Json));
settings->_LayerOrCreateProfile(profile0Json);
VERIFY_ARE_EQUAL(1u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
settings._LayerOrCreateProfile(profile0Json);
VERIFY_ARE_EQUAL(1u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
settings->_LayerOrCreateProfile(profile1Json);
VERIFY_ARE_EQUAL(2u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
settings._LayerOrCreateProfile(profile1Json);
VERIFY_ARE_EQUAL(2u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
settings->_LayerOrCreateProfile(profile2Json);
VERIFY_ARE_EQUAL(3u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile0", settings->_profiles.GetAt(0).Name());
settings._LayerOrCreateProfile(profile2Json);
VERIFY_ARE_EQUAL(3u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile0", settings._profiles.at(0)._name);
settings->_LayerOrCreateProfile(profile3Json);
VERIFY_ARE_EQUAL(3u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile3", settings->_profiles.GetAt(0).Name());
settings._LayerOrCreateProfile(profile3Json);
VERIFY_ARE_EQUAL(3u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile3", settings._profiles.at(0)._name);
settings->_LayerOrCreateProfile(profile4Json);
VERIFY_ARE_EQUAL(3u, settings->_profiles.Size());
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings->_FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile4", settings->_profiles.GetAt(0).Name());
settings._LayerOrCreateProfile(profile4Json);
VERIFY_ARE_EQUAL(3u, settings._profiles.size());
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile0Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile1Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile2Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile3Json));
VERIFY_IS_NOT_NULL(settings._FindMatchingProfile(profile4Json));
VERIFY_ARE_EQUAL(L"profile4", settings._profiles.at(0)._name);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -51,8 +51,7 @@ namespace TerminalAppLocalTests
TEST_METHOD(TryCreateSettingsType);
TEST_METHOD(TryCreateConnectionType);
TEST_METHOD(TryCreateXamlObjects);
TEST_METHOD(TryInitializePage);
TEST_METHOD(TryCreateTab);
TEST_METHOD(CreateSimpleTerminalXamlType);
TEST_METHOD(CreateTerminalMuxXamlType);
@@ -62,25 +61,15 @@ namespace TerminalAppLocalTests
TEST_METHOD(TryDuplicateBadTab);
TEST_METHOD(TryDuplicateBadPane);
TEST_METHOD(TryZoomPane);
TEST_METHOD(MoveFocusFromZoomedPane);
TEST_METHOD(CloseZoomedPane);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
TEST_METHOD_CLEANUP(MethodCleanup)
{
return true;
}
private:
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
winrt::com_ptr<winrt::TerminalApp::implementation::CascadiaSettings>& initialSettings);
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> _commonSetup();
std::shared_ptr<CascadiaSettings> initialSettings);
};
void TabTests::EnsureTestsActivate()
@@ -95,7 +84,7 @@ namespace TerminalAppLocalTests
{
// Verify we can create a WinRT type we authored
// Just creating it is enough to know that everything is working.
TerminalSettings settings;
winrt::Microsoft::Terminal::Settings::TerminalSettings settings;
VERIFY_IS_NOT_NULL(settings);
auto oldFontSize = settings.FontSize();
settings.FontSize(oldFontSize + 5);
@@ -136,6 +125,35 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}
void TabTests::TryCreateTab()
{
// If you leave the Tab ptr owned by the RunOnUIThread lambda, it
// will crash when the test tears down. Not totally clear why, but make
// sure it's owned outside the lambda
winrt::com_ptr<winrt::TerminalApp::implementation::Tab> newTab{ nullptr };
auto result = RunOnUIThread([&newTab]() {
// Try creating all of:
// 1. one of our pure c++ types (Profile)
// 2. one of our c++winrt types (TerminalSettings, EchoConnection)
// 3. one of our types that uses MUX/Xaml (TermControl).
// 4. one of our types that uses MUX/Xaml in this dll (Tab).
// Just creating all of them is enough to know that everything is working.
const auto profileGuid{ Utils::CreateGuid() };
winrt::Microsoft::Terminal::Settings::TerminalSettings settings{};
VERIFY_IS_NOT_NULL(settings);
winrt::Microsoft::Terminal::TerminalConnection::EchoConnection conn{};
VERIFY_IS_NOT_NULL(conn);
winrt::Microsoft::Terminal::TerminalControl::TermControl term{ settings, conn };
VERIFY_IS_NOT_NULL(term);
newTab = winrt::make_self<winrt::TerminalApp::implementation::Tab>(profileGuid, term);
VERIFY_IS_NOT_NULL(newTab);
});
VERIFY_SUCCEEDED(result);
}
void TabTests::CreateSimpleTerminalXamlType()
{
winrt::com_ptr<winrt::TerminalApp::implementation::MinMaxCloseControl> mmcc{ nullptr };
@@ -195,7 +213,7 @@ namespace TerminalAppLocalTests
// Return Value:
// - <none>
void TabTests::_initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
winrt::com_ptr<winrt::TerminalApp::implementation::CascadiaSettings>& initialSettings)
std::shared_ptr<CascadiaSettings> initialSettings)
{
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
@@ -211,7 +229,7 @@ namespace TerminalAppLocalTests
auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() {
projectedPage = winrt::TerminalApp::TerminalPage();
page.copy_from(winrt::get_self<winrt::TerminalApp::implementation::TerminalPage>(projectedPage));
page->_settings = *initialSettings;
page->_settings = initialSettings;
});
VERIFY_SUCCEEDED(result);
@@ -258,456 +276,229 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}
void TabTests::TryInitializePage()
{
// This is a very simple test to prove we can create settings and a
// TerminalPage and not only create them successfully, but also create a
// tab using those settings successfully.
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
VerifyParseSucceeded(settingsJson0);
auto settings0 = winrt::make_self<implementation::CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings0);
settings0->_ParseJsonString(settingsJson0, false);
settings0->LayerJson(settings0->_userSettings);
settings0->_ValidateSettings();
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
}
void TabTests::TryDuplicateBadTab()
{
// * Create a tab with a profile with GUID 1
// * Reload the settings so that GUID 1 is no longer in the list of profiles
// * Try calling _DuplicateTabViewItem on tab 1
// * No new tab should be created (and more importantly, the app should not crash)
//
// Created to test GH#2455
Log::Comment(L"This test regressed recently - it is temporarily disabled while GH#5169 is investigated");
Log::Result(WEX::Logging::TestResults::Skipped);
return;
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// // * Create a tab with a profile with GUID 1
// // * Reload the settings so that GUID 1 is no longer in the list of profiles
// // * Try calling _DuplicateTabViewItem on tab 1
// // * No new tab should be created (and more importantly, the app should not crash)
// //
// // Created to test GH#2455
const std::string settingsJson1{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// const std::string settingsJson0{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile0",
// "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "historySize": 1
// },
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
VerifyParseSucceeded(settingsJson0);
auto settings0 = winrt::make_self<implementation::CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings0);
settings0->_ParseJsonString(settingsJson0, false);
settings0->LayerJson(settings0->_userSettings);
settings0->_ValidateSettings();
// const std::string settingsJson1{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
VerifyParseSucceeded(settingsJson1);
auto settings1 = winrt::make_self<implementation::CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings1);
settings1->_ParseJsonString(settingsJson1, false);
settings1->LayerJson(settings1->_userSettings);
settings1->_ValidateSettings();
// VerifyParseSucceeded(settingsJson0);
// auto settings0 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings0);
// settings0->_ParseJsonString(settingsJson0, false);
// settings0->LayerJson(settings0->_userSettings);
// settings0->_ValidateSettings();
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
// VerifyParseSucceeded(settingsJson1);
// auto settings1 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings1);
// settings1->_ParseJsonString(settingsJson1, false);
// settings1->LayerJson(settings1->_userSettings);
// settings1->_ValidateSettings();
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
// const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
// const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
// const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// // This is super wacky, but we can't just initialize the
// // com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// // the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// // during TerminalPage::Create() below.
// //
// // Instead, create the winrt object, then get a com_ptr to the
// // implementation _from_ the winrt object. This seems to work, even if
// // it's weird.
// winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
// _initializeTerminalPage(page, settings0);
Log::Comment(L"Duplicate the first tab");
result = RunOnUIThread([&page]() {
page->_DuplicateTabViewItem();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// auto result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(
L"Change the settings of the TerminalPage so the first profile is "
L"no longer in the list of profiles"));
result = RunOnUIThread([&page, settings1]() {
page->_settings = *settings1;
});
VERIFY_SUCCEEDED(result);
// Log::Comment(L"Duplicate the first tab");
// result = RunOnUIThread([&page]() {
// page->_DuplicateTabViewItem();
// VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(L"Duplicate the tab, and don't crash");
result = RunOnUIThread([&page]() {
page->_DuplicateTabViewItem();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
});
VERIFY_SUCCEEDED(result);
// Log::Comment(NoThrowString().Format(
// L"Change the settings of the TerminalPage so the first profile is "
// L"no longer in the list of profiles"));
// result = RunOnUIThread([&page, settings1]() {
// page->_settings = settings1;
// });
// VERIFY_SUCCEEDED(result);
// Log::Comment(L"Duplicate the tab, and don't crash");
// result = RunOnUIThread([&page]() {
// page->_DuplicateTabViewItem();
// VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
// });
// VERIFY_SUCCEEDED(result);
}
void TabTests::TryDuplicateBadPane()
{
// * Create a tab with a profile with GUID 1
// * Reload the settings so that GUID 1 is no longer in the list of profiles
// * Try calling _SplitPane(Duplicate) on tab 1
// * No new pane should be created (and more importantly, the app should not crash)
//
// Created to test GH#2455
Log::Comment(L"This test regressed recently - it is temporarily disabled while GH#5169 is investigated");
Log::Result(WEX::Logging::TestResults::Skipped);
return;
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// // * Create a tab with a profile with GUID 1
// // * Reload the settings so that GUID 1 is no longer in the list of profiles
// // * Try calling _SplitPane(Duplicate) on tab 1
// // * No new pane should be created (and more importantly, the app should not crash)
// //
// // Created to test GH#2455
const std::string settingsJson1{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
// const std::string settingsJson0{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile0",
// "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "historySize": 1
// },
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
VerifyParseSucceeded(settingsJson0);
auto settings0 = winrt::make_self<implementation::CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings0);
settings0->_ParseJsonString(settingsJson0, false);
settings0->LayerJson(settings0->_userSettings);
settings0->_ValidateSettings();
// const std::string settingsJson1{ R"(
// {
// "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
// "profiles": [
// {
// "name" : "profile1",
// "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
// "historySize": 2
// }
// ]
// })" };
VerifyParseSucceeded(settingsJson1);
auto settings1 = winrt::make_self<implementation::CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings1);
settings1->_ParseJsonString(settingsJson1, false);
settings1->LayerJson(settings1->_userSettings);
settings1->_ValidateSettings();
// VerifyParseSucceeded(settingsJson0);
// auto settings0 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings0);
// settings0->_ParseJsonString(settingsJson0, false);
// settings0->LayerJson(settings0->_userSettings);
// settings0->_ValidateSettings();
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
// VerifyParseSucceeded(settingsJson1);
// auto settings1 = std::make_shared<CascadiaSettings>(false);
// VERIFY_IS_NOT_NULL(settings1);
// settings1->_ParseJsonString(settingsJson1, false);
// settings1->LayerJson(settings1->_userSettings);
// settings1->_ValidateSettings();
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
// const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
// const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
// const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
// // This is super wacky, but we can't just initialize the
// // com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// // the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// // during TerminalPage::Create() below.
// //
// // Instead, create the winrt object, then get a com_ptr to the
// // implementation _from_ the winrt object. This seems to work, even if
// // it's weird.
// winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
// _initializeTerminalPage(page, settings0);
result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(1, tab->GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
// auto result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
// result = RunOnUIThread([&page]() {
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(1, tab->_GetLeafPaneCount());
// });
// VERIFY_SUCCEEDED(result);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
// Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
// result = RunOnUIThread([&page]() {
// page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
Log::Comment(NoThrowString().Format(
L"Change the settings of the TerminalPage so the first profile is "
L"no longer in the list of profiles"));
result = RunOnUIThread([&page, settings1]() {
page->_settings = *settings1;
});
VERIFY_SUCCEEDED(result);
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(2, tab->_GetLeafPaneCount());
// });
// VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
// Log::Comment(NoThrowString().Format(
// L"Change the settings of the TerminalPage so the first profile is "
// L"no longer in the list of profiles"));
// result = RunOnUIThread([&page, settings1]() {
// page->_settings = settings1;
// });
// VERIFY_SUCCEEDED(result);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2,
tab->GetLeafPaneCount(),
L"We should gracefully do nothing here - the profile no longer exists.");
});
VERIFY_SUCCEEDED(result);
// Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
// result = RunOnUIThread([&page]() {
// page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
auto cleanup = wil::scope_exit([] {
auto result = RunOnUIThread([]() {
// There's something causing us to crash north of
// TSFInputControl::NotifyEnter, or LayoutRequested. It's very
// unclear what that issue is. Since these tests don't run in
// CI, simply log a message so that the dev running these tests
// knows it's expected.
Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
});
VERIFY_SUCCEEDED(result);
});
// VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
// auto tab = page->_GetStrongTabImpl(0);
// VERIFY_ARE_EQUAL(2,
// tab->_GetLeafPaneCount(),
// L"We should gracefully do nothing here - the profile no longer exists.");
// });
// VERIFY_SUCCEEDED(result);
// auto cleanup = wil::scope_exit([] {
// auto result = RunOnUIThread([]() {
// // There's something causing us to crash north of
// // TSFInputControl::NotifyEnter, or LayoutRequested. It's very
// // unclear what that issue is. Since these tests don't run in
// // CI, simply log a message so that the dev running these tests
// // knows it's expected.
// Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
// });
// VERIFY_SUCCEEDED(result);
// });
}
// Method Description:
// - This is a helper method for setting up a TerminalPage with some common
// settings, and creating the first tab.
// Arguments:
// - <none>
// Return Value:
// - The initialized TerminalPage, ready to use.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> TabTests::_commonSetup()
{
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
VERIFY_IS_NOT_NULL(settings0);
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
return page;
}
void TabTests::TryZoomPane()
{
auto page = _commonSetup();
Log::Comment(L"Create a second pane");
auto result = RunOnUIThread([&page]() {
SplitPaneArgs args{ SplitType::Duplicate };
ActionEventArgs eventArgs{ args };
// eventArgs.Args(args);
page->_HandleSplitPane(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Zoom in on the pane");
result = RunOnUIThread([&page]() {
ActionEventArgs eventArgs{};
page->_HandleTogglePaneZoom(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_TRUE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Zoom out of the pane");
result = RunOnUIThread([&page]() {
ActionEventArgs eventArgs{};
page->_HandleTogglePaneZoom(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
}
void TabTests::MoveFocusFromZoomedPane()
{
auto page = _commonSetup();
Log::Comment(L"Create a second pane");
auto result = RunOnUIThread([&page]() {
// Set up action
SplitPaneArgs args{ SplitType::Duplicate };
ActionEventArgs eventArgs{ args };
page->_HandleSplitPane(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Zoom in on the pane");
result = RunOnUIThread([&page]() {
// Set up action
ActionEventArgs eventArgs{};
page->_HandleTogglePaneZoom(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_TRUE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Move focus. This will cause us to un-zoom.");
result = RunOnUIThread([&page]() {
// Set up action
MoveFocusArgs args{ Direction::Left };
ActionEventArgs eventArgs{ args };
page->_HandleMoveFocus(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
}
void TabTests::CloseZoomedPane()
{
auto page = _commonSetup();
Log::Comment(L"Create a second pane");
auto result = RunOnUIThread([&page]() {
// Set up action
SplitPaneArgs args{ SplitType::Duplicate };
ActionEventArgs eventArgs{ args };
page->_HandleSplitPane(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Zoom in on the pane");
result = RunOnUIThread([&page]() {
// Set up action
ActionEventArgs eventArgs{};
page->_HandleTogglePaneZoom(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_TRUE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Close Pane. This should cause us to un-zoom, and remove the second pane from the tree");
result = RunOnUIThread([&page]() {
// Set up action
ActionEventArgs eventArgs{};
page->_HandleClosePane(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
// Introduce a slight delay to let the events finish propagating
Sleep(250);
Log::Comment(L"Check to ensure there's only one pane left.");
result = RunOnUIThread([&page]() {
auto firstTab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(1, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
}
}

View File

@@ -74,14 +74,15 @@
<!-- ========================= Project References ======================== -->
<ItemGroup>
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\TerminalAppLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
<!-- If you don't reference these projects here, the
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\dll\TerminalApp.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj" />
</ItemGroup>
<!-- ========================= Globals ======================== -->
@@ -89,7 +90,7 @@
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->

View File

@@ -45,11 +45,11 @@
"taef.png" is actually in the package. taef.png will get copied to the
OutputPath when taef is run, but if this isn't set to false, then the CI
will try and make sure taef.png is in the OutputPath at build time.-->
<PropertyGroup Label="UserMacros">
<GenerateAppxPackageOnBuild>false</GenerateAppxPackageOnBuild>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<DisableSpecificWarnings>4453;%(DisableSpecificWarnings)</DisableSpecificWarnings>
@@ -94,9 +94,10 @@
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj">
<Project>{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\dll\TerminalApp.vcxproj">
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj">
<Project>{ca5cad1a-44bd-4ac7-ac72-f16e576fdd12}</Project>
</ProjectReference>
@@ -113,7 +114,7 @@
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore">
<HintPath>$(OpenConsoleDir)\packages\Taef.Redist.Wlk.10.57.200731005-develop\lib\Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore.winmd</HintPath>
<HintPath>$(OpenConsoleDir)\packages\Taef.Redist.Wlk.10.51.200127004\lib\Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<!-- This path is _relative to the .winmd_ -->

View File

@@ -19,31 +19,35 @@ public:
// - This is a helper to retrieve the ActionAndArgs from the keybindings
// for a given chord.
// Arguments:
// - keymap: The AppKeyBindings to lookup the ActionAndArgs from.
// - bindings: The AppKeyBindings to lookup the ActionAndArgs from.
// - kc: The key chord to look up the bound ActionAndArgs for.
// Return Value:
// - The ActionAndArgs bound to the given key, or nullptr if nothing is bound to it.
static const winrt::TerminalApp::ActionAndArgs GetActionAndArgs(const winrt::TerminalApp::implementation::KeyMapping& keymap,
const winrt::Microsoft::Terminal::TerminalControl::KeyChord& kc)
static const winrt::TerminalApp::ActionAndArgs GetActionAndArgs(const winrt::TerminalApp::implementation::AppKeyBindings& bindings,
const winrt::Microsoft::Terminal::Settings::KeyChord& kc)
{
std::wstring buffer{ L"" };
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Ctrl))
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Settings::KeyModifiers::Ctrl))
{
buffer += L"Ctrl+";
}
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Shift))
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Settings::KeyModifiers::Shift))
{
buffer += L"Shift+";
}
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Alt))
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Settings::KeyModifiers::Alt))
{
buffer += L"Alt+";
}
buffer += static_cast<wchar_t>(MapVirtualKeyW(kc.Vkey(), MAPVK_VK_TO_CHAR));
WEX::Logging::Log::Comment(WEX::Common::NoThrowString().Format(L"Looking for key:%s", buffer.c_str()));
const auto action = keymap.TryLookup(kc);
VERIFY_IS_NOT_NULL(action, L"Expected to find an action bound to the given KeyChord");
return action;
const auto keyIter = bindings._keyShortcuts.find(kc);
VERIFY_IS_TRUE(keyIter != bindings._keyShortcuts.end(), L"Expected to find an action bound to the given KeyChord");
if (keyIter != bindings._keyShortcuts.end())
{
return keyIter->second;
}
return nullptr;
};
};

View File

@@ -15,8 +15,6 @@ Author(s):
#pragma once
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
// This includes support libraries from the CRT, STL, WIL, and GSL
#include "LibraryIncludes.h"
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
@@ -35,6 +33,12 @@ Author(s):
#include <json.h>
#include "consoletaeftemplates.hpp"
// Common includes for most tests:
#include "../../inc/argb.h"
#include "../../inc/conattrs.hpp"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include "winrt/Windows.UI.Xaml.Markup.h"
#include <winrt/Windows.system.h>
@@ -59,12 +63,3 @@ Author(s):
#include <regex>
#include <CLI11/CLI11.hpp>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
// Common includes for most tests:
#include "../../inc/argb.h"
#include "../../inc/conattrs.hpp"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"

View File

@@ -16,35 +16,12 @@ using namespace ::Microsoft::Terminal::Core;
static LPCWSTR term_window_class = L"HwndTerminalClass";
// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx
// "If the high-order bit is 1, the key is down; otherwise, it is up."
static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
static constexpr bool _IsMouseMessage(UINT uMsg)
{
return uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK ||
uMsg == WM_MBUTTONDOWN || uMsg == WM_MBUTTONUP || uMsg == WM_MBUTTONDBLCLK ||
uMsg == WM_RBUTTONDOWN || uMsg == WM_RBUTTONUP || uMsg == WM_RBUTTONDBLCLK ||
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL;
}
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
// See microsoft/terminal#2066 for more info.
static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) noexcept
{
return false; // glyph is not wide.
}
static bool _EnsureStaticInitialization()
{
// use C++11 magic statics to make sure we only do this once.
static bool initialized = []() {
// *** THIS IS A SINGLETON ***
SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback);
return true;
}();
return initialized;
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL;
}
LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
@@ -59,31 +36,10 @@ try
if (terminal)
{
if (_IsMouseMessage(uMsg))
if (_IsMouseMessage(uMsg) && terminal->_CanSendVTMouseInput())
{
if (terminal->_CanSendVTMouseInput() && terminal->_SendMouseEvent(uMsg, wParam, lParam))
if (terminal->_SendMouseEvent(uMsg, wParam, lParam))
{
// GH#6401: Capturing the mouse ensures that we get drag/release events
// even if the user moves outside the window.
// _SendMouseEvent returns false if the terminal's not in VT mode, so we'll
// fall through to release the capture.
switch (uMsg)
{
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
SetCapture(hwnd);
break;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
ReleaseCapture();
break;
default:
break;
}
// Suppress all mouse events that made it into the terminal.
return 0;
}
}
@@ -101,10 +57,6 @@ try
return 0;
case WM_LBUTTONUP:
terminal->_singleClickTouchdownPos = std::nullopt;
[[fallthrough]];
case WM_MBUTTONUP:
case WM_RBUTTONUP:
ReleaseCapture();
break;
case WM_MOUSEMOVE:
if (WI_IsFlagSet(wParam, MK_LBUTTON))
@@ -120,7 +72,7 @@ try
{
const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false);
LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true));
TerminalClearSelection(terminal);
terminal->_terminal->ClearSelection();
}
CATCH_LOG();
}
@@ -129,13 +81,6 @@ try
terminal->_PasteTextFromClipboard();
}
return 0;
case WM_DESTROY:
// Release Terminal's hwnd so Teardown doesn't try to destroy it again
terminal->_hwnd.release();
terminal->Teardown();
return 0;
default:
break;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
@@ -169,16 +114,14 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
}
HwndTerminal::HwndTerminal(HWND parentHwnd) :
_desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8 },
_actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
_desiredFont{ L"Consolas", 0, 10, { 0, 14 }, CP_UTF8 },
_actualFont{ L"Consolas", 0, 10, { 0, 14 }, CP_UTF8, false },
_uiaProvider{ nullptr },
_uiaProviderInitialized{ false },
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
_pfnWriteCallback{ nullptr },
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
{
_EnsureStaticInitialization();
HINSTANCE hInstance = wil::GetModuleInstanceHandle();
if (RegisterTermClass(hInstance))
@@ -205,11 +148,6 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
}
}
HwndTerminal::~HwndTerminal()
{
Teardown();
}
HRESULT HwndTerminal::Initialize()
{
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
@@ -224,6 +162,9 @@ HRESULT HwndTerminal::Initialize()
RETURN_IF_FAILED(dxEngine->Enable());
_renderer->AddRenderEngine(dxEngine.get());
const auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
SetGlyphWidthFallback(pfn);
_UpdateFont(USER_DEFAULT_SCREEN_DPI);
RECT windowRect;
GetWindowRect(_hwnd.get(), &windowRect);
@@ -240,9 +181,9 @@ HRESULT HwndTerminal::Initialize()
_terminal->SetBackgroundCallback([](auto) {});
_terminal->Create(COORD{ 80, 25 }, 1000, *_renderer);
_terminal->SetDefaultBackground(RGB(12, 12, 12));
_terminal->SetDefaultForeground(RGB(204, 204, 204));
_terminal->SetWriteInputCallback([=](std::wstring& input) noexcept { _WriteTextToConnection(input); });
_terminal->SetDefaultBackground(RGB(5, 27, 80));
_terminal->SetDefaultForeground(RGB(255, 255, 255));
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); });
localPointerToThread->EnablePainting();
_multiClickTime = std::chrono::milliseconds{ GetDoubleClickTime() };
@@ -250,33 +191,6 @@ HRESULT HwndTerminal::Initialize()
return S_OK;
}
void HwndTerminal::Teardown() noexcept
try
{
// As a rule, detach resources from the Terminal before shutting them down.
// This ensures that teardown is reentrant.
// Shut down the renderer (and therefore the thread) before we implode
if (auto localRenderEngine{ std::exchange(_renderEngine, nullptr) })
{
if (auto localRenderer{ std::exchange(_renderer, nullptr) })
{
localRenderer->TriggerTeardown();
// renderer is destroyed
}
// renderEngine is destroyed
}
if (auto localHwnd{ _hwnd.release() })
{
// If we're being called through WM_DESTROY, we won't get here (hwnd is already released)
// If we're not, we may end up in Teardown _again_... but by the time we do, all other
// resources have been released and will not be released again.
DestroyWindow(localHwnd);
}
}
CATCH_LOG();
void HwndTerminal::RegisterScrollCallback(std::function<void(int, int, int)> callback)
{
_terminal->SetScrollPositionChangedCallback(callback);
@@ -433,15 +347,7 @@ void _stdcall TerminalSendOutput(void* terminal, LPCWSTR data)
publicTerminal->SendOutput(data);
}
/// <summary>
/// Triggers a terminal resize using the new width and height in pixel.
/// </summary>
/// <param name="terminal">Terminal pointer.</param>
/// <param name="width">New width of the terminal in pixels.</param>
/// <param name="height">New height of the terminal in pixels</param>
/// <param name="dimensions">Out parameter containing the columns and rows that fit the new size.</param>
/// <returns>HRESULT of the attempted resize.</returns>
HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions)
HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double height, _Out_ COORD* dimensions)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
@@ -454,55 +360,10 @@ HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _I
static_cast<int>(height),
0));
const SIZE windowSize{ width, height };
const SIZE windowSize{ static_cast<short>(width), static_cast<short>(height) };
return publicTerminal->Refresh(windowSize, dimensions);
}
/// <summary>
/// Helper method for resizing the terminal using character column and row counts
/// </summary>
/// <param name="terminal">Pointer to the terminal object.</param>
/// <param name="dimensionsInCharacters">New terminal size in row and column count.</param>
/// <param name="dimensionsInPixels">Out parameter with the new size of the renderer.</param>
/// <returns>HRESULT of the attempted resize.</returns>
HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensionsInCharacters, _Out_ SIZE* dimensionsInPixels)
{
RETURN_HR_IF_NULL(E_INVALIDARG, dimensionsInPixels);
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto viewInCharacters = Viewport::FromDimensions({ 0, 0 }, { (dimensionsInCharacters.X), (dimensionsInCharacters.Y) });
const auto viewInPixels = publicTerminal->_renderEngine->GetViewportInPixels(viewInCharacters);
dimensionsInPixels->cx = viewInPixels.Width();
dimensionsInPixels->cy = viewInPixels.Height();
COORD unused{ 0, 0 };
return TerminalTriggerResize(terminal, viewInPixels.Width(), viewInPixels.Height(), &unused);
}
/// <summary>
/// Calculates the amount of rows and columns that fit in the provided width and height.
/// </summary>
/// <param name="terminal">Terminal pointer</param>
/// <param name="width">Width of the terminal area to calculate.</param>
/// <param name="height">Height of the terminal area to calculate.</param>
/// <param name="dimensions">Out parameter containing the columns and rows that fit the new size.</param>
/// <returns>HRESULT of the calculation.</returns>
HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, { width, height });
const auto viewInCharacters = publicTerminal->_renderEngine->GetViewportInCharacters(viewInPixels);
dimensions->X = viewInCharacters.Width();
dimensions->Y = viewInCharacters.Height();
return S_OK;
}
void _stdcall TerminalDpiChanged(void* terminal, int newDpi)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
@@ -606,21 +467,11 @@ try
}
CATCH_RETURN();
void HwndTerminal::_ClearSelection() noexcept
try
{
auto lock{ _terminal->LockForWriting() };
_terminal->ClearSelection();
_renderer->TriggerSelection();
}
CATCH_LOG();
void _stdcall TerminalClearSelection(void* terminal)
{
auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_ClearSelection();
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
publicTerminal->_terminal->ClearSelection();
}
bool _stdcall TerminalIsSelectionActive(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
@@ -631,10 +482,9 @@ bool _stdcall TerminalIsSelectionActive(void* terminal)
// Returns the selected text in the terminal.
const wchar_t* _stdcall TerminalGetSelection(void* terminal)
{
auto publicTerminal = static_cast<HwndTerminal*>(terminal);
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
publicTerminal->_ClearSelection();
// convert text: vector<string> --> string
std::wstring selectedText;
@@ -644,6 +494,8 @@ const wchar_t* _stdcall TerminalGetSelection(void* terminal)
}
auto returnText = wil::make_cotaskmem_string_nothrow(selectedText.c_str());
TerminalClearSelection(terminal);
return returnText.release();
}
@@ -689,30 +541,19 @@ bool HwndTerminal::_CanSendVTMouseInput() const noexcept
bool HwndTerminal::_SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept
try
{
til::point cursorPosition{
const til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
};
const til::size fontSize{ this->_actualFont.GetSize() };
short wheelDelta{ 0 };
if (uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL)
if (uMsg == WM_MOUSEWHEEL)
{
wheelDelta = HIWORD(wParam);
// If it's a *WHEEL event, it's in screen coordinates, not window (?!)
POINT coordsToTransform = cursorPosition;
ScreenToClient(_hwnd.get(), &coordsToTransform);
cursorPosition = coordsToTransform;
}
const TerminalInput::MouseButtonState state{
WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed),
WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed),
WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed)
};
return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state);
return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta);
}
catch (...)
{
@@ -720,24 +561,20 @@ catch (...)
return false;
}
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept
try
{
auto modifiers = getControlKeyState();
if (WI_IsFlagSet(flags, ENHANCED_KEY))
{
modifiers |= ControlKeyStates::EnhancedKey;
}
_terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown);
const auto flags = getControlKeyState();
_terminal->SendKeyEvent(vkey, scanCode, flags, keyDown);
}
CATCH_LOG();
void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept
void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode) noexcept
try
{
if (_terminal->IsSelectionActive())
{
_ClearSelection();
_terminal->ClearSelection();
if (ch == UNICODE_ESC)
{
// ESC should clear any selection before it triggers input.
@@ -752,25 +589,21 @@ try
return;
}
auto modifiers = getControlKeyState();
if (WI_IsFlagSet(flags, ENHANCED_KEY))
{
modifiers |= ControlKeyStates::EnhancedKey;
}
_terminal->SendCharEvent(ch, scanCode, modifiers);
const auto flags = getControlKeyState();
_terminal->SendCharEvent(ch, scanCode, flags);
}
CATCH_LOG();
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown)
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_SendKeyEvent(vkey, scanCode, flags, keyDown);
publicTerminal->_SendKeyEvent(vkey, scanCode, keyDown);
}
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags)
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_SendCharEvent(ch, scanCode, flags);
publicTerminal->_SendCharEvent(ch, scanCode);
}
void _stdcall DestroyTerminal(void* terminal)
@@ -788,7 +621,6 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
publicTerminal->_terminal->SetDefaultForeground(theme.DefaultForeground);
publicTerminal->_terminal->SetDefaultBackground(theme.DefaultBackground);
publicTerminal->_renderEngine->SetSelectionBackground(theme.DefaultSelectionBackground, theme.SelectionBackgroundAlpha);
// Set the font colors
for (size_t tableIndex = 0; tableIndex < 16; tableIndex++)
@@ -800,7 +632,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
publicTerminal->_terminal->SetCursorStyle(theme.CursorStyle);
publicTerminal->_desiredFont = { fontFamily, 0, DEFAULT_FONT_WEIGHT, { 0, fontSize }, CP_UTF8 };
publicTerminal->_desiredFont = { fontFamily, 0, 10, { 0, fontSize }, CP_UTF8 };
publicTerminal->_UpdateFont(newDpi);
// When the font changes the terminal dimensions need to be recalculated since the available row and column
@@ -813,6 +645,18 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
publicTerminal->Refresh(windowSize, &dimensions);
}
// Resizes the terminal to the specified rows and columns.
HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
auto lock = publicTerminal->_terminal->LockForWriting();
publicTerminal->_terminal->ClearSelection();
publicTerminal->_renderer->TriggerRedrawAll();
return publicTerminal->_terminal->UserResize(dimensions);
}
void _stdcall TerminalBlinkCursor(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);

View File

@@ -12,13 +12,10 @@
using namespace Microsoft::Console::VirtualTerminal;
// Keep in sync with TerminalTheme.cs
typedef struct _TerminalTheme
{
COLORREF DefaultBackground;
COLORREF DefaultForeground;
COLORREF DefaultSelectionBackground;
float SelectionBackgroundAlpha;
DispatchTypes::CursorStyle CursorStyle;
COLORREF ColorTable[16];
} TerminalTheme, *LPTerminalTheme;
@@ -27,9 +24,8 @@ extern "C" {
__declspec(dllexport) HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
__declspec(dllexport) void _stdcall TerminalSendOutput(void* terminal, LPCWSTR data);
__declspec(dllexport) void _stdcall TerminalRegisterScrollCallback(void* terminal, void __stdcall callback(int, int, int));
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensions, _Out_ SIZE* dimensionsInPixels);
__declspec(dllexport) HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double height, _Out_ COORD* dimensions);
__declspec(dllexport) HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
__declspec(dllexport) void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
__declspec(dllexport) void _stdcall TerminalUserScroll(void* terminal, int viewTop);
__declspec(dllexport) void _stdcall TerminalClearSelection(void* terminal);
@@ -38,8 +34,8 @@ __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
__declspec(dllexport) void _stdcall DestroyTerminal(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
__declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*));
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD flags, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
__declspec(dllexport) void _stdcall TerminalSetFocus(void* terminal);
@@ -55,10 +51,9 @@ public:
HwndTerminal(HwndTerminal&&) = default;
HwndTerminal& operator=(const HwndTerminal&) = default;
HwndTerminal& operator=(HwndTerminal&&) = default;
~HwndTerminal();
~HwndTerminal() = default;
HRESULT Initialize();
void Teardown() noexcept;
void SendOutput(std::wstring_view data);
HRESULT Refresh(const SIZE windowSize, _Out_ COORD* dimensions);
void RegisterScrollCallback(std::function<void(int, int, int)> callback);
@@ -91,16 +86,14 @@ private:
std::optional<til::point> _singleClickTouchdownPos;
friend HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
friend HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
friend HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensions, _Out_ SIZE* dimensionsInPixels);
friend HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
friend HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
friend void _stdcall TerminalUserScroll(void* terminal, int viewTop);
friend void _stdcall TerminalClearSelection(void* terminal);
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
friend void _stdcall TerminalBlinkCursor(void* terminal);
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
@@ -119,13 +112,11 @@ private:
HRESULT _MoveSelection(LPARAM lParam) noexcept;
IRawElementProviderSimple* _GetUiaProvider() noexcept;
void _ClearSelection() noexcept;
bool _CanSendVTMouseInput() const noexcept;
bool _SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;
void _SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept;
void _SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept;
void _SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept;
void _SendCharEvent(wchar_t ch, WORD scanCode) noexcept;
// Inherited via IControlAccessibilityInfo
COORD GetFontSize() const override;

View File

@@ -185,17 +185,12 @@ HRESULT OpenTerminalHere::GetState(IShellItemArray* /*psiItemArray*/,
HRESULT OpenTerminalHere::GetIcon(IShellItemArray* /*psiItemArray*/,
LPWSTR* ppszIcon)
try
{
std::filesystem::path modulePath{ wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
modulePath.replace_filename(WindowsTerminalExe);
// WindowsTerminal.exe,-101 will be the first icon group in WT
// We're using WindowsTerminal here explicitly, and not wt (from _getExePath), because
// WindowsTerminal is the only one built with the right icons.
const auto resource{ modulePath.wstring() + L",-101" };
return SHStrDupW(resource.c_str(), ppszIcon);
// the icon ref ("dll,-<resid>") is provided here, in this case none is provided
*ppszIcon = nullptr;
// TODO GH#6111: Return the Terminal icon here
return E_NOTIMPL;
}
CATCH_RETURN();
HRESULT OpenTerminalHere::GetFlags(EXPCMDFLAGS* pFlags)
{

View File

@@ -2,49 +2,40 @@
#include "ActionArgs.h"
#include "ActionAndArgs.h"
#include "ActionAndArgs.g.cpp"
#include "JsonUtils.h"
#include <LibraryResources.h>
static constexpr std::string_view AdjustFontSizeKey{ "adjustFontSize" };
static constexpr std::string_view CloseOtherTabsKey{ "closeOtherTabs" };
static constexpr std::string_view ClosePaneKey{ "closePane" };
static constexpr std::string_view CloseTabKey{ "closeTab" };
static constexpr std::string_view CloseTabsAfterKey{ "closeTabsAfter" };
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
static constexpr std::string_view CopyTextKey{ "copy" };
static constexpr std::string_view PasteTextKey{ "paste" };
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
static constexpr std::string_view DuplicateTabKey{ "duplicateTab" };
static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
static constexpr std::string_view FindKey{ "find" };
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
static constexpr std::string_view NewTabKey{ "newTab" };
static constexpr std::string_view NewWindowKey{ "newWindow" };
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
static constexpr std::string_view CloseTabKey{ "closeTab" };
static constexpr std::string_view ClosePaneKey{ "closePane" };
static constexpr std::string_view SwitchtoTabKey{ "switchToTab" };
static constexpr std::string_view NextTabKey{ "nextTab" };
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
static constexpr std::string_view PasteTextKey{ "paste" };
static constexpr std::string_view PrevTabKey{ "prevTab" };
static constexpr std::string_view RenameTabKey{ "renameTab" };
static constexpr std::string_view AdjustFontSizeKey{ "adjustFontSize" };
static constexpr std::string_view ResetFontSizeKey{ "resetFontSize" };
static constexpr std::string_view ResizePaneKey{ "resizePane" };
static constexpr std::string_view ScrolldownKey{ "scrollDown" };
static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" };
static constexpr std::string_view ScrollupKey{ "scrollUp" };
static constexpr std::string_view ScrolldownKey{ "scrollDown" };
static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" };
static constexpr std::string_view SendInputKey{ "sendInput" };
static constexpr std::string_view SetColorSchemeKey{ "setColorScheme" };
static constexpr std::string_view SetTabColorKey{ "setTabColor" };
static constexpr std::string_view SplitPaneKey{ "splitPane" };
static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" };
static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
static constexpr std::string_view TabSearchKey{ "tabSearch" };
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings
static constexpr std::string_view SplitPaneKey{ "splitPane" };
static constexpr std::string_view ResizePaneKey{ "resizePane" };
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
static constexpr std::string_view FindKey{ "find" };
static constexpr std::string_view ToggleRetroEffectKey{ "toggleRetroEffect" };
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" };
static constexpr std::string_view ToggleRetroEffectKey{ "toggleRetroEffect" };
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
static constexpr std::string_view SetTabColorKey{ "setTabColor" };
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
static constexpr std::string_view RenameTabKey{ "renameTab" };
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
static constexpr std::string_view ActionKey{ "action" };
@@ -53,8 +44,6 @@ static constexpr std::string_view UnboundKey{ "unbound" };
namespace winrt::TerminalApp::implementation
{
using namespace ::TerminalApp;
// Specifically use a map here over an unordered_map. We want to be able to
// iterate over these entries in-order when we're serializing the keybindings.
// HERE BE DRAGONS:
@@ -64,48 +53,41 @@ namespace winrt::TerminalApp::implementation
// the map are all const for the lifetime of the app, we have nothing to worry
// about here.
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionKeyNamesMap{
{ AdjustFontSizeKey, ShortcutAction::AdjustFontSize },
{ CloseOtherTabsKey, ShortcutAction::CloseOtherTabs },
{ ClosePaneKey, ShortcutAction::ClosePane },
{ CloseTabKey, ShortcutAction::CloseTab },
{ CloseTabsAfterKey, ShortcutAction::CloseTabsAfter },
{ CloseWindowKey, ShortcutAction::CloseWindow },
{ CopyTextKey, ShortcutAction::CopyText },
{ PasteTextKey, ShortcutAction::PasteText },
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
{ DuplicateTabKey, ShortcutAction::DuplicateTab },
{ ExecuteCommandlineKey, ShortcutAction::ExecuteCommandline },
{ FindKey, ShortcutAction::Find },
{ MoveFocusKey, ShortcutAction::MoveFocus },
{ NewTabKey, ShortcutAction::NewTab },
{ NewWindowKey, ShortcutAction::NewWindow },
{ CloseWindowKey, ShortcutAction::CloseWindow },
{ CloseTabKey, ShortcutAction::CloseTab },
{ ClosePaneKey, ShortcutAction::ClosePane },
{ NextTabKey, ShortcutAction::NextTab },
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
{ OpenSettingsKey, ShortcutAction::OpenSettings },
{ OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker },
{ PasteTextKey, ShortcutAction::PasteText },
{ PrevTabKey, ShortcutAction::PrevTab },
{ RenameTabKey, ShortcutAction::RenameTab },
{ AdjustFontSizeKey, ShortcutAction::AdjustFontSize },
{ ResetFontSizeKey, ShortcutAction::ResetFontSize },
{ ResizePaneKey, ShortcutAction::ResizePane },
{ ScrolldownKey, ShortcutAction::ScrollDown },
{ ScrolldownpageKey, ShortcutAction::ScrollDownPage },
{ ScrollupKey, ShortcutAction::ScrollUp },
{ ScrolldownKey, ShortcutAction::ScrollDown },
{ ScrolluppageKey, ShortcutAction::ScrollUpPage },
{ SendInputKey, ShortcutAction::SendInput },
{ SetColorSchemeKey, ShortcutAction::SetColorScheme },
{ SetTabColorKey, ShortcutAction::SetTabColor },
{ SplitPaneKey, ShortcutAction::SplitPane },
{ ScrolldownpageKey, ShortcutAction::ScrollDownPage },
{ SwitchToTabKey, ShortcutAction::SwitchToTab },
{ TabSearchKey, ShortcutAction::TabSearch },
{ ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop },
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
{ ResizePaneKey, ShortcutAction::ResizePane },
{ MoveFocusKey, ShortcutAction::MoveFocus },
{ OpenSettingsKey, ShortcutAction::OpenSettings },
{ ToggleRetroEffectKey, ShortcutAction::ToggleRetroEffect },
{ ToggleFocusModeKey, ShortcutAction::ToggleFocusMode },
{ ToggleFullscreenKey, ShortcutAction::ToggleFullscreen },
{ TogglePaneZoomKey, ShortcutAction::TogglePaneZoom },
{ ToggleRetroEffectKey, ShortcutAction::ToggleRetroEffect },
{ ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop },
{ SplitPaneKey, ShortcutAction::SplitPane },
{ SetTabColorKey, ShortcutAction::SetTabColor },
{ OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker },
{ UnboundKey, ShortcutAction::Invalid },
{ FindKey, ShortcutAction::Find },
{ RenameTabKey, ShortcutAction::RenameTab },
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
};
using ParseResult = std::tuple<IActionArgs, std::vector<TerminalApp::SettingsLoadWarnings>>;
using ParseResult = std::tuple<IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
using ParseActionFunction = std::function<ParseResult(const Json::Value&)>;
// This is a map of ShortcutAction->function<IActionArgs(Json::Value)>. It holds
@@ -114,21 +96,25 @@ namespace winrt::TerminalApp::implementation
// placed into this map, with the corresponding deserializer function as the
// value.
static const std::map<ShortcutAction, ParseActionFunction, std::less<>> argParsers{
{ ShortcutAction::AdjustFontSize, AdjustFontSizeArgs::FromJson },
{ ShortcutAction::CloseOtherTabs, CloseOtherTabsArgs::FromJson },
{ ShortcutAction::CloseTabsAfter, CloseTabsAfterArgs::FromJson },
{ ShortcutAction::CopyText, CopyTextArgs::FromJson },
{ ShortcutAction::ExecuteCommandline, ExecuteCommandlineArgs::FromJson },
{ ShortcutAction::MoveFocus, MoveFocusArgs::FromJson },
{ ShortcutAction::NewTab, NewTabArgs::FromJson },
{ ShortcutAction::OpenSettings, OpenSettingsArgs::FromJson },
{ ShortcutAction::RenameTab, RenameTabArgs::FromJson },
{ ShortcutAction::ResizePane, ResizePaneArgs::FromJson },
{ ShortcutAction::SendInput, SendInputArgs::FromJson },
{ ShortcutAction::SetColorScheme, SetColorSchemeArgs::FromJson },
{ ShortcutAction::SetTabColor, SetTabColorArgs::FromJson },
{ ShortcutAction::SplitPane, SplitPaneArgs::FromJson },
{ ShortcutAction::SwitchToTab, SwitchToTabArgs::FromJson },
{ ShortcutAction::CopyText, winrt::TerminalApp::implementation::CopyTextArgs::FromJson },
{ ShortcutAction::NewTab, winrt::TerminalApp::implementation::NewTabArgs::FromJson },
{ ShortcutAction::SwitchToTab, winrt::TerminalApp::implementation::SwitchToTabArgs::FromJson },
{ ShortcutAction::ResizePane, winrt::TerminalApp::implementation::ResizePaneArgs::FromJson },
{ ShortcutAction::MoveFocus, winrt::TerminalApp::implementation::MoveFocusArgs::FromJson },
{ ShortcutAction::AdjustFontSize, winrt::TerminalApp::implementation::AdjustFontSizeArgs::FromJson },
{ ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson },
{ ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson },
{ ShortcutAction::SetTabColor, winrt::TerminalApp::implementation::SetTabColorArgs::FromJson },
{ ShortcutAction::RenameTab, winrt::TerminalApp::implementation::RenameTabArgs::FromJson },
{ ShortcutAction::Invalid, nullptr },
};
@@ -169,7 +155,7 @@ namespace winrt::TerminalApp::implementation
// - a deserialized ActionAndArgs corresponding to the values in json, or
// null if we failed to deserialize an action.
winrt::com_ptr<ActionAndArgs> ActionAndArgs::FromJson(const Json::Value& json,
std::vector<TerminalApp::SettingsLoadWarnings>& warnings)
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
{
// Invalid is our placeholder that the action was not parsed.
ShortcutAction action = ShortcutAction::Invalid;
@@ -197,9 +183,11 @@ namespace winrt::TerminalApp::implementation
}
else if (json.isObject())
{
if (const auto actionString{ JsonUtils::GetValueForKey<std::optional<std::string>>(json, ActionKey) })
const auto actionVal = json[JsonKey(ActionKey)];
if (actionVal.isString())
{
action = GetActionFromString(*actionString);
auto actionString = actionVal.asString();
action = GetActionFromString(actionString);
argsVal = json;
}
}
@@ -208,7 +196,7 @@ namespace winrt::TerminalApp::implementation
// does, we'll try to deserialize any "args" that were provided with
// the binding.
IActionArgs args{ nullptr };
std::vector<TerminalApp::SettingsLoadWarnings> parseWarnings;
std::vector<::TerminalApp::SettingsLoadWarnings> parseWarnings;
const auto deserializersIter = argParsers.find(action);
if (deserializersIter != argParsers.end())
{
@@ -246,45 +234,38 @@ namespace winrt::TerminalApp::implementation
// to load the resources at _init_, only at runtime.
static const auto GeneratedActionNames = []() {
return std::unordered_map<ShortcutAction, winrt::hstring>{
{ ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
{ ShortcutAction::CloseOtherTabs, L"" }, // Intentionally omitted, must be generated by GenerateName
{ ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
{ ShortcutAction::CloseTab, RS_(L"CloseTabCommandKey") },
{ ShortcutAction::CloseTabsAfter, L"" }, // Intentionally omitted, must be generated by GenerateName
{ ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
{ ShortcutAction::CopyText, RS_(L"CopyTextCommandKey") },
{ ShortcutAction::PasteText, RS_(L"PasteTextCommandKey") },
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
{ ShortcutAction::DuplicateTab, RS_(L"DuplicateTabCommandKey") },
{ ShortcutAction::ExecuteCommandline, RS_(L"ExecuteCommandlineCommandKey") },
{ ShortcutAction::Find, RS_(L"FindCommandKey") },
{ ShortcutAction::Invalid, L"" },
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
{ ShortcutAction::NewTab, RS_(L"NewTabCommandKey") },
{ ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") },
{ ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
{ ShortcutAction::CloseTab, RS_(L"CloseTabCommandKey") },
{ ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
{ ShortcutAction::NextTab, RS_(L"NextTabCommandKey") },
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
{ ShortcutAction::PasteText, RS_(L"PasteTextCommandKey") },
{ ShortcutAction::PrevTab, RS_(L"PrevTabCommandKey") },
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
{ ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
{ ShortcutAction::ResetFontSize, RS_(L"ResetFontSizeCommandKey") },
{ ShortcutAction::ResizePane, RS_(L"ResizePaneCommandKey") },
{ ShortcutAction::ScrollDown, RS_(L"ScrollDownCommandKey") },
{ ShortcutAction::ScrollDownPage, RS_(L"ScrollDownPageCommandKey") },
{ ShortcutAction::ScrollUp, RS_(L"ScrollUpCommandKey") },
{ ShortcutAction::ScrollDown, RS_(L"ScrollDownCommandKey") },
{ ShortcutAction::ScrollUpPage, RS_(L"ScrollUpPageCommandKey") },
{ ShortcutAction::SendInput, L"" },
{ ShortcutAction::SetColorScheme, L"" },
{ ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
{ ShortcutAction::ScrollDownPage, RS_(L"ScrollDownPageCommandKey") },
{ ShortcutAction::SwitchToTab, RS_(L"SwitchToTabCommandKey") },
{ ShortcutAction::TabSearch, RS_(L"TabSearchCommandKey") },
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
{ ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") },
{ ShortcutAction::ResizePane, RS_(L"ResizePaneCommandKey") },
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
{ ShortcutAction::ToggleRetroEffect, RS_(L"ToggleRetroEffectCommandKey") },
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
{ ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") },
{ ShortcutAction::ToggleRetroEffect, RS_(L"ToggleRetroEffectCommandKey") },
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
{ ShortcutAction::Invalid, L"" },
{ ShortcutAction::Find, RS_(L"FindCommandKey") },
{ ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
{ ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") },
};
}();
@@ -300,4 +281,5 @@ namespace winrt::TerminalApp::implementation
const auto found = GeneratedActionNames.find(_Action);
return found != GeneratedActionNames.end() ? found->second : L"";
}
}

View File

@@ -9,7 +9,7 @@ namespace winrt::TerminalApp::implementation
{
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionKeyNamesMap;
static winrt::com_ptr<ActionAndArgs> FromJson(const Json::Value& json,
std::vector<TerminalApp::SettingsLoadWarnings>& warnings);
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
ActionAndArgs() = default;
hstring GenerateName() const;

View File

@@ -13,20 +13,13 @@
#include "ResizePaneArgs.g.cpp"
#include "MoveFocusArgs.g.cpp"
#include "AdjustFontSizeArgs.g.cpp"
#include "SendInputArgs.g.cpp"
#include "SplitPaneArgs.g.cpp"
#include "OpenSettingsArgs.g.cpp"
#include "SetColorSchemeArgs.g.cpp"
#include "SetTabColorArgs.g.cpp"
#include "RenameTabArgs.g.cpp"
#include "ExecuteCommandlineArgs.g.cpp"
#include "Utils.h"
#include <LibraryResources.h>
using namespace winrt::Microsoft::Terminal::TerminalControl;
namespace winrt::TerminalApp::implementation
{
winrt::hstring NewTerminalArgs::GenerateName() const
@@ -68,47 +61,11 @@ namespace winrt::TerminalApp::implementation
winrt::hstring CopyTextArgs::GenerateName() const
{
std::wstringstream ss;
if (_SingleLine)
{
ss << RS_(L"CopyTextAsSingleLineCommandKey").c_str();
return RS_(L"CopyTextAsSingleLineCommandKey");
}
else
{
ss << RS_(L"CopyTextCommandKey").c_str();
}
if (_CopyFormatting != nullptr)
{
ss << L", copyFormatting: ";
if (_CopyFormatting.Value() == CopyFormat::All)
{
ss << L"all, ";
}
else if (_CopyFormatting.Value() == static_cast<CopyFormat>(0))
{
ss << L"none, ";
}
else
{
if (WI_IsFlagSet(_CopyFormatting.Value(), CopyFormat::HTML))
{
ss << L"html, ";
}
if (WI_IsFlagSet(_CopyFormatting.Value(), CopyFormat::RTF))
{
ss << L"rtf, ";
}
}
// Chop off the last ","
auto result = ss.str();
return winrt::hstring{ result.substr(0, result.size() - 2) };
}
return winrt::hstring{ ss.str() };
return RS_(L"CopyTextCommandKey");
}
winrt::hstring NewTabArgs::GenerateName() const
@@ -207,16 +164,6 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::hstring SendInputArgs::GenerateName() const
{
// The string will be similar to the following:
// * "Send Input: ...input..."
auto escapedInput = VisualizeControlCodes(_Input);
auto name = fmt::format(std::wstring_view(RS_(L"SendInputCommandKey")), escapedInput);
return winrt::hstring{ name };
}
winrt::hstring SplitPaneArgs::GenerateName() const
{
// The string will be similar to the following:
@@ -281,19 +228,6 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::hstring SetColorSchemeArgs::GenerateName() const
{
// "Set color scheme to "{_SchemeName}""
if (!_SchemeName.empty())
{
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"SetColorSchemeCommandKey")),
_SchemeName.c_str())
};
}
return L"";
}
winrt::hstring SetTabColorArgs::GenerateName() const
{
// "Set tab color to #RRGGBB"
@@ -324,42 +258,4 @@ namespace winrt::TerminalApp::implementation
return RS_(L"ResetTabNameCommandKey");
}
winrt::hstring ExecuteCommandlineArgs::GenerateName() const
{
// "Run commandline "{_Commandline}" in this window"
if (!_Commandline.empty())
{
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"ExecuteCommandlineCommandKey")),
_Commandline.c_str())
};
}
return L"";
}
winrt::hstring CloseOtherTabsArgs::GenerateName() const
{
if (_Index)
{
// "Close tabs other than index {0}"
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"CloseOtherTabsCommandKey")),
_Index.Value())
};
}
return RS_(L"CloseOtherTabsDefaultCommandKey");
}
winrt::hstring CloseTabsAfterArgs::GenerateName() const
{
if (_Index)
{
// "Close tabs after index {0}"
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"CloseTabsAfterCommandKey")),
_Index.Value())
};
}
return RS_(L"CloseTabsAfterDefaultCommandKey");
}
}

View File

@@ -13,23 +13,16 @@
#include "ResizePaneArgs.g.h"
#include "MoveFocusArgs.g.h"
#include "AdjustFontSizeArgs.g.h"
#include "SendInputArgs.g.h"
#include "SplitPaneArgs.g.h"
#include "OpenSettingsArgs.g.h"
#include "SetColorSchemeArgs.g.h"
#include "SetTabColorArgs.g.h"
#include "RenameTabArgs.g.h"
#include "ExecuteCommandlineArgs.g.h"
#include "CloseOtherTabsArgs.g.h"
#include "CloseTabsAfterArgs.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include "Utils.h"
#include "JsonUtils.h"
#include "TerminalWarnings.h"
#include "TerminalSettingsSerializationHelpers.h"
// Notes on defining ActionArgs and ActionEventArgs:
// * All properties specific to an action should be defined as an ActionArgs
// class that implements IActionArgs
@@ -38,8 +31,7 @@
namespace winrt::TerminalApp::implementation
{
using namespace ::TerminalApp;
using FromJsonResult = std::tuple<winrt::TerminalApp::IActionArgs, std::vector<TerminalApp::SettingsLoadWarnings>>;
using FromJsonResult = std::tuple<winrt::TerminalApp::IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
struct ActionEventArgs : public ActionEventArgsT<ActionEventArgs>
{
@@ -81,11 +73,26 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<NewTerminalArgs>();
JsonUtils::GetValueForKey(json, CommandlineKey, args->_Commandline);
JsonUtils::GetValueForKey(json, StartingDirectoryKey, args->_StartingDirectory);
JsonUtils::GetValueForKey(json, TabTitleKey, args->_TabTitle);
JsonUtils::GetValueForKey(json, ProfileIndexKey, args->_ProfileIndex);
JsonUtils::GetValueForKey(json, ProfileKey, args->_Profile);
if (auto commandline{ json[JsonKey(CommandlineKey)] })
{
args->_Commandline = winrt::to_hstring(commandline.asString());
}
if (auto startingDirectory{ json[JsonKey(StartingDirectoryKey)] })
{
args->_StartingDirectory = winrt::to_hstring(startingDirectory.asString());
}
if (auto tabTitle{ json[JsonKey(TabTitleKey)] })
{
args->_TabTitle = winrt::to_hstring(tabTitle.asString());
}
if (auto index{ json[JsonKey(ProfileIndexKey)] })
{
args->_ProfileIndex = index.asInt();
}
if (auto profile{ json[JsonKey(ProfileKey)] })
{
args->_Profile = winrt::to_hstring(profile.asString());
}
return *args;
}
};
@@ -94,10 +101,8 @@ namespace winrt::TerminalApp::implementation
{
CopyTextArgs() = default;
GETSET_PROPERTY(bool, SingleLine, false);
GETSET_PROPERTY(Windows::Foundation::IReference<Microsoft::Terminal::TerminalControl::CopyFormat>, CopyFormatting, nullptr);
static constexpr std::string_view SingleLineKey{ "singleLine" };
static constexpr std::string_view CopyFormattingKey{ "copyFormatting" };
public:
hstring GenerateName() const;
@@ -107,8 +112,7 @@ namespace winrt::TerminalApp::implementation
auto otherAsUs = other.try_as<CopyTextArgs>();
if (otherAsUs)
{
return otherAsUs->_SingleLine == _SingleLine &&
otherAsUs->_CopyFormatting == _CopyFormatting;
return otherAsUs->_SingleLine == _SingleLine;
}
return false;
};
@@ -116,8 +120,10 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<CopyTextArgs>();
JsonUtils::GetValueForKey(json, SingleLineKey, args->_SingleLine);
JsonUtils::GetValueForKey(json, CopyFormattingKey, args->_CopyFormatting);
if (auto singleLine{ json[JsonKey(SingleLineKey)] })
{
args->_SingleLine = singleLine.asBool();
}
return { *args, {} };
}
};
@@ -171,11 +177,49 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SwitchToTabArgs>();
JsonUtils::GetValueForKey(json, TabIndexKey, args->_TabIndex);
if (auto tabIndex{ json[JsonKey(TabIndexKey)] })
{
args->_TabIndex = tabIndex.asUInt();
}
return { *args, {} };
}
};
// Possible Direction values
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view LeftString{ "left" };
static constexpr std::string_view RightString{ "right" };
static constexpr std::string_view UpString{ "up" };
static constexpr std::string_view DownString{ "down" };
// Function Description:
// - Helper function for parsing a Direction from a string
// Arguments:
// - directionString: the string to attempt to parse
// Return Value:
// - The encoded Direction value, or Direction::None if it was an invalid string
static TerminalApp::Direction ParseDirection(const std::string& directionString)
{
if (directionString == LeftString)
{
return TerminalApp::Direction::Left;
}
else if (directionString == RightString)
{
return TerminalApp::Direction::Right;
}
else if (directionString == UpString)
{
return TerminalApp::Direction::Up;
}
else if (directionString == DownString)
{
return TerminalApp::Direction::Down;
}
// default behavior for invalid data
return TerminalApp::Direction::None;
};
struct ResizePaneArgs : public ResizePaneArgsT<ResizePaneArgs>
{
ResizePaneArgs() = default;
@@ -199,10 +243,13 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<ResizePaneArgs>();
JsonUtils::GetValueForKey(json, DirectionKey, args->_Direction);
if (auto directionString{ json[JsonKey(DirectionKey)] })
{
args->_Direction = ParseDirection(directionString.asString());
}
if (args->_Direction == TerminalApp::Direction::None)
{
return { nullptr, { TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
}
else
{
@@ -214,9 +261,6 @@ namespace winrt::TerminalApp::implementation
struct MoveFocusArgs : public MoveFocusArgsT<MoveFocusArgs>
{
MoveFocusArgs() = default;
MoveFocusArgs(TerminalApp::Direction direction) :
_Direction{ direction } {};
GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::None);
static constexpr std::string_view DirectionKey{ "direction" };
@@ -237,10 +281,13 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<MoveFocusArgs>();
JsonUtils::GetValueForKey(json, DirectionKey, args->_Direction);
if (auto directionString{ json[JsonKey(DirectionKey)] })
{
args->_Direction = ParseDirection(directionString.asString());
}
if (args->_Direction == TerminalApp::Direction::None)
{
return { nullptr, { TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
}
else
{
@@ -272,50 +319,51 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<AdjustFontSizeArgs>();
JsonUtils::GetValueForKey(json, AdjustFontSizeDelta, args->_Delta);
if (auto jsonDelta{ json[JsonKey(AdjustFontSizeDelta)] })
{
args->_Delta = jsonDelta.asInt();
}
return { *args, {} };
}
};
struct SendInputArgs : public SendInputArgsT<SendInputArgs>
// Possible SplitState values
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view VerticalKey{ "vertical" };
static constexpr std::string_view HorizontalKey{ "horizontal" };
static constexpr std::string_view AutomaticKey{ "auto" };
static TerminalApp::SplitState ParseSplitState(const std::string& stateString)
{
SendInputArgs() = default;
GETSET_PROPERTY(winrt::hstring, Input, L"");
static constexpr std::string_view InputKey{ "input" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
if (stateString == VerticalKey)
{
if (auto otherAsUs = other.try_as<SendInputArgs>(); otherAsUs)
{
return otherAsUs->_Input == _Input;
}
return false;
};
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SendInputArgs>();
JsonUtils::GetValueForKey(json, InputKey, args->_Input);
if (args->_Input.empty())
{
return { nullptr, { TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
}
return { *args, {} };
return TerminalApp::SplitState::Vertical;
}
else if (stateString == HorizontalKey)
{
return TerminalApp::SplitState::Horizontal;
}
else if (stateString == AutomaticKey)
{
return TerminalApp::SplitState::Automatic;
}
// default behavior for invalid data
return TerminalApp::SplitState::Automatic;
};
// Possible SplitType values
static constexpr std::string_view DuplicateKey{ "duplicate" };
static TerminalApp::SplitType ParseSplitModeState(const std::string& stateString)
{
if (stateString == DuplicateKey)
{
return TerminalApp::SplitType::Duplicate;
}
return TerminalApp::SplitType::Manual;
}
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
{
SplitPaneArgs() = default;
SplitPaneArgs(winrt::TerminalApp::SplitState style, const winrt::TerminalApp::NewTerminalArgs& terminalArgs) :
_SplitStyle{ style },
_TerminalArgs{ terminalArgs } {};
SplitPaneArgs(SplitType splitMode) :
_SplitMode{ splitMode } {};
GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::Automatic);
GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr);
GETSET_PROPERTY(winrt::TerminalApp::SplitType, SplitMode, winrt::TerminalApp::SplitType::Manual);
@@ -343,12 +391,48 @@ namespace winrt::TerminalApp::implementation
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SplitPaneArgs>();
args->_TerminalArgs = NewTerminalArgs::FromJson(json);
JsonUtils::GetValueForKey(json, SplitKey, args->_SplitStyle);
JsonUtils::GetValueForKey(json, SplitModeKey, args->_SplitMode);
if (auto jsonStyle{ json[JsonKey(SplitKey)] })
{
args->_SplitStyle = ParseSplitState(jsonStyle.asString());
}
if (auto jsonStyle{ json[JsonKey(SplitModeKey)] })
{
args->_SplitMode = ParseSplitModeState(jsonStyle.asString());
}
return { *args, {} };
}
};
// Possible SettingsTarget values
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view SettingsFileString{ "settingsFile" };
static constexpr std::string_view DefaultsFileString{ "defaultsFile" };
static constexpr std::string_view AllFilesString{ "allFiles" };
// Function Description:
// - Helper function for parsing a SettingsTarget from a string
// Arguments:
// - targetString: the string to attempt to parse
// Return Value:
// - The encoded SettingsTarget value, or SettingsTarget::SettingsFile if it was an invalid string
static TerminalApp::SettingsTarget ParseSettingsTarget(const std::string& targetString)
{
if (targetString == SettingsFileString)
{
return TerminalApp::SettingsTarget::SettingsFile;
}
else if (targetString == DefaultsFileString)
{
return TerminalApp::SettingsTarget::DefaultsFile;
}
else if (targetString == AllFilesString)
{
return TerminalApp::SettingsTarget::AllFiles;
}
// default behavior for invalid data
return TerminalApp::SettingsTarget::SettingsFile;
};
struct OpenSettingsArgs : public OpenSettingsArgsT<OpenSettingsArgs>
{
OpenSettingsArgs() = default;
@@ -372,38 +456,9 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<OpenSettingsArgs>();
JsonUtils::GetValueForKey(json, TargetKey, args->_Target);
return { *args, {} };
}
};
struct SetColorSchemeArgs : public SetColorSchemeArgsT<SetColorSchemeArgs>
{
SetColorSchemeArgs() = default;
GETSET_PROPERTY(winrt::hstring, SchemeName, L"");
static constexpr std::string_view NameKey{ "colorScheme" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<SetColorSchemeArgs>();
if (otherAsUs)
if (auto targetString{ json[JsonKey(TargetKey)] })
{
return otherAsUs->_SchemeName == _SchemeName;
}
return false;
};
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SetColorSchemeArgs>();
JsonUtils::GetValueForKey(json, NameKey, args->_SchemeName);
if (args->_SchemeName.empty())
{
return { nullptr, { TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
args->_Target = ParseSettingsTarget(targetString.asString());
}
return { *args, {} };
}
@@ -432,10 +487,16 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SetTabColorArgs>();
if (const auto temp{ JsonUtils::GetValueForKey<std::optional<til::color>>(json, ColorKey) })
std::optional<til::color> temp;
try
{
args->_TabColor = static_cast<uint32_t>(*temp);
::TerminalApp::JsonUtils::GetOptionalColor(json, ColorKey, temp);
if (temp.has_value())
{
args->_TabColor = static_cast<uint32_t>(temp.value());
}
}
CATCH_LOG();
return { *args, {} };
}
};
@@ -463,95 +524,10 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<RenameTabArgs>();
JsonUtils::GetValueForKey(json, TitleKey, args->_Title);
return { *args, {} };
}
};
struct ExecuteCommandlineArgs : public ExecuteCommandlineArgsT<ExecuteCommandlineArgs>
{
ExecuteCommandlineArgs() = default;
GETSET_PROPERTY(winrt::hstring, Commandline, L"");
static constexpr std::string_view CommandlineKey{ "commandline" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<ExecuteCommandlineArgs>();
if (otherAsUs)
if (auto title{ json[JsonKey(TitleKey)] })
{
return otherAsUs->_Commandline == _Commandline;
args->_Title = winrt::to_hstring(title.asString());
}
return false;
};
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<ExecuteCommandlineArgs>();
JsonUtils::GetValueForKey(json, CommandlineKey, args->_Commandline);
if (args->_Commandline.empty())
{
return { nullptr, { TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
}
return { *args, {} };
}
};
struct CloseOtherTabsArgs : public CloseOtherTabsArgsT<CloseOtherTabsArgs>
{
CloseOtherTabsArgs() = default;
GETSET_PROPERTY(winrt::Windows::Foundation::IReference<uint32_t>, Index, nullptr);
static constexpr std::string_view IndexKey{ "index" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<CloseOtherTabsArgs>();
if (otherAsUs)
{
return otherAsUs->_Index == _Index;
}
return false;
};
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<CloseOtherTabsArgs>();
JsonUtils::GetValueForKey(json, IndexKey, args->_Index);
return { *args, {} };
}
};
struct CloseTabsAfterArgs : public CloseTabsAfterArgsT<CloseTabsAfterArgs>
{
CloseTabsAfterArgs() = default;
GETSET_PROPERTY(winrt::Windows::Foundation::IReference<uint32_t>, Index, nullptr);
static constexpr std::string_view IndexKey{ "index" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<CloseTabsAfterArgs>();
if (otherAsUs)
{
return otherAsUs->_Index == _Index;
}
return false;
};
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<CloseTabsAfterArgs>();
JsonUtils::GetValueForKey(json, IndexKey, args->_Index);
return { *args, {} };
}
};
@@ -561,6 +537,4 @@ namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(ActionEventArgs);
BASIC_FACTORY(NewTerminalArgs);
BASIC_FACTORY(MoveFocusArgs);
BASIC_FACTORY(SplitPaneArgs);
}

Some files were not shown because too many files have changed in this diff Show More