mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-17 11:41:04 +00:00
Compare commits
73 Commits
release-1.
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
795fdb043c | ||
|
|
82406c7886 | ||
|
|
d4b8b8bb6f | ||
|
|
1db0b70143 | ||
|
|
661fde5937 | ||
|
|
791e8c8d3c | ||
|
|
aead759d93 | ||
|
|
674c9182d7 | ||
|
|
4cee91f843 | ||
|
|
ad45139bb4 | ||
|
|
1721b91489 | ||
|
|
37c802c6df | ||
|
|
e7ff7903ac | ||
|
|
2db6967521 | ||
|
|
e8fff83307 | ||
|
|
e3ecc4d8da | ||
|
|
c38696da5d | ||
|
|
9e93ccbf32 | ||
|
|
16ce16e2f6 | ||
|
|
0d451cce2d | ||
|
|
c3b11bf827 | ||
|
|
45cde9edab | ||
|
|
3ac05363b1 | ||
|
|
14a7e45280 | ||
|
|
6aa3d70091 | ||
|
|
0284a917a6 | ||
|
|
f67db399c1 | ||
|
|
cddeb0898f | ||
|
|
bea3e7f696 | ||
|
|
fadd1852d3 | ||
|
|
15028f399e | ||
|
|
c00ae340db | ||
|
|
0c31c43cc4 | ||
|
|
806181db86 | ||
|
|
96930431c8 | ||
|
|
17f9dbf7bb | ||
|
|
aae9b83c42 | ||
|
|
e40b5e8ec1 | ||
|
|
842b162c85 | ||
|
|
a114d1b324 | ||
|
|
976a3c7d60 | ||
|
|
dd1c3df54d | ||
|
|
f8e8572c23 | ||
|
|
7b6625d7d9 | ||
|
|
a8aa295757 | ||
|
|
979ef5d981 | ||
|
|
0e68d0c09a | ||
|
|
823d28a8c6 | ||
|
|
aee33691bb | ||
|
|
3ca5374fe2 | ||
|
|
9e49ff2a05 | ||
|
|
3ffefeb39f | ||
|
|
80463833c1 | ||
|
|
396a698468 | ||
|
|
27bcdb03a2 | ||
|
|
a6af9a51ca | ||
|
|
1481475d2d | ||
|
|
2f81f5361c | ||
|
|
2d53cb2f4d | ||
|
|
42c01b83ea | ||
|
|
03b373f254 | ||
|
|
c7dc350504 | ||
|
|
7f1bdaf962 | ||
|
|
fb4dab0143 | ||
|
|
77f181febd | ||
|
|
89c9e6db84 | ||
|
|
f5f330a019 | ||
|
|
ec89b85462 | ||
|
|
e295cc4ae2 | ||
|
|
e103ca59e4 | ||
|
|
28c7890cbc | ||
|
|
4dee531aee | ||
|
|
c951a70208 |
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"XamlStyler.Console": {
|
||||
"version": "3.2008.4",
|
||||
"commands": [
|
||||
"xstyler"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
54
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
Normal file
54
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: "Bug report 🐛"
|
||||
about: Report errors or unexpected behavior
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||
|
||||
I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
|
||||
1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement.
|
||||
2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement.
|
||||
3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number).
|
||||
4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement.
|
||||
5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement.
|
||||
|
||||
All good? Then proceed!
|
||||
-->
|
||||
|
||||
<!--
|
||||
This bug tracker is monitored by Windows Terminal development team and other technical folks.
|
||||
|
||||
**Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**.
|
||||
Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue.
|
||||
|
||||
If this is an application crash, please also provide a Feedback Hub submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal (Preview)" and choose "Share My Feedback" after submission to get the link.
|
||||
|
||||
Please use this form and describe your issue, concisely but precisely, with as much detail as possible.
|
||||
|
||||
-->
|
||||
|
||||
# Environment
|
||||
|
||||
```none
|
||||
Windows build number: [run `[Environment]::OSVersion` for powershell, or `ver` for cmd]
|
||||
Windows Terminal version (if applicable):
|
||||
|
||||
Any other software?
|
||||
```
|
||||
|
||||
# Steps to reproduce
|
||||
|
||||
<!-- A description of how to trigger this bug. -->
|
||||
|
||||
# Expected behavior
|
||||
|
||||
<!-- A description of what you're expecting, possibly containing screenshots or reference material. -->
|
||||
|
||||
# Actual behavior
|
||||
|
||||
<!-- What's actually happening? -->
|
||||
53
.github/ISSUE_TEMPLATE/Bug_Report.yml
vendored
53
.github/ISSUE_TEMPLATE/Bug_Report.yml
vendored
@@ -1,53 +0,0 @@
|
||||
name: "Bug report 🐛"
|
||||
description: Report errors or unexpected behavior
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please make sure to [search for existing issues](https://github.com/microsoft/terminal/issues) before filing a new one!
|
||||
|
||||
If this is an application crash, please also provide a [Feedback Hub](https://aka.ms/terminal-feedback-hub) submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal" and choose "Share My Feedback" after submission to get the link.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Windows Terminal version (or Windows build number)
|
||||
placeholder: "10.0.19042.0, 1.7.3651.0"
|
||||
description: |
|
||||
If you are reporting an issue in Windows Terminal, you can find the version in the about dialog.
|
||||
|
||||
If you are reporting an issue with the Windows Console, please run `ver` or `[Environment]::OSVersion`.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Other Software
|
||||
description: If you're reporting a bug about our interaction with other software, what software? What versions?
|
||||
placeholder: |
|
||||
vim 8.2 (inside WSL)
|
||||
OpenSSH_for_Windows_8.1p1
|
||||
My Cool Application v0.3 (include a code snippet if it would help!)
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
placeholder: Tell us the steps required to trigger your bug.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: If you want to include screenshots, paste them into the markdown editor below.
|
||||
placeholder: What were you expecting?
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
placeholder: What happened instead?
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="vswhere" version="2.6.7" />
|
||||
</packages>
|
||||
</packages>
|
||||
13
.vscode/extensions.json
vendored
13
.vscode/extensions.json
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"ms-vscode.cpptools"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
|
||||
]
|
||||
}
|
||||
24
.vscode/launch.json
vendored
24
.vscode/launch.json
vendored
@@ -1,24 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug OpenConsole by Launching (x64, debug)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\bin\\x64\\debug\\openconsole.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
},
|
||||
{
|
||||
"name": "Debug Terminal by Attaching (You go build/register/launch it first.)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
105
.vscode/settings.json
vendored
105
.vscode/settings.json
vendored
@@ -1,105 +0,0 @@
|
||||
{
|
||||
"C_Cpp.default.browse.databaseFilename": "${workspaceFolder}\\.vscode\\.BROWSE.VC.DB",
|
||||
"C_Cpp.default.browse.path": [
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"C_Cpp.loggingLevel": "None",
|
||||
"files.associations": {
|
||||
"xstring": "cpp",
|
||||
"*.idl": "cpp",
|
||||
"array": "cpp",
|
||||
"future": "cpp",
|
||||
"istream": "cpp",
|
||||
"memory": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"variant": "cpp",
|
||||
"xlocmes": "cpp",
|
||||
"xlocmon": "cpp",
|
||||
"xlocnum": "cpp",
|
||||
"xloctime": "cpp",
|
||||
"multi_span": "cpp",
|
||||
"pointers": "cpp",
|
||||
"vector": "cpp",
|
||||
"bitset": "cpp",
|
||||
"deque": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"list": "cpp",
|
||||
"queue": "cpp",
|
||||
"random": "cpp",
|
||||
"regex": "cpp",
|
||||
"stack": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xtree": "cpp",
|
||||
"xutility": "cpp",
|
||||
"span": "cpp",
|
||||
"string_span": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"cctype": "cpp",
|
||||
"charconv": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"complex": "cpp",
|
||||
"concepts": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"exception": "cpp",
|
||||
"filesystem": "cpp",
|
||||
"fstream": "cpp",
|
||||
"functional": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"ios": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"iterator": "cpp",
|
||||
"limits": "cpp",
|
||||
"locale": "cpp",
|
||||
"map": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"set": "cpp",
|
||||
"shared_mutex": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string": "cpp",
|
||||
"system_error": "cpp",
|
||||
"thread": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"xfacet": "cpp",
|
||||
"xiosbase": "cpp",
|
||||
"xlocale": "cpp",
|
||||
"xlocbuf": "cpp",
|
||||
"xlocinfo": "cpp",
|
||||
"xmemory": "cpp",
|
||||
"xstddef": "cpp",
|
||||
"xtr1common": "cpp"
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/bin/**": true,
|
||||
"**/obj/**": true,
|
||||
"**/packages/**": true,
|
||||
"**/generated files/**": true
|
||||
}
|
||||
}
|
||||
121
.vscode/tasks.json
vendored
121
.vscode/tasks.json
vendored
@@ -1,121 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Build Terminal/Console",
|
||||
"command": "powershell.exe",
|
||||
"args": [
|
||||
"-Command",
|
||||
"Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;",
|
||||
"Set-MsBuildDevEnvironment;",
|
||||
"$project = switch(\"${input:buildProjectChoice}\"){OpenConsole{\"Conhost\\Host_EXE\"} Terminal{\"Terminal\\CascadiaPackage\"} TermControl{\"Terminal\\TerminalControl\"}};",
|
||||
"$target = switch(\"${input:buildModeChoice}\"){Build{\"\"} Rebuild{\":Rebuild\"} Clean{\":Clean\"}};",
|
||||
"$target = $project + $target;",
|
||||
"msbuild",
|
||||
"${workspaceFolder}\\OpenConsole.sln",
|
||||
"/p:Configuration=${input:configChoice}",
|
||||
"/p:Platform=${input:platformChoice}",
|
||||
"/p:AppxSymbolPackageEnabled=false", // This takes a long time, so false if we don't really need it.
|
||||
"/t:$target",
|
||||
"/m", // Parallel builds
|
||||
"/verbosity:minimal"
|
||||
],
|
||||
"problemMatcher": ["$msCompile"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"runOptions": {
|
||||
"reevaluateOnRerun": false,
|
||||
"instanceLimit": 1,
|
||||
"runOn": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Register Windows Terminal x64 Debug",
|
||||
"command": "powershell.exe",
|
||||
"args": [
|
||||
"-Command",
|
||||
"Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;",
|
||||
"Set-MsBuildDevEnvironment;",
|
||||
"Set-Location -Path ${workspaceFolder}\\src\\cascadia\\CascadiaPackage\\AppPackages\\CascadiaPackage_0.0.1.0_x64_Debug_Test;",
|
||||
"if ((Get-AppxPackage -Name 'WindowsTerminalDev*') -ne $null) { Remove-AppxPackage 'WindowsTerminalDev_0.0.1.0_x64__8wekyb3d8bbwe'};",
|
||||
"New-Item ..\\loose -Type Directory -Force;",
|
||||
"makeappx unpack /v /o /p .\\CascadiaPackage_0.0.1.0_x64_Debug.msix /d ..\\Loose\\;",
|
||||
"Add-AppxPackage -Path ..\\loose\\AppxManifest.xml -Register -ForceUpdateFromAnyVersion -ForceApplicationShutdown"
|
||||
],
|
||||
"problemMatcher": ["$msCompile"],
|
||||
"group": {
|
||||
"kind": "build"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Run Windows Terminal Dev",
|
||||
"command": "wtd.exe",
|
||||
"args": [
|
||||
],
|
||||
"problemMatcher": ["$msCompile"],
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Run Code Format",
|
||||
"command": "powershell.exe",
|
||||
"args": [
|
||||
"-Command",
|
||||
"Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;",
|
||||
"Set-MsBuildDevEnvironment;",
|
||||
"Invoke-CodeFormat",
|
||||
],
|
||||
"problemMatcher": ["$msCompile"],
|
||||
}
|
||||
],
|
||||
"inputs":[
|
||||
{
|
||||
"id": "platformChoice",
|
||||
"type": "pickString",
|
||||
"description": "Processor architecture choice",
|
||||
"options":[
|
||||
"x64",
|
||||
"x86",
|
||||
"arm64"
|
||||
],
|
||||
"default": "x64"
|
||||
},
|
||||
{
|
||||
"id": "configChoice",
|
||||
"type": "pickString",
|
||||
"description": "Debug or release?",
|
||||
"options":[
|
||||
"Debug",
|
||||
"Release"
|
||||
],
|
||||
"default": "Debug"
|
||||
},
|
||||
{
|
||||
"id": "buildModeChoice",
|
||||
"type": "pickString",
|
||||
"description": "Build, rebuild, or clean?",
|
||||
"options":[
|
||||
"Build",
|
||||
"Rebuild",
|
||||
"Clean"
|
||||
],
|
||||
"default": "Build"
|
||||
},
|
||||
{
|
||||
"id": "buildProjectChoice",
|
||||
"type": "pickString",
|
||||
"description": "OpenConsole or Terminal?",
|
||||
"options":[
|
||||
"OpenConsole",
|
||||
"Terminal",
|
||||
"TermControl"
|
||||
],
|
||||
"default": "Terminal"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
"Microsoft.Net.Component.4.5.TargetingPack",
|
||||
"Microsoft.VisualStudio.Component.DiagnosticTools",
|
||||
"Microsoft.VisualStudio.Component.Debugger.JustInTime",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.19041",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.18362",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.Support",
|
||||
"Microsoft.VisualStudio.Component.VC.CoreIde",
|
||||
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
|
||||
|
||||
@@ -43,7 +43,7 @@ If no existing item describes your issue/feature, great - please file a new issu
|
||||
* Have a question that you don't see answered in docs, videos, etc.? File an issue
|
||||
* Want to know if we're planning on building a particular feature? File an issue
|
||||
* Got a great idea for a new feature? File an issue/request/idea
|
||||
* Don't understand how to do something? File an issue
|
||||
* Don't understand how to do something? File an issue/Community Guidance Request
|
||||
* Found an existing issue that describes yours? Great - upvote and add additional commentary / info / repro-steps / etc.
|
||||
|
||||
When you hit "New Issue", select the type of issue closest to what you want to report/ask/request:
|
||||
@@ -111,13 +111,13 @@ However, some issues/features will require careful thought & formal design befor
|
||||
|
||||
Specs help collaborators discuss different approaches to solve a problem, describe how the feature will behave, how the feature will impact the user, what happens if something goes wrong, etc. Driving towards agreement in a spec, before any code is written, often results in simpler code, and less wasted effort in the long run.
|
||||
|
||||
Specs will be managed in a very similar manner as code contributions so please follow the "[Fork, Branch and Create your PR](CONTRIBUTING.md#fork-clone-branch-and-create-your-pr)" section below.
|
||||
Specs will be managed in a very similar manner as code contributions so please follow the "Fork, Branch and Create your PR" below.
|
||||
|
||||
### Writing / Contributing-to a Spec
|
||||
|
||||
To write/contribute to a spec: fork, branch and commit via PRs, as you would with any code changes.
|
||||
|
||||
Specs are written in markdown, stored under the [`\doc\specs`](./doc/specs) folder and named `[issue id] - [spec description].md`.
|
||||
Specs are written in markdown, stored under the `\doc\spec` folder and named `[issue id] - [spec description].md`.
|
||||
|
||||
👉 **It is important to follow the spec templates and complete the requested information**. The available spec templates will help ensure that specs contain the minimum information & decisions necessary to permit development to begin. In particular, specs require you to confirm that you've already discussed the issue/idea with the team in an issue and that you provide the issue ID for reference.
|
||||
|
||||
|
||||
46
NOTICE.md
46
NOTICE.md
@@ -251,49 +251,3 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
```
|
||||
|
||||
# Microsoft Open Source
|
||||
|
||||
This product also incorporates source code from other Microsoft open source projects, all licensed under the MIT license.
|
||||
|
||||
## `GSL`
|
||||
|
||||
**Source**: [https://github.com/microsoft/GSL](https://github.com/microsoft/GSL)
|
||||
|
||||
## `Microsoft-UI-XAML`
|
||||
|
||||
**Source**: [https://github.com/microsoft/Microsoft-UI-XAML](https://github.com/microsoft/Microsoft-UI-XAML)
|
||||
|
||||
## `VirtualDesktopUtils`
|
||||
|
||||
**Source**: [https://github.com/microsoft/PowerToys](https://github.com/microsoft/PowerToys)
|
||||
|
||||
## `wil`
|
||||
|
||||
**Source**: [https://github.com/microsoft/wil](https://github.com/microsoft/wil)
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
The MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
821
OpenConsole.sln
821
OpenConsole.sln
File diff suppressed because it is too large
Load Diff
22
README.md
22
README.md
@@ -1,5 +1,3 @@
|
||||

|
||||
|
||||
# Welcome to the Windows Terminal, Console and Command-Line repo
|
||||
|
||||
This repository contains the source code for:
|
||||
@@ -35,23 +33,10 @@ This is our preferred method.
|
||||
|
||||
#### Via GitHub
|
||||
|
||||
For users who are unable to install Windows Terminal from the Microsoft Store,
|
||||
released builds can be manually downloaded from this repository's [Releases
|
||||
For users who are unable to install Terminal from the Microsoft Store, Terminal
|
||||
builds can be manually downloaded from this repository's [Releases
|
||||
page](https://github.com/microsoft/terminal/releases).
|
||||
|
||||
Download the `Microsoft.WindowsTerminal_<versionNumber>.msixbundle` file from
|
||||
the **Assets** section. To install the app, you can simply double-click on the
|
||||
`.msixbundle` file, and the app installer should automatically run. If that
|
||||
fails for any reason, you can try the following command at a PowerShell prompt:
|
||||
|
||||
```powershell
|
||||
# NOTE: If you are using PowerShell 7+, please run
|
||||
# Import-Module Appx -UseWindowsPowerShell
|
||||
# before using Add-AppxPackage.
|
||||
|
||||
Add-AppxPackage Microsoft.WindowsTerminal_<versionNumber>.msixbundle
|
||||
```
|
||||
|
||||
> 🔴 Note: If you install Terminal manually:
|
||||
>
|
||||
> * Terminal will not auto-update when new builds are released so you will need
|
||||
@@ -243,7 +228,7 @@ Visual Studio.
|
||||
|
||||
## Documentation
|
||||
|
||||
All project documentation is located at [aka.ms/terminal-docs](https://aka.ms/terminal-docs). If you would like
|
||||
All project documentation is located at aka.ms/terminal-docs. If you would like
|
||||
to contribute to the documentation, please submit a pull request on the [Windows
|
||||
Terminal Documentation repo](https://github.com/MicrosoftDocs/terminal).
|
||||
|
||||
@@ -278,7 +263,6 @@ If you would like to ask a question that you feel doesn't warrant an issue
|
||||
* Carlos Zamora, Developer: [@cazamor_msft](https://twitter.com/cazamor_msft)
|
||||
* Leon Liang, Developer: [@leonmsft](https://twitter.com/leonmsft)
|
||||
* Pankaj Bhojwani, Developer
|
||||
* Leonard Hecker, Developer: [@LeonardHecker](https://twitter.com/LeonardHecker)
|
||||
|
||||
## Developer Guidance
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"AttributesTolerance": 1,
|
||||
"KeepFirstAttributeOnSameLine": true,
|
||||
"MaxAttributeCharactersPerLine": 0,
|
||||
"MaxAttributesPerLine": 1,
|
||||
"NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter",
|
||||
"SeparateByGroups": false,
|
||||
"AttributeIndentation": 0,
|
||||
"AttributeIndentationStyle": 1,
|
||||
"RemoveDesignTimeReferences": false,
|
||||
"EnableAttributeReordering": true,
|
||||
"AttributeOrderingRuleGroups": [
|
||||
"x:Class",
|
||||
"xmlns, xmlns:x",
|
||||
"xmlns:*",
|
||||
"x:Key, Key, x:Name, Name, x:Uid, Uid, Title",
|
||||
"Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom",
|
||||
"Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight",
|
||||
"Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex",
|
||||
"*:*, *",
|
||||
"PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint",
|
||||
"mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText",
|
||||
"Storyboard.*, From, To, Duration"
|
||||
],
|
||||
"FirstLineAttributes": "",
|
||||
"OrderAttributesByName": true,
|
||||
"PutEndingBracketOnNewLine": false,
|
||||
"RemoveEndingTagOfEmptyElement": true,
|
||||
"SpaceBeforeClosingSlash": true,
|
||||
"RootElementLineBreakRule": 0,
|
||||
"ReorderVSM": 2,
|
||||
"ReorderGridChildren": false,
|
||||
"ReorderCanvasChildren": false,
|
||||
"ReorderSetters": 0,
|
||||
"FormatMarkupExtension": true,
|
||||
"NoNewLineMarkupExtensions": "x:Bind, Binding",
|
||||
"ThicknessSeparator": 2,
|
||||
"ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin",
|
||||
"FormatOnSave": true,
|
||||
"CommentPadding": 2,
|
||||
}
|
||||
@@ -29,147 +29,4 @@ function GetQueryTestRunsUri
|
||||
$baseUri = GetAzureDevOpsBaseUri -CollectionUri $CollectionUri -TeamProject $TeamProject
|
||||
$queryUri = "$baseUri/_apis/test/runs?buildUri=$BuildUri$includeRunDetailsParameter&api-version=5.0"
|
||||
return $queryUri
|
||||
}
|
||||
|
||||
function Get-HelixJobTypeFromTestRun
|
||||
{
|
||||
Param ($testRun)
|
||||
|
||||
$testRunSingleResultUri = "$($testRun.url)/results?`$top=1&`$skip=0&api-version=5.1"
|
||||
$singleTestResult = Invoke-RestMethod -Uri $testRunSingleResultUri -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
$count = $singleTestResult.value.Length
|
||||
if($count -eq 0)
|
||||
{
|
||||
# If the count is 0, then results have not yet been reported for this run.
|
||||
# We only care about completed runs with results, so it is ok to just return 'UNKNOWN' for this run.
|
||||
return "UNKNOWN"
|
||||
}
|
||||
else
|
||||
{
|
||||
$info = ConvertFrom-Json $singleTestResult.value.comment
|
||||
$helixJobId = $info.HelixJobId
|
||||
$job = Invoke-RestMethodWithRetries "https://helix.dot.net/api/2019-06-17/jobs/${helixJobId}?access_token=${HelixAccessToken}"
|
||||
return $job.Type
|
||||
}
|
||||
}
|
||||
|
||||
function Append-HelixAccessTokenToUrl
|
||||
{
|
||||
Param ([string]$url, [string]$token)
|
||||
if($url.Contains("?"))
|
||||
{
|
||||
$url = "$($url)&access_token=$($token)"
|
||||
}
|
||||
else
|
||||
{
|
||||
$url = "$($url)?access_token=$($token)"
|
||||
}
|
||||
return $url
|
||||
}
|
||||
|
||||
|
||||
# The Helix Rest api is sometimes unreliable. So we call these apis with retry logic.
|
||||
# Note: The Azure DevOps apis are stable and do not need to be called with this retry logic.
|
||||
$helixApiRetries = 0
|
||||
$helixApiRetriesMax = 10
|
||||
|
||||
function Download-StringWithRetries
|
||||
{
|
||||
Param ([string]$fileName, [string]$url)
|
||||
|
||||
$result = ""
|
||||
$done = $false
|
||||
|
||||
while(!($done))
|
||||
{
|
||||
try
|
||||
{
|
||||
Write-Host "Downloading $fileName"
|
||||
$result = (New-Object System.Net.WebClient).DownloadString($url)
|
||||
$done = $true
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Host "Failed to download $fileName $($PSItem.Exception)"
|
||||
|
||||
$helixApiRetries = $helixApiRetries + 1
|
||||
if($helixApiRetries -lt $helixApiRetriesMax)
|
||||
{
|
||||
Write-Host "Sleep and retry download of $fileName"
|
||||
Start-Sleep 60
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Failed to download $fileName"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Invoke-RestMethodWithRetries
|
||||
{
|
||||
Param ([string]$url,$Headers)
|
||||
|
||||
$result = @()
|
||||
$done = $false
|
||||
|
||||
while(!($done))
|
||||
{
|
||||
try
|
||||
{
|
||||
$result = Invoke-RestMethod -Uri $url -Method Get -Headers $Headers
|
||||
$done = $true
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Host "Failed to invoke Rest method $($PSItem.Exception)"
|
||||
|
||||
$helixApiRetries = $helixApiRetries + 1
|
||||
if($helixApiRetries -lt $helixApiRetriesMax)
|
||||
{
|
||||
Write-Host "Sleep and retry invoke"
|
||||
Start-Sleep 60
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Failed to invoke Rest method"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Download-FileWithRetries
|
||||
{
|
||||
Param ([string]$fileurl, [string]$destination)
|
||||
|
||||
$done = $false
|
||||
|
||||
while(!($done))
|
||||
{
|
||||
try
|
||||
{
|
||||
Write-Host "Downloading $destination"
|
||||
$webClient.DownloadFile($fileurl, $destination)
|
||||
$done = $true
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Host "Failed to download $destination $($PSItem.Exception)"
|
||||
|
||||
$helixApiRetries = $helixApiRetries + 1
|
||||
if($helixApiRetries -lt $helixApiRetriesMax)
|
||||
{
|
||||
Write-Host "Sleep and retry download of $destination"
|
||||
Start-Sleep 60
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Failed to download $destination"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,11 @@ 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\Microsoft.Taef.10.58.210305002\build\Binaries\$Platform\*" $payloadDir
|
||||
Copy-Item "$nugetPackagesDir\Microsoft.Taef.10.58.210305002\build\Binaries\$Platform\NetFx4.5\*" $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\"
|
||||
New-Item -ItemType Directory -Force -Path "$payloadDir\content\"
|
||||
Copy-Item "$nugetPackagesDir\Microsoft.Internal.Windows.Terminal.TestContent.1.0.1\content\*" "$payloadDir\content\"
|
||||
|
||||
function Copy-If-Exists
|
||||
{
|
||||
@@ -54,13 +52,3 @@ 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"
|
||||
|
||||
# Copy the APPX package from the 'drop' artifact dir
|
||||
Copy-Item "$repoDirectory\Artifacts\$ArtifactName\appx\CascadiaPackage_0.0.1.0_$Platform.msix" $payloadDir\CascadiaPackage.zip
|
||||
|
||||
# Rename it to extension of ZIP because Expand-Archive is real sassy on the build machines
|
||||
# and refuses to unzip it because of its file extension while on a desktop, it just
|
||||
# does the job without complaining.
|
||||
|
||||
# Extract the APPX package
|
||||
Expand-Archive -LiteralPath $payloadDir\CascadiaPackage.zip -DestinationPath $payloadDir\appx
|
||||
|
||||
@@ -9,6 +9,11 @@ Param(
|
||||
|
||||
$helixLinkFile = "$OutputFolder\LinksToHelixTestFiles.html"
|
||||
|
||||
$accessTokenParam = ""
|
||||
if($HelixAccessToken)
|
||||
{
|
||||
$accessTokenParam = "?access_token=$HelixAccessToken"
|
||||
}
|
||||
|
||||
function Generate-File-Links
|
||||
{
|
||||
@@ -40,69 +45,66 @@ $azureDevOpsRestApiHeaders = @{
|
||||
$queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri -IncludeRunDetails
|
||||
Write-Host "queryUri = $queryUri"
|
||||
|
||||
$testRuns = Invoke-RestMethodWithRetries $queryUri -Headers $azureDevOpsRestApiHeaders
|
||||
$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)
|
||||
{
|
||||
Write-Host "testRunUri = $testRun.url"
|
||||
$testResults = Invoke-RestMethodWithRetries "$($testRun.url)/results?api-version=5.0" -Headers $azureDevOpsRestApiHeaders
|
||||
$testResults = Invoke-RestMethod -Uri "$($testRun.url)/results?api-version=5.0" -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
$isTestRunNameShown = $false
|
||||
|
||||
foreach ($testResult in $testResults.value)
|
||||
{
|
||||
$info = ConvertFrom-Json $testResult.comment
|
||||
$helixJobId = $info.HelixJobId
|
||||
$helixWorkItemName = $info.HelixWorkItemName
|
||||
|
||||
$workItem = "$helixJobId-$helixWorkItemName"
|
||||
|
||||
Write-Host "Helix Work Item = $workItem"
|
||||
|
||||
if (-not $workItems.Contains($workItem))
|
||||
if ("comment" -in $testResult)
|
||||
{
|
||||
$workItems.Add($workItem)
|
||||
$filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files$accessTokenParam"
|
||||
$files = Invoke-RestMethodWithRetries $filesQueryUri
|
||||
$info = ConvertFrom-Json $testResult.comment
|
||||
$helixJobId = $info.HelixJobId
|
||||
$helixWorkItemName = $info.HelixWorkItemName
|
||||
|
||||
$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)
|
||||
$workItem = "$helixJobId-$helixWorkItemName"
|
||||
|
||||
if (-not $workItems.Contains($workItem))
|
||||
{
|
||||
if(-Not $isTestRunNameShown)
|
||||
$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)
|
||||
{
|
||||
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 = $testResult.automatedTestName.Split('.')[0]
|
||||
$archPath = $testResult.automatedTestName.Split('.')[1]
|
||||
$fileName = $pgcFile.Name
|
||||
$fullPath = "$OutputFolder\PGO\$flavorPath\$archPath"
|
||||
$destination = "$fullPath\$fileName"
|
||||
|
||||
Write-Host "Copying $($pgcFile.Name) to $destination"
|
||||
|
||||
if (-Not (Test-Path $fullPath))
|
||||
if(-Not $isTestRunNameShown)
|
||||
{
|
||||
New-Item $fullPath -ItemType Directory
|
||||
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"
|
||||
|
||||
$link = $pgcFile.Link
|
||||
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 "Downloading $link to $destination"
|
||||
Write-Host "Copying $($pgcFile.Name) to $destination"
|
||||
|
||||
Download-FileWithRetries $link $destination
|
||||
if (-Not (Test-Path $fullPath))
|
||||
{
|
||||
New-Item $fullPath -ItemType Directory
|
||||
}
|
||||
|
||||
$link = "$($pgcFile.Link)$accessTokenParam"
|
||||
$webClient.DownloadFile($link, $destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<!-- These .proj files are generated by the build machine prior to running tests via GenerateTestProjFile.ps1. -->
|
||||
<Import Project="$(ProjFilesPath)\$(Configuration)\$(Platform)\RunTestsInHelix-TerminalAppLocalTests.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
|
||||
<Import Project="$(ProjFilesPath)\$(Configuration)\$(Platform)\RunTestsInHelix-SettingsModelLocalTests.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
|
||||
<Import Project="$(ProjFilesPath)\$(Configuration)\$(Platform)\RunTestsInHelix-HostTestsUIA.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
|
||||
<Import Project="$(ProjFilesPath)\$(Configuration)\$(Platform)\RunTestsInHelix-WindowsTerminalUIATests.proj" Condition=" '$(TestSuite)'=='PgoInstrumentationSuite' " />
|
||||
<Import Project="$(ProjFilesPath)\RunTestsInHelix-TerminalAppLocalTests.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
|
||||
<Import Project="$(ProjFilesPath)\RunTestsInHelix-HostTestsUIA.proj" Condition=" '$(TestSuite)'=='DevTestSuite' " />
|
||||
</Project>
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
|
||||
<package id="Microsoft.Internal.Windows.Terminal.TestContent" version="1.0.1" />
|
||||
<package id="Microsoft.Taef" version="10.58.210305002" 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" />
|
||||
|
||||
@@ -28,7 +28,7 @@ echo %TIME%
|
||||
powershell -ExecutionPolicy Bypass .\InstallTestAppDependencies.ps1
|
||||
echo %TIME%
|
||||
|
||||
set testBinaryCandidates=TerminalApp.LocalTests.dll SettingsModel.LocalTests.dll Conhost.UIA.Tests.dll WindowsTerminal.UIA.Tests.dll
|
||||
set testBinaryCandidates=TerminalApp.LocalTests.dll SettingsModel.LocalTests.dll Conhost.UIA.Tests.dll
|
||||
set testBinaries=
|
||||
for %%B in (%testBinaryCandidates%) do (
|
||||
if exist %%B (
|
||||
@@ -46,6 +46,7 @@ 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=
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="MUXCustomBuildTasks" version="1.0.48" targetFramework="native" />
|
||||
<package id="Microsoft.Taef" version="10.58.210305002" targetFramework="native" />
|
||||
<package id="TAEF.Redist.Wlk" version="10.57.200731005-develop" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
variables:
|
||||
- name: runCodesignValidationInjectionBG
|
||||
value: false
|
||||
|
||||
# 0.0.yyMM.dd##
|
||||
# 0.0.1904.0900
|
||||
name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr)
|
||||
|
||||
stages:
|
||||
- stage: Build_x64
|
||||
displayName: Build x64
|
||||
dependsOn: []
|
||||
condition: succeeded()
|
||||
jobs:
|
||||
- template: ./templates/build-console-pgo.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
- stage: Publish_PGO_Databases
|
||||
displayName: Publish PGO databases
|
||||
dependsOn: ['Build_x64']
|
||||
jobs:
|
||||
- template: ./templates/pgo-build-and-publish-nuget-job.yml
|
||||
parameters:
|
||||
pgoArtifact: 'PGO'
|
||||
@@ -20,12 +20,6 @@ jobs:
|
||||
parameters:
|
||||
additionalBuildArguments: ${{ parameters.additionalBuildArguments }}
|
||||
|
||||
# It appears that the Component Governance build task that gets automatically injected stopped working
|
||||
# when we renamed our main branch.
|
||||
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
|
||||
displayName: 'Component Detection'
|
||||
condition: and(succeededOrFailed(), not(eq(variables['Build.Reason'], 'PullRequest')))
|
||||
|
||||
- template: helix-runtests-job.yml
|
||||
parameters:
|
||||
name: 'RunTestsInHelix'
|
||||
|
||||
@@ -9,7 +9,6 @@ jobs:
|
||||
variables:
|
||||
BuildConfiguration: ${{ parameters.configuration }}
|
||||
BuildPlatform: ${{ parameters.platform }}
|
||||
PGOBuildMode: 'Optimize'
|
||||
|
||||
pool:
|
||||
name: Package ES Lab E
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
parameters:
|
||||
configuration: 'Release'
|
||||
platform: ''
|
||||
additionalBuildArguments: ''
|
||||
minimumExpectedTestsExecutedCount: 1 # Sanity check for minimum expected tests to be reported
|
||||
rerunPassesRequiredToAvoidFailure: 0
|
||||
|
||||
jobs:
|
||||
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }}
|
||||
variables:
|
||||
BuildConfiguration: ${{ parameters.configuration }}
|
||||
BuildPlatform: ${{ parameters.platform }}
|
||||
PGOBuildMode: 'Instrument'
|
||||
pool: "windevbuildagents"
|
||||
# The public pool is also an option!
|
||||
# pool: { vmImage: windows-2019 }
|
||||
|
||||
steps:
|
||||
- template: build-console-steps.yml
|
||||
parameters:
|
||||
additionalBuildArguments: ${{ parameters.additionalBuildArguments }}
|
||||
|
||||
- template: helix-runtests-job.yml
|
||||
parameters:
|
||||
name: 'RunTestsInHelix'
|
||||
dependsOn: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
condition: succeeded()
|
||||
testSuite: 'PgoInstrumentationSuite'
|
||||
taefQuery: '@IsPgo=true'
|
||||
configuration: ${{ parameters.configuration }}
|
||||
platform: ${{ parameters.platform }}
|
||||
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
|
||||
|
||||
- template: helix-processtestresults-job.yml
|
||||
parameters:
|
||||
name: 'ProcessTestResults'
|
||||
pgoArtifact: 'PGO'
|
||||
dependsOn:
|
||||
- RunTestsInHelix
|
||||
condition: succeededOrFailed()
|
||||
rerunPassesRequiredToAvoidFailure: ${{ parameters.rerunPassesRequiredToAvoidFailure }}
|
||||
minimumExpectedTestsExecutedCount: ${{ parameters.minimumExpectedTestsExecutedCount }}
|
||||
|
||||
- template: pgo-merge-pgd-job.yml
|
||||
parameters:
|
||||
name: 'MergePGD'
|
||||
dependsOn:
|
||||
- ProcessTestResults
|
||||
pgoArtifact: 'PGO'
|
||||
platform: ${{ parameters.platform }}
|
||||
@@ -22,7 +22,7 @@ steps:
|
||||
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:
|
||||
@@ -32,29 +32,6 @@ steps:
|
||||
restoreSolution: build/packages.config
|
||||
restoreDirectory: '$(Build.SourcesDirectory)\packages'
|
||||
|
||||
# The environment variable VCToolsInstallDir isn't defined on lab machines, so we need to retrieve it ourselves.
|
||||
- script: |
|
||||
"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -Latest -requires Microsoft.Component.MSBuild -property InstallationPath > %TEMP%\vsinstalldir.txt
|
||||
set /p _VSINSTALLDIR15=<%TEMP%\vsinstalldir.txt
|
||||
del %TEMP%\vsinstalldir.txt
|
||||
call "%_VSINSTALLDIR15%\Common7\Tools\VsDevCmd.bat"
|
||||
echo VCToolsInstallDir = %VCToolsInstallDir%
|
||||
echo ##vso[task.setvariable variable=VCToolsInstallDir]%VCToolsInstallDir%
|
||||
displayName: 'Retrieve VC tools directory'
|
||||
|
||||
- task: CmdLine@1
|
||||
displayName: 'Display build machine environment variables'
|
||||
inputs:
|
||||
filename: 'set'
|
||||
|
||||
- task: powershell@2
|
||||
displayName: 'Restore PGO database'
|
||||
condition: eq(variables['PGOBuildMode'], 'Optimize')
|
||||
inputs:
|
||||
targetType: filePath
|
||||
workingDirectory: $(Build.SourcesDirectory)\tools\PGODatabase
|
||||
filePath: $(Build.SourcesDirectory)\tools\PGODatabase\restore-pgodb.ps1
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\OpenConsole.sln'
|
||||
inputs:
|
||||
@@ -68,9 +45,6 @@ steps:
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Check MSIX for common regressions'
|
||||
# PGO runtime needs its own CRT and it's in the package for convenience.
|
||||
# That will make this script mad so skip since we're not shipping the PGO Instrumentation one anyway.
|
||||
condition: ne(variables['PGOBuildMode'], 'Instrument')
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
@@ -79,7 +53,6 @@ steps:
|
||||
|
||||
- task: powershell@2
|
||||
displayName: 'Source Index PDBs'
|
||||
condition: ne(variables['PGOBuildMode'], 'Instrument')
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Index-Pdbs.ps1
|
||||
@@ -95,25 +68,13 @@ steps:
|
||||
If ($Arch -Eq "x86") { $Arch = "Win32" }
|
||||
Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}"
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Validate binaries are optimized'
|
||||
condition: eq(variables['pgoBuildMode'], 'Optimize')
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
$Binaries = 'OpenConsole.exe', 'WindowsTerminal.exe', 'TerminalApp.dll', 'TerminalConnection.dll', 'Microsoft.Terminal.Control.dll', 'Microsoft.Terminal.Remoting.dll', 'Microsoft.Terminal.Settings.Editor.dll', 'Microsoft.Terminal.Settings.Model.dll'
|
||||
foreach ($BinFile in $Binaries)
|
||||
{
|
||||
& "$(Build.SourcesDirectory)\tools\PGODatabase\verify-pgo.ps1" "$(Build.SourcesDirectory)/bin/$(RationalizedBuildPlatform)/$(BuildConfiguration)/$BinFile"
|
||||
}
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Run Unit Tests'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}'
|
||||
condition: and(and(succeeded(), ne(variables['PGOBuildMode'], 'Instrument')), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
|
||||
condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Run Feature Tests (x64 only)'
|
||||
@@ -121,7 +82,7 @@ steps:
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Run-Tests.ps1
|
||||
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}'
|
||||
condition: and(and(succeeded(), ne(variables['PGOBuildMode'], 'Instrument')), eq(variables['BuildPlatform'], 'x64'))
|
||||
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Convert Test Logs from WTL to xUnit format'
|
||||
@@ -129,14 +90,13 @@ steps:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\ConvertWttLogToXUnit.ps1
|
||||
arguments: -WttInputPath '${{ parameters.testLogPath }}' -WttSingleRerunInputPath 'unused.wtl' -WttMultipleRerunInputPath 'unused2.wtl' -XUnitOutputPath 'onBuildMachineResults.xml' -TestNamePrefix '$(BuildConfiguration).$(BuildPlatform)'
|
||||
condition: and(ne(variables['PGOBuildMode'], 'Instrument'),or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
|
||||
condition: or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86'))
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: 'Upload converted test logs'
|
||||
condition: ne(variables['PGOBuildMode'], 'Instrument')
|
||||
inputs:
|
||||
testResultsFormat: 'xUnit' # Options: JUnit, NUnit, VSTest, xUnit, cTest
|
||||
testResultsFiles: '**/onBuildMachineResults.xml'
|
||||
testResultsFiles: '**/onBuildMachineResults.xml'
|
||||
#searchFolder: '$(System.DefaultWorkingDirectory)' # Optional
|
||||
#mergeTestResults: false # Optional
|
||||
#failTaskOnFailedTests: false # Optional
|
||||
@@ -167,47 +127,24 @@ steps:
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/appx'
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
condition: succeeded()
|
||||
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/$(RationalizedBuildPlatform)/$(BuildConfiguration)/*.exe
|
||||
$(Build.SourcesDirectory)/bin/$(RationalizedBuildPlatform)/$(BuildConfiguration)/*.dll
|
||||
$(Build.SourcesDirectory)/bin/$(RationalizedBuildPlatform)/$(BuildConfiguration)/*.xml
|
||||
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.exe
|
||||
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.dll
|
||||
$(Build.SourcesDirectory)/bin/$(BuildPlatform)/$(BuildConfiguration)/*.xml
|
||||
**/Microsoft.VCLibs.*.appx
|
||||
**/TestHostApp/*.exe
|
||||
**/TestHostApp/*.dll
|
||||
**/TestHostApp/*.xml
|
||||
!**/*.pdb
|
||||
!**/*.ipdb
|
||||
!**/*.obj
|
||||
!**/*.pch
|
||||
**/TestHostApp/*
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/$(BuildPlatform)/test'
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
condition: succeeded()
|
||||
condition: and(and(succeeded(), eq(variables['BuildPlatform'], 'x64')), ne(variables['Build.Reason'], 'PullRequest'))
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish All Build Artifacts'
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'drop'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy PGO databases needed for PGO instrumentation run'
|
||||
inputs:
|
||||
Contents: |
|
||||
**/*.pgd
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/PGO/$(BuildPlatform)'
|
||||
OverWrite: true
|
||||
flattenFolders: true
|
||||
condition: and(succeeded(), eq(variables['PGOBuildMode'], 'Instrument'))
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish All PGO Artifacts'
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(BuildConfiguration)/PGO'
|
||||
ArtifactName: 'PGO'
|
||||
condition: and(succeeded(), eq(variables['PGOBuildMode'], 'Instrument'))
|
||||
ArtifactName: 'drop'
|
||||
@@ -12,4 +12,4 @@ steps:
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\GenerateTestProjFile.ps1
|
||||
arguments: -TestFile '${{ parameters.testFilePath }}' -OutputProjFile '$(Build.ArtifactStagingDirectory)\$(BuildConfiguration)\$(BuildPlatform)\${{ parameters.outputProjFileName }}' -JobTestSuiteName '${{ parameters.testSuite }}' -TaefPath '$(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.58.210305002\build\Binaries\x86' -TaefQuery '${{ parameters.taefQuery }}'
|
||||
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 }}'
|
||||
@@ -10,11 +10,19 @@ parameters:
|
||||
maxParallel: 4
|
||||
rerunPassesRequiredToAvoidFailure: 5
|
||||
taefQuery: ''
|
||||
configuration: ''
|
||||
platform: ''
|
||||
# if 'useBuildOutputFromBuildId' is set, we will default to using a build from this pipeline:
|
||||
useBuildOutputFromPipeline: $(System.DefinitionId)
|
||||
openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml'
|
||||
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 }}
|
||||
@@ -25,12 +33,10 @@ jobs:
|
||||
timeoutInMinutes: 120
|
||||
strategy:
|
||||
maxParallel: ${{ parameters.maxParallel }}
|
||||
matrix: ${{ parameters.matrix }}
|
||||
variables:
|
||||
buildConfiguration: ${{ parameters.configuration }}
|
||||
buildPlatform: ${{ parameters.platform }}
|
||||
openHelixTargetQueues: ${{ parameters.openHelixTargetQueues }}
|
||||
artifactsDir: $(Build.SourcesDirectory)\Artifacts
|
||||
taefPath: $(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.58.210305002\build\Binaries\$(buildPlatform)
|
||||
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}}'
|
||||
|
||||
|
||||
@@ -91,17 +97,9 @@ jobs:
|
||||
filename: 'dir'
|
||||
arguments: '/s $(Build.SourcesDirectory)\HelixPayload'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Make artifact directories'
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
New-Item -ItemType Directory -Force -Path "$(Build.ArtifactStagingDirectory)\$(BuildConfiguration)\"
|
||||
New-Item -ItemType Directory -Force -Path "$(Build.ArtifactStagingDirectory)\$(BuildConfiguration)\$(BuildPlatform)\"
|
||||
|
||||
- template: helix-createprojfile-steps.yml
|
||||
parameters:
|
||||
condition: and(succeeded(),eq('${{ parameters.testSuite }}','DevTestSuite'))
|
||||
condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite'))
|
||||
testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\TerminalApp.LocalTests.dll'
|
||||
outputProjFileName: 'RunTestsInHelix-TerminalAppLocalTests.proj'
|
||||
testSuite: '${{ parameters.testSuite }}'
|
||||
@@ -109,7 +107,7 @@ jobs:
|
||||
|
||||
- template: helix-createprojfile-steps.yml
|
||||
parameters:
|
||||
condition: and(succeeded(),eq('${{ parameters.testSuite }}','DevTestSuite'))
|
||||
condition: and(succeeded(),ne('${{ parameters.testSuite }}','NugetTestSuite'))
|
||||
testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\SettingsModel.LocalTests.dll'
|
||||
outputProjFileName: 'RunTestsInHelix-SettingsModelLocalTests.proj'
|
||||
testSuite: '${{ parameters.testSuite }}'
|
||||
@@ -118,20 +116,12 @@ jobs:
|
||||
|
||||
- template: helix-createprojfile-steps.yml
|
||||
parameters:
|
||||
condition: and(succeeded(),eq('${{ parameters.testSuite }}','DevTestSuite'))
|
||||
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 }}
|
||||
|
||||
- template: helix-createprojfile-steps.yml
|
||||
parameters:
|
||||
condition: and(succeeded(),or(eq('${{ parameters.testSuite }}','PgoInstrumentationSuite'),eq('${{ parameters.testSuite }}','DevTestSuite')))
|
||||
testFilePath: '$(artifactsDir)\${{ parameters.artifactName }}\$(buildConfiguration)\$(buildPlatform)\Test\WindowsTerminal.UIA.Tests.dll'
|
||||
outputProjFileName: 'RunTestsInHelix-WindowsTerminalUIATests.proj'
|
||||
testSuite: '${{ parameters.testSuite }}'
|
||||
taefQuery: ${{ parameters.taefQuery }}
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish generated .proj files'
|
||||
inputs:
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# From our friends at MUX: https://github.com/microsoft/microsoft-ui-xaml/blob/main/build/AzurePipelinesTemplates/MUX-BuildAndPublishPGONuGet-Job.yml
|
||||
|
||||
parameters:
|
||||
dependsOn: ''
|
||||
pgoArtifact: PGO
|
||||
|
||||
jobs:
|
||||
- job: BuildAndPublishPGONuGet
|
||||
dependsOn: ${{ parameters.dependsOn }}
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
variables:
|
||||
artifactsPath: $(Build.SourcesDirectory)\Artifacts
|
||||
pgoToolsPath: $(Build.SourcesDirectory)\tools\PGODatabase
|
||||
nuspecPath: $(pgoToolsPath)\NuSpecs
|
||||
nuspecFilename: PGO.nuspec
|
||||
|
||||
steps:
|
||||
- task: DownloadBuildArtifacts@0
|
||||
inputs:
|
||||
artifactName: ${{ parameters.pgoArtifact }}
|
||||
downloadPath: $(artifactsPath)
|
||||
|
||||
- task: NuGetToolInstaller@0
|
||||
displayName: 'Use NuGet 5.2.0'
|
||||
inputs:
|
||||
versionSpec: 5.2.0
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy pgd files to NuGet build directory'
|
||||
inputs:
|
||||
sourceFolder: $(artifactsPath)\${{ parameters.pgoArtifact }}
|
||||
contents: '**\*.pgd'
|
||||
targetFolder: $(nuspecPath)\tools
|
||||
|
||||
- task: powershell@2
|
||||
displayName: 'Generate NuSpec file'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: $(pgoToolsPath)\generate-nuspec.ps1
|
||||
workingDirectory: $(pgoToolsPath)
|
||||
arguments: $(nuspecPath)\$(nuspecFilename).template $(nuspecPath)\$(nuspecFilename)
|
||||
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: 'NuGet pack'
|
||||
inputs:
|
||||
command: pack
|
||||
packagesToPack: '$(nuspecPath)\$(nuspecFilename)'
|
||||
basePath: '$(nuspecPath)'
|
||||
packDestination: '$(Build.ArtifactStagingDirectory)'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathToPublish: $(Build.ArtifactStagingDirectory)
|
||||
artifactName: ${{ parameters.pgoArtifact }}
|
||||
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: 'NuGet push'
|
||||
inputs:
|
||||
command: push
|
||||
publishVstsFeed: Terminal/TerminalDependencies
|
||||
packagesToPush: $(Build.ArtifactStagingDirectory)/*.nupkg
|
||||
@@ -1,90 +0,0 @@
|
||||
parameters:
|
||||
dependsOn: ''
|
||||
pgoArtifact: PGO
|
||||
platform: ''
|
||||
|
||||
jobs:
|
||||
- job: MergePGD
|
||||
dependsOn: ${{ parameters.dependsOn }}
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
variables:
|
||||
artifactsPath: $(Build.SourcesDirectory)\Artifacts
|
||||
pgoArtifactsPath: $(artifactsPath)\${{ parameters.pgoArtifact }}
|
||||
buildPlatform: ${{ parameters.platform }}
|
||||
|
||||
steps:
|
||||
# The environment variable VCToolsInstallDir isn't defined on lab machines, so we need to retrieve it ourselves.
|
||||
- script: |
|
||||
"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -Latest -requires Microsoft.Component.MSBuild -property InstallationPath > %TEMP%\vsinstalldir.txt
|
||||
set /p _VSINSTALLDIR15=<%TEMP%\vsinstalldir.txt
|
||||
del %TEMP%\vsinstalldir.txt
|
||||
call "%_VSINSTALLDIR15%\Common7\Tools\VsDevCmd.bat"
|
||||
echo VCToolsInstallDir = %VCToolsInstallDir%
|
||||
echo ##vso[task.setvariable variable=VCToolsInstallDir]%VCToolsInstallDir%
|
||||
displayName: 'Retrieve VC tools directory'
|
||||
|
||||
- task: DownloadBuildArtifacts@0
|
||||
inputs:
|
||||
artifactName: ${{ parameters.pgoArtifact }}
|
||||
downloadPath: $(artifactsPath)
|
||||
|
||||
- script: |
|
||||
cd $(buildPlatform)
|
||||
"%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge WindowsTerminal*.pgc WindowsTerminal.pgd
|
||||
displayName: 'Merge Terminal pgc files into pgd'
|
||||
workingDirectory: $(pgoArtifactsPath)
|
||||
|
||||
- script: |
|
||||
cd $(buildPlatform)
|
||||
"%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge OpenConsole*.pgc OpenConsole.pgd
|
||||
displayName: 'Merge OpenConsole pgc files into pgd'
|
||||
workingDirectory: $(pgoArtifactsPath)
|
||||
|
||||
- script: |
|
||||
cd $(buildPlatform)
|
||||
"%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge Microsoft.Terminal.Control*.pgc Microsoft.Terminal.Control.pgd
|
||||
displayName: 'Merge Microsoft.Terminal.Control pgc files into pgd'
|
||||
workingDirectory: $(pgoArtifactsPath)
|
||||
|
||||
- script: |
|
||||
cd $(buildPlatform)
|
||||
"%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge Microsoft.Terminal.Remoting*.pgc Microsoft.Terminal.Remoting.pgd
|
||||
displayName: 'Merge Microsoft.Terminal.Remoting pgc files into pgd'
|
||||
workingDirectory: $(pgoArtifactsPath)
|
||||
|
||||
- script: |
|
||||
cd $(buildPlatform)
|
||||
"%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge Microsoft.Terminal.Settings.Editor*.pgc Microsoft.Terminal.Settings.Editor.pgd
|
||||
displayName: 'Merge Microsoft.Terminal.Settings.Editor pgc files into pgd'
|
||||
workingDirectory: $(pgoArtifactsPath)
|
||||
|
||||
- script: |
|
||||
cd $(buildPlatform)
|
||||
"%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge Microsoft.Terminal.Settings.Model*.pgc Microsoft.Terminal.Settings.Model.pgd
|
||||
displayName: 'Merge Microsoft.Terminal.Settings.Model pgc files into pgd'
|
||||
workingDirectory: $(pgoArtifactsPath)
|
||||
|
||||
- script: |
|
||||
cd $(buildPlatform)
|
||||
"%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge TerminalApp*.pgc TerminalApp.pgd
|
||||
displayName: 'Merge TerminalApp pgc files into pgd'
|
||||
workingDirectory: $(pgoArtifactsPath)
|
||||
|
||||
- script: |
|
||||
cd $(buildPlatform)
|
||||
"%VCToolsInstallDir%\bin\hostx64\x64\pgomgr.exe" /merge TerminalConnection*.pgc TerminalConnection.pgd
|
||||
displayName: 'Merge TerminalConnection pgc files into pgd'
|
||||
workingDirectory: $(pgoArtifactsPath)
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy merged pgd to artifact staging'
|
||||
inputs:
|
||||
sourceFolder: $(pgoArtifactsPath)
|
||||
contents: '**\$(buildPlatform)\*.pgd'
|
||||
targetFolder: $(Build.ArtifactStagingDirectory)
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathToPublish: $(Build.ArtifactStagingDirectory)
|
||||
artifactName: ${{ parameters.pgoArtifact }}
|
||||
@@ -1,97 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- THIS PROJECT CANNOT BE LOADED INTO THE SOLUTION. -->
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Release|Any CPU">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Fuzzing|Any CPU">
|
||||
<Configuration>Fuzzing</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="AuditMode|Any CPU">
|
||||
<Configuration>AuditMode</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|Any CPU">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>d97c3c61-53cd-4e72-919b-9a0940e038f9</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<IntermediateOutputPath>$(SolutionDir)obj\$(Configuration)\GenerateFeatureFlags\</IntermediateOutputPath>
|
||||
<OpenConsoleCommonOutDir>$(SolutionDir)bin\$(Configuration)\</OpenConsoleCommonOutDir>
|
||||
|
||||
<_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Preview'">Preview</_WTBrandingName>
|
||||
<_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Release'">Release</_WTBrandingName>
|
||||
<_WTBrandingName Condition="'$(_WTBrandingName)'==''">Dev</_WTBrandingName>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="_GenerateBranchAndBrandingCache">
|
||||
<Exec Command="git.exe rev-parse --abbrev-ref HEAD"
|
||||
CustomWarningRegularExpression="^fatal:.*"
|
||||
ConsoleToMsBuild="true"
|
||||
IgnoreExitCode="true">
|
||||
<Output TaskParameter="ConsoleOutput" ItemName="_GitBranchLines" />
|
||||
</Exec>
|
||||
|
||||
<ItemGroup>
|
||||
<_BrandingLines Include="$(_WTBrandingName)" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile File="$(IntermediateOutputPath)branch_branding_cache.txt"
|
||||
Lines="@(_GitBranchLines);@(_BrandingLines)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="true" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(IntermediateOutputPath)branch_branding_cache.txt" />
|
||||
<_BranchBrandingCacheFiles Include="$(IntermediateOutputPath)branch_branding_cache.txt" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_RunFeatureFlagScript"
|
||||
Inputs="@(FeatureFlagFile);@(_BranchBrandingCacheFiles)"
|
||||
Outputs="$(OpenConsoleCommonOutDir)\inc\TilFeatureStaging.h"
|
||||
DependsOnTargets="_GenerateBranchAndBrandingCache">
|
||||
<MakeDir Directories="$(OpenConsoleCommonOutDir)\inc" />
|
||||
<Exec
|
||||
Command="powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy ByPass -Command "$(SolutionDir)\tools\Generate-FeatureStagingHeader.ps1" -Path "%(FeatureFlagFile.FullPath)" -Branding $(_WTBrandingName)"
|
||||
ConsoleToMsBuild="true"
|
||||
StandardOutputImportance="low">
|
||||
<Output TaskParameter="ConsoleOutput" ItemName="_FeatureFlagFileLines" />
|
||||
</Exec>
|
||||
|
||||
<!--
|
||||
We gather the feature flag output in MSBuild and emit the file so that we can take advantage of
|
||||
WriteOnlyWhenDifferent. Doing this ensures that we don't rebuild the world when the branch changes
|
||||
(if it results in a new TilFeatureStaging.h that would have had the same content/features as the previous one)
|
||||
-->
|
||||
|
||||
<WriteLinesToFile File="$(OpenConsoleCommonOutDir)\inc\TilFeatureStaging.h"
|
||||
Lines="@(_FeatureFlagFileLines)"
|
||||
Overwrite="true"
|
||||
WriteOnlyWhenDifferent="true" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="$(OpenConsoleCommonOutDir)\inc\TilFeatureStaging.h" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="Build" DependsOnTargets="_RunFeatureFlagScript" />
|
||||
<Target Name="Clean">
|
||||
<Delete Files="$(OpenConsoleCommonOutDir)\inc\TilFeatureStaging.h" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<FeatureFlagFile Include="$(SolutionDir)\src\features.xml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -22,7 +22,7 @@ Param(
|
||||
[Parameter(HelpMessage="Path to makeappx.exe")]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\MakeAppx.exe"
|
||||
$MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\MakeAppx.exe"
|
||||
)
|
||||
|
||||
If ($null -Eq (Get-Item $MakeAppxPath -EA:SilentlyContinue)) {
|
||||
|
||||
@@ -3,24 +3,12 @@
|
||||
# Checks for code formatting errors. Will throw exception if any are found.
|
||||
function Invoke-CheckBadCodeFormatting() {
|
||||
Import-Module ./tools/OpenConsole.psm1
|
||||
|
||||
# Don't run the XAML formatter in this step - even if it changes nothing,
|
||||
# it'll still touch all the .xaml files.
|
||||
Invoke-CodeFormat -IgnoreXaml
|
||||
|
||||
Invoke-CodeFormat
|
||||
# returns a non-zero exit code if there are any diffs in the tracked files in the repo
|
||||
git diff-index --quiet HEAD --
|
||||
if ($lastExitCode -eq 1) {
|
||||
|
||||
# Write the list of files that need updating to the log
|
||||
git diff-index --name-only HEAD
|
||||
|
||||
throw "code formatting bad, run Invoke-CodeFormat on branch"
|
||||
}
|
||||
|
||||
# Manually check the formatting of our .xaml files, without touching them.
|
||||
Test-XamlFormat
|
||||
|
||||
}
|
||||
|
||||
Invoke-CheckBadCodeFormatting
|
||||
|
||||
@@ -8,7 +8,7 @@ Param(
|
||||
[Parameter(HelpMessage="Path to Windows Kit")]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$WindowsKitPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0"
|
||||
$WindowsKitPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -58,7 +58,7 @@ Try {
|
||||
|
||||
### Check the activatable class entries for a few DLLs we need.
|
||||
$inProcServers = $Manifest.Package.Extensions.Extension.InProcessServer.Path
|
||||
$RequiredInProcServers = ("TerminalApp.dll", "Microsoft.Terminal.Control.dll", "Microsoft.Terminal.Remoting.dll", "Microsoft.Terminal.Settings.Editor.dll", "Microsoft.Terminal.Settings.Model.dll", "TerminalConnection.dll")
|
||||
$RequiredInProcServers = ("TerminalApp.dll", "TerminalControl.dll", "TerminalConnection.dll")
|
||||
|
||||
Write-Verbose "InProc Servers: $inProcServers"
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"/.github/",
|
||||
"/samples/",
|
||||
"/res/terminal/",
|
||||
"/res/fonts/",
|
||||
"/doc/specs/",
|
||||
"/doc/cascadia/",
|
||||
"/doc/user-docs/",
|
||||
@@ -39,7 +38,6 @@
|
||||
".wrn",
|
||||
".rec",
|
||||
".err",
|
||||
"XamlStyler.json",
|
||||
".xlsx"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2021</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>9</VersionMinor>
|
||||
<VersionMinor>7</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -12,9 +12,9 @@ Use the [TAEF Verify Macros for C++](https://docs.microsoft.com/en-us/windows-ha
|
||||
|
||||
### Running Tests
|
||||
|
||||
If you have Visual Studio and related C++ components installed, and you have successfully restored NuGets, you should have the TAEF test runner `te.exe` available locally as part of the `Microsoft.Taef` package.
|
||||
If you have Visual Studio and related C++ components installed, and you have successfully restored NuGets, you should have the TAEF test runner `te.exe` available locally as part of the `Taef.Redist.Wlk` package.
|
||||
|
||||
> Note that you cannot easily run TAEF tests directly through Visual Studio. The `Microsoft.Taef` NuGet package comes with an adapter that will let you browse and execute TAEF tests inside of Visual Studio, but its performance and reliability prevent us from recommending it here.
|
||||
> Note that you cannot easily run TAEF tests directly through Visual Studio. The `Taef.Redist.Wlk` NuGet package comes with an adapter that will let you browse and execute TAEF tests inside of Visual Studio, but its performance and reliability prevent us from recommending it here.
|
||||
|
||||
In a "normal" CMD environment, `te.exe` may not be directly available. Try the following command to set up the development enviroment first:
|
||||
|
||||
|
||||
@@ -127,4 +127,4 @@ When a release is created, if the PR ID number is linked inside the release desc
|
||||
- Issue message: 🎉This issue was addressed in #{pull request ID}, which has now been successfully released as {release name} {release version}.🎉"
|
||||
|
||||
## Admin Panel
|
||||
[Here](https://portal.fabricbot.ms/bot/?repo=microsoft/terminal)
|
||||
[Here](https://fabric-cp.azurewebsites.net/bot/)
|
||||
|
||||
@@ -9,13 +9,7 @@ git submodule update --init --recursive
|
||||
|
||||
OpenConsole.sln may be built from within Visual Studio or from the command-line using a set of convenience scripts & tools in the **/tools** directory:
|
||||
|
||||
When using Visual Studio, be sure to set up the path for code formatting. To download the required clang-format.exe file, follow one of the building instructions below and run:
|
||||
```powershell
|
||||
Import-Module .\tools\OpenConsole.psm1
|
||||
Set-MsBuildDevEnvironment
|
||||
Get-Format
|
||||
```
|
||||
After, go to Tools > Options > Text Editor > C++ > Formatting and checking "Use custom clang-format.exe file" in Visual Studio and choose the clang-format.exe in the repository at /packages/clang-format.win-x86.10.0.0/tools/clang-format.exe by clicking "browse" right under the check box.
|
||||
When using Visual Studio, be sure to set up the path for code formatting. This can be done in Visual Studio by going to Tools > Options > Text Editor > C++ > Formatting and checking "Use custom clang-format.exe file" and choosing the clang-format.exe in the repository at /dep/llvm/clang-format.exe by clicking "browse" right under the check box.
|
||||
|
||||
### Building in PowerShell
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ should be working just the same as before.
|
||||
Now that you have a static library project, you can start building your unittest
|
||||
dll. Start by creating a new directory for your unittest code, and creating a
|
||||
`.vcxproj` for a TAEF unittest dll. For the Terminal solution, we use the TAEF
|
||||
nuget package `Microsoft.Taef`.
|
||||
nuget package `Taef.Redist.Wlk`.
|
||||
|
||||
### Referencing your C++/WinRT static lib
|
||||
|
||||
@@ -380,7 +380,7 @@ Here's the AppxManifest we're using:
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug" MinVersion="14.0.27023.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug.UWPDesktop" MinVersion="14.0.27027.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
</Dependencies>
|
||||
@@ -517,7 +517,7 @@ This is because of a few key lines we already put in the appxmanifest:
|
||||
|
||||
```xml
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug" MinVersion="14.0.27023.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug.UWPDesktop" MinVersion="14.0.27027.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
</Dependencies>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
"title": "Microsoft's Windows Terminal Settings Profile Schema",
|
||||
"definitions": {
|
||||
"KeyChordSegment": {
|
||||
"pattern": "^(?<modifier>(?<mod1>ctrl|alt|shift|win)(?:\\+(?<mod2>ctrl|alt|shift|win)(?<!\\k<mod1>))?(?:\\+(?<mod3>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>))?(?:\\+(?<mod4>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>|\\k<mod3>))?\\+)?(?<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+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
|
||||
"type": "string",
|
||||
"description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<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.\napp, menu\tMENU key\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)"
|
||||
},
|
||||
"Color": {
|
||||
"default": "#",
|
||||
@@ -37,8 +37,7 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"audible",
|
||||
"window",
|
||||
"taskbar"
|
||||
"visual"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -46,125 +45,13 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"audible",
|
||||
"taskbar",
|
||||
"window",
|
||||
"visual",
|
||||
"all",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"AppearanceConfig": {
|
||||
"properties": {
|
||||
"colorScheme": {
|
||||
"description": "The name of a color scheme to use when unfocused.",
|
||||
"type": "string"
|
||||
},
|
||||
"foreground": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": "#cccccc",
|
||||
"description": "Sets the text color when unfocused. Overrides \"foreground\" from the color scheme. Uses hex color format: \"#rrggbb\".",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"background": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": "#0c0c0c",
|
||||
"description": "Sets the background color of the text when unfocused. Overrides \"background\" from the color scheme. Uses hex color format: \"#rrggbb\".",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"selectionBackground": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/Color"},
|
||||
{ "type": "null" }
|
||||
],
|
||||
"description": "Sets the background color of selected text when unfocused. Overrides selectionBackground set in the color scheme. Uses hex color format: \"#rrggbb\"."
|
||||
},
|
||||
"cursorColor": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/Color" },
|
||||
{"type": "null"}
|
||||
],
|
||||
"description": "Sets the color of the cursor when unfocused. Overrides the cursor color from the color scheme. Uses hex color format: \"#rrggbb\"."
|
||||
},
|
||||
"cursorShape": {
|
||||
"default": "bar",
|
||||
"description": "Sets the shape of the cursor when unfocused. Possible values:\n -\"bar\" ( ┃, default )\n -\"doubleUnderscore\" ( ‗ )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
|
||||
"enum": [
|
||||
"bar",
|
||||
"doubleUnderscore",
|
||||
"emptyBox",
|
||||
"filledBox",
|
||||
"underscore",
|
||||
"vintage"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"cursorHeight": {
|
||||
"description": "Sets the percentage height of the cursor (when unfocused) starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 1-100.",
|
||||
"maximum": 100,
|
||||
"minimum": 1,
|
||||
"type": ["integer","null"],
|
||||
"default": 25
|
||||
},
|
||||
"backgroundImage": {
|
||||
"description": "Sets the file location of the image to draw over the window background when unfocused.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": ["string", null]
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"desktopWallpaper"
|
||||
]
|
||||
}
|
||||
],
|
||||
"type": [ "string", "null" ]
|
||||
},
|
||||
"backgroundImageOpacity": {
|
||||
"default": 1.0,
|
||||
"description": "Sets the transparency of the background image when unfocused. Accepts floating point values from 0-1.",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0,
|
||||
"type": "number"
|
||||
},
|
||||
"backgroundImageStretchMode": {
|
||||
"default": "uniformToFill",
|
||||
"description": "Sets how the background image is resized to fill the window when unfocused.",
|
||||
"enum": [
|
||||
"fill",
|
||||
"none",
|
||||
"uniform",
|
||||
"uniformToFill"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"backgroundImageAlignment": {
|
||||
"default": "center",
|
||||
"enum": [
|
||||
"bottom",
|
||||
"bottomLeft",
|
||||
"bottomRight",
|
||||
"center",
|
||||
"left",
|
||||
"right",
|
||||
"top",
|
||||
"topLeft",
|
||||
"topRight"
|
||||
],
|
||||
"description": "Sets how the background image aligns to the boundaries of the window when unfocused. Possible values: \"center\", \"left\", \"top\", \"right\", \"bottom\", \"topLeft\", \"topRight\", \"bottomLeft\", \"bottomRight\"",
|
||||
"type": "string"
|
||||
},
|
||||
"experimental.retroTerminalEffect": {
|
||||
"description": "When set to true, enable retro terminal effects when unfocused. This is an experimental feature, and its continued existence is not guaranteed.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental.pixelShaderPath": {
|
||||
"description": "Use to set a path to a pixel shader to use with the Terminal when unfocused. Overrides `experimental.retroTerminalEffect`. This is an experimental feature, and its continued existence is not guaranteed.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ProfileGuid": {
|
||||
"default": "{}",
|
||||
"pattern": "^\\{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\}$",
|
||||
@@ -190,10 +77,6 @@
|
||||
"duplicateTab",
|
||||
"find",
|
||||
"findMatch",
|
||||
"focusPane",
|
||||
"globalSummon",
|
||||
"identifyWindow",
|
||||
"identifyWindows",
|
||||
"moveFocus",
|
||||
"moveTab",
|
||||
"newTab",
|
||||
@@ -202,15 +85,12 @@
|
||||
"openNewTabDropdown",
|
||||
"openSettings",
|
||||
"openTabColorPicker",
|
||||
"openWindowRenamer",
|
||||
"paste",
|
||||
"prevTab",
|
||||
"renameTab",
|
||||
"openTabRenamer",
|
||||
"quakeMode",
|
||||
"resetFontSize",
|
||||
"resizePane",
|
||||
"renameWindow",
|
||||
"scrollDown",
|
||||
"scrollDownPage",
|
||||
"scrollUp",
|
||||
@@ -342,32 +222,10 @@
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": null,
|
||||
"description": "If provided, will set the tab's color to the given value"
|
||||
},
|
||||
"suppressApplicationTitle": {
|
||||
"type": "boolean",
|
||||
"default": "false",
|
||||
"description": "When set to true, tabTitle overrides the default title of the tab and any title change messages from the application will be suppressed. When set to false, tabTitle behaves as normal"
|
||||
},
|
||||
"colorScheme": {
|
||||
"description": "The name of a color scheme to use, instead of the one specified by the profile",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SwitchToAdjacentTabArgs" : {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{
|
||||
"enum": [
|
||||
"mru",
|
||||
"inOrder",
|
||||
"disabled"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShortcutAction": {
|
||||
"properties": {
|
||||
"action": {
|
||||
@@ -546,14 +404,12 @@
|
||||
"action": { "type": "string", "pattern": "openSettings" },
|
||||
"target": {
|
||||
"type": "string",
|
||||
"default": "settingsUI",
|
||||
"description": "Opens Settings UI or settings file.",
|
||||
"default": "settingsFile",
|
||||
"description": "The settings file to open.",
|
||||
"enum": [
|
||||
"settingsFile",
|
||||
"defaultsFile",
|
||||
"allFiles",
|
||||
"settingsUI"
|
||||
|
||||
"allFiles"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -741,143 +597,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PrevTabAction": {
|
||||
"description": "Arguments corresponding to a Previous Tab Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type":"string", "pattern": "prevTab" },
|
||||
"tabSwitcherMode": {
|
||||
"$ref": "#/definitions/SwitchToAdjacentTabArgs",
|
||||
"default": null,
|
||||
"description": "Move to the previous tab using \"tabSwitcherMode\". If no mode is provided, use the one globally defined one."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"NextTabAction": {
|
||||
"description": "Arguments corresponding to a Next Tab Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type":"string", "pattern": "nextTab" },
|
||||
"tabSwitcherMode": {
|
||||
"$ref": "#/definitions/SwitchToAdjacentTabArgs",
|
||||
"default": null,
|
||||
"description": "Move to the next tab using \"tabSwitcherMode\". If no mode is provided, use the one globally defined one."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RenameTabAction": {
|
||||
"description": "Arguments corresponding to a renameTab Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "renameTab" },
|
||||
"title": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "A title to assign to the tab. If omitted or null, this action will restore the tab's title to the original value."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RenameWindowAction": {
|
||||
"description": "Arguments corresponding to a renameWindow Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "renameWindow" },
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "A name to assign to the window."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"FocusPaneAction": {
|
||||
"description": "Arguments corresponding to a focusPane Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "focusPane" },
|
||||
"id": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The ID of the pane to focus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"GlobalSummonAction": {
|
||||
"description": "This is a special action that works globally in the OS, rather than only in the context of the terminal window. When pressed, this action will summon the terminal window.",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "globalSummon" },
|
||||
"desktop": {
|
||||
"type": "string",
|
||||
"default": "toCurrent",
|
||||
"description": "This controls how the terminal should interact with virtual desktops.\n- \"any\": Leave the window on whichever desktop it's already on - will switch to that desktop as the window is activated.\n- \"toCurrent\" (default): Move the window to the current virtual desktop.\n- \"onCurrent\": Only summon the window if it's already on the current virtual desktop. ",
|
||||
"enum": [
|
||||
"any",
|
||||
"toCurrent",
|
||||
"onCurrent"
|
||||
]
|
||||
},
|
||||
"monitor": {
|
||||
"type": "string",
|
||||
"default": "toMouse",
|
||||
"description": "This controls the monitor that the window will be summoned from/to.\n- \"any\": Summon the most recently used window, regardless of which monitor it's currently on.\n- \"toCurrent\": Summon the most recently used window to the monitor with the current foreground window.\n- \"toMouse\" (default): Summon the most recently used window to the monitor where the mouse cursor is.",
|
||||
"enum": [
|
||||
"any",
|
||||
"toCurrent",
|
||||
"toMouse"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "When provided, summon the window whose name or ID matches the given name value. If no such window exists, then create a new window with that name."
|
||||
},
|
||||
"dropdownDuration": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"default": 0,
|
||||
"description": "When provided with a positive number, \"slide\" the window in from the top of the screen using an animation that lasts dropdownDuration milliseconds."
|
||||
},
|
||||
"toggleVisibility": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "When true, pressing the assigned keys for this action will dismiss (minimize) the window when the window is currently the foreground window."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"QuakeModeAction": {
|
||||
"description": "This action is a special variation of the globalSummon action. It specifically summons a window called \"_quake\". If you would like to change the behavior of the quakeMode action, we recommended creating a new globalSummon entry.",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "quakeMode" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -904,13 +623,6 @@
|
||||
{ "$ref": "#/definitions/MoveTabAction" },
|
||||
{ "$ref": "#/definitions/FindMatchAction" },
|
||||
{ "$ref": "#/definitions/NewWindowAction" },
|
||||
{ "$ref": "#/definitions/NextTabAction" },
|
||||
{ "$ref": "#/definitions/PrevTabAction" },
|
||||
{ "$ref": "#/definitions/RenameTabAction" },
|
||||
{ "$ref": "#/definitions/RenameWindowAction" },
|
||||
{ "$ref": "#/definitions/FocusPaneAction" },
|
||||
{ "$ref": "#/definitions/GlobalSummonAction" },
|
||||
{ "$ref": "#/definitions/QuakeModeAction" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
@@ -931,38 +643,16 @@
|
||||
},
|
||||
"icon": { "$ref": "#/definitions/Icon" },
|
||||
"name": {
|
||||
"description": "The name that will appear in the command palette. If one isn't provided, the terminal will attempt to automatically generate a name.\nIf name is a string, it will be the name of the command.\nIf name is a object, the key property of the object will be used to lookup a localized string resource for the command",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "The name that will appear in the command palette. If one isn't provided, the terminal will attempt to automatically generate a name.",
|
||||
"type": [
|
||||
"string",
|
||||
"object",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"iterateOn": {
|
||||
"type": "string",
|
||||
"description": "Used to create iterable commands based on other objects in your settings. Possible values:\n- \"profiles\" \n- \"schemes\"",
|
||||
"enum": [
|
||||
"profiles",
|
||||
"schemes"
|
||||
]
|
||||
},
|
||||
"commands": {
|
||||
"description": "List of commands to execute",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Keybinding/properties/command"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{"required": ["name","commands"]},
|
||||
{"required": ["command"]}
|
||||
"required": [
|
||||
"command",
|
||||
"keys"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -1005,16 +695,6 @@
|
||||
"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"
|
||||
},
|
||||
"trimBlockSelection": {
|
||||
"default": false,
|
||||
"description": "When set to true, trailing white-spaces will be removed from text in rectangular (block) selection while copied to your clipboard. When set to false, the white-spaces will be preserved.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental.detectURLs": {
|
||||
"default": true,
|
||||
"description": "When set to true, URLs will be detected by the Terminal. This will cause URLs to underline on hover and be clickable by pressing Ctrl.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableAnimations": {
|
||||
"default": false,
|
||||
"description": "When set to `true`, visual animations will be disabled across the application.",
|
||||
@@ -1096,16 +776,8 @@
|
||||
"type": [ "integer", "string" ],
|
||||
"deprecated": true
|
||||
},
|
||||
"actions": {
|
||||
"description": "Properties are specific to each custom action.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Keybinding"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"keybindings": {
|
||||
"description": "[deprecated] Use actions instead.",
|
||||
"deprecated": true,
|
||||
"description": "Properties are specific to each custom key binding.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Keybinding"
|
||||
},
|
||||
@@ -1234,11 +906,6 @@
|
||||
"description": "Sets the background color of the text. Overrides \"background\" from the color scheme. Uses hex color format: \"#rrggbb\".",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"unfocusedAppearance": {
|
||||
"$ref": "#/definitions/AppearanceConfig",
|
||||
"description": "Sets the appearance of the terminal when it is unfocused.",
|
||||
"type": ["object", "null"]
|
||||
},
|
||||
"backgroundImage": {
|
||||
"description": "Sets the file location of the image to draw over the window background.",
|
||||
"oneOf": [
|
||||
@@ -1289,7 +956,7 @@
|
||||
},
|
||||
"bellStyle": {
|
||||
"default": "audible",
|
||||
"description": "Controls what happens when the application emits a BEL character. When set to \"all\", the Terminal will play a sound, flash the taskbar icon (if the terminal window is not in focus) and flash the window. An array of specific behaviors can also be used. Supported array values include `audible`, `window` and `taskbar`. When set to \"none\", nothing will happen.",
|
||||
"description": "Controls what happens when the application emits a BEL character. When set to \"all\", the Terminal will play a sound and flash the taskbar icon. An array of specific behaviors can also be used. Supported array values include `audible` and `visual`. When set to \"none\", nothing will happen.",
|
||||
"$ref": "#/definitions/BellStyle"
|
||||
},
|
||||
"closeOnExit": {
|
||||
@@ -1326,9 +993,9 @@
|
||||
"description": "Sets the color of the cursor. Overrides the cursor color from the color scheme. Uses hex color format: \"#rrggbb\"."
|
||||
},
|
||||
"cursorHeight": {
|
||||
"description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 1-100.",
|
||||
"description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 25-100.",
|
||||
"maximum": 100,
|
||||
"minimum": 1,
|
||||
"minimum": 25,
|
||||
"type": ["integer","null"],
|
||||
"default": 25
|
||||
},
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# til::feature
|
||||
|
||||
Feature flags are controlled by an XML document stored at `src/features.xml`.
|
||||
|
||||
## Example Document
|
||||
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<featureStaging xmlns="http://microsoft.com/TilFeatureStaging-Schema.xsd">
|
||||
<feature>
|
||||
<!-- This will produce Feature_XYZ::IsEnabled() and TIL_FEATURE_XYZ_ENABLED (preprocessor) -->
|
||||
<name>Feature_XYZ</name>
|
||||
|
||||
<description>Does a cool thing</description>
|
||||
|
||||
<!-- GitHub deliverable number; optional -->
|
||||
<id>1234</id>
|
||||
|
||||
<!-- Whether the feature defaults to enabled or disabled -->
|
||||
<stage>AlwaysEnabled|AlwaysDisabled</stage>
|
||||
|
||||
<!-- Branch wildcards where the feature should be *DISABLED* -->
|
||||
<alwaysDisabledBranchTokens>
|
||||
<branchToken>branch/with/wildcard/*</branchToken>
|
||||
<!-- ... more branchTokens ... -->
|
||||
</alwaysDisabledBranchTokens>
|
||||
|
||||
<!-- Just like alwaysDisabledBranchTokens, but for *ENABLING* the feature. -->
|
||||
<alwaysEnabledBranchTokens>
|
||||
<branchToken>...</branchToken>
|
||||
</alwaysEnabledBranchTokens>
|
||||
|
||||
<!-- Brandings where the feature should be *DISABLED* -->
|
||||
<alwaysDisabledBrandingTokens>
|
||||
<!-- Valid brandings include Dev, Preview, Release, WindowsInbox -->
|
||||
<brandingToken>Release</brandingToken>
|
||||
<!-- ... more brandingTokens ... -->
|
||||
</alwaysDisabledBrandingTokens>
|
||||
|
||||
<!-- Just like alwaysDisabledBrandingTokens, but for *ENABLING* the feature -->
|
||||
<alwaysEnabledBrandingTokens>
|
||||
<branchToken>...</branchToken>
|
||||
</alwaysEnabledBrandingTokens>
|
||||
|
||||
<!-- Unequivocally disable this feature in Release -->
|
||||
<alwaysDisabledReleaseTokens />
|
||||
</feature>
|
||||
</featureStaging>
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Features that are disabled for Release using `alwaysDisabledReleaseTokens` are
|
||||
*always* disabled in Release, even if they come from a branch that would have
|
||||
been enabled by the wildcard.
|
||||
|
||||
### Precedence
|
||||
|
||||
1. `alwaysDisabledReleaseTokens`
|
||||
2. Enabled branches
|
||||
3. Disabled branches
|
||||
* The longest branch token that matches your branch will win.
|
||||
3. Enabled brandings
|
||||
4. Disabled brandings
|
||||
5. The feature's default state
|
||||
@@ -1,736 +0,0 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2021-02-23
|
||||
last updated: 2021-05-13
|
||||
issue id: #653
|
||||
---
|
||||
|
||||
# Quake Mode
|
||||
|
||||
## Abstract
|
||||
|
||||
Many existing terminals support a feature whereby a user can press a keybinding
|
||||
anywhere in the OS, and summon their terminal application. Oftentimes the act of
|
||||
summoning this window is accompanied by a "dropdown" animation, where the window
|
||||
slides in to view from the top of the screen. This global summon action is often
|
||||
referred to as "quake mode", a reference to the video game Quake, who's console
|
||||
slid in from the top.
|
||||
|
||||
This spec addresses both of the following two issues:
|
||||
* "Quake Mode" ([#653])
|
||||
* "Minimize to tray" ([#5727])
|
||||
|
||||
Readers should make sure to have read the [Process Model 2.0 Spec], for
|
||||
background on Monarch and Peasant processes.
|
||||
|
||||
## Background
|
||||
|
||||
### Inspiration
|
||||
|
||||
For an example of the original Quake console in action, take a look at the
|
||||
following video (noisy video warning): [Quake 3 sample]. Additionally, plenty of
|
||||
existing terminal emulators support similar functionality:
|
||||
|
||||
* **Tilda** allows the user to specify different keys to summon the window on
|
||||
different monitors.
|
||||
* **Guake** alternatively allows the user to either summon the terminal window to
|
||||
a specific monitor, or whichever monitor the mouse is on. Guake only allows
|
||||
one single instance, so pressing the global hotkey always summons the same
|
||||
instance.
|
||||
|
||||
### User Stories
|
||||
|
||||
The original quake mode thread (#653) is absolutely _filled_ with variations on
|
||||
how users want to be able to summon their terminal windows. These include, but
|
||||
are not limited to:
|
||||
|
||||
* **Story A** Press a hotkey anywhere to activate the single Terminal window
|
||||
wherever it was
|
||||
* **Story B** Press a hotkey anywhere to activate the single Terminal window _on
|
||||
the current monitor_. If it wasn't previously on that monitor, move it there.
|
||||
* **Story C** When the Terminal is summoned using the hotkey, have it "slide in"
|
||||
from the top. Similarly, slide out on deactivate.
|
||||
* **Story D** <kbd>Ctrl+1</kbd> to activate the terminal on monitor 1,
|
||||
<kbd>Ctrl+2</kbd> to activate the terminal on monitor 2.
|
||||
* **Story E** Variable dropdown speed
|
||||
* **Story F** Minimize to tray, press a hotkey to activate the terminal window
|
||||
(#5727)
|
||||
* **Story G** Terminal doesn't appear in alt+tab view, press a hotkey to
|
||||
activate the single terminal window / the nearest terminal window (I'm not
|
||||
sure this is distinct from the above)
|
||||
|
||||
## Solution Design
|
||||
|
||||
To implement this feature, we'll add the following settings:
|
||||
* a new action, named `globalSummon`.
|
||||
* a new global, app setting named `minimizeToTray`
|
||||
* a new global, app setting named `alwaysShowTrayIcon`
|
||||
* a new action, named `quakeMode`, and a specially named `_quake` window.
|
||||
|
||||
### `globalSummon` Action
|
||||
|
||||
The `globalSummon` action will be a keybinding the user can use to summon a
|
||||
Terminal window from anywhere in the OS. Various arguments to the action will
|
||||
specify which window is summoned, to where, and how the window should behave on
|
||||
summon.
|
||||
|
||||
From a technical perspective, the action will work by using the
|
||||
[`RegisterHotKey`] function. This API allows us to bind a particular hotkey with
|
||||
the OS. Whenever that hotkey is pressed, our window message loop will receive a
|
||||
`WM_HOTKEY`. We'll use the payload of that window message to lookup the action
|
||||
arguments for that hotkey. Then we'll use those arguments to control which
|
||||
window is invoked, where, and how the window behaves.
|
||||
|
||||
Since `RegisterHotKey` can only be used to register a hotkey _once_ with the OS,
|
||||
we'll need to make sure it's only ever set up by the Monarch process. We know
|
||||
that there will only ever be one Monarch for the Terminal at a time, so it's the
|
||||
perfect process to have the responsibility of managing the global hotkey.
|
||||
|
||||
The Monarch will be responsible for calling `RegisterHotKey`, and processing the
|
||||
`WM_HOTKEY` messages. It will then dispatch method calls to the appropriate
|
||||
window to summon it. When a Monarch dies and a new process becomes the Monarch,
|
||||
the new Monarch will re-register for the hotkeys.
|
||||
|
||||
#### Where in the settings?
|
||||
|
||||
Since users may want to bind multiple keys to summon different windows, we'll
|
||||
need to allow the user to specify multiple keybindings simultaneously, each with
|
||||
their own set of args.
|
||||
|
||||
We stick all the `globalSummon`s in the actions array, like they're any other
|
||||
keybinding.
|
||||
|
||||
However, these are not keys that are handled by the TerminalApp layer itself.
|
||||
These are keys that need to be registered with the OS. So while they will be in
|
||||
the normal `KeyMap`, they will need to be retrieved from that object and
|
||||
manually passed to the window layer.
|
||||
|
||||
> A previous iteration of this spec considered placing the `globalSummon`
|
||||
> actions in their own top-level array of the settings file, separate from the
|
||||
> keybindings. This is no longer being considered, because it would not work for
|
||||
> the case where the user has something like:
|
||||
> ```json
|
||||
> { "keys": "ctrl+c", "command": { "action": "globalSummon", "monitor": 1 } },
|
||||
> { "keys": "ctrl+v", "command": { "action": "copy" } },
|
||||
> ```
|
||||
|
||||
#### Which window, and where?
|
||||
|
||||
When looking at the list of requested scenarios, there are lots of different
|
||||
ways people would like to use the global summon action. Some want the most
|
||||
recent window activated, always. Others want to have one window _per monitor_.
|
||||
Some would like to move the window to where the user is currently interacting
|
||||
with the PC, and others want to activate the window where it already exists.
|
||||
Trying to properly express all these possible configurations is complex. The
|
||||
settings should be unambiguous as to what will happen when you press the
|
||||
keybinding.
|
||||
|
||||
I believe that in order to accurately support all the variations that people
|
||||
might want, we'll need two properties in the `globalSummon` action. These
|
||||
properties will specify _which_ window we're summoning, and _where_ to summon
|
||||
the window. To try and satisfy all these scenarios, I'm proposing the following
|
||||
two arguments to the `globalSummon` action:
|
||||
|
||||
```json
|
||||
"monitor": "any"|"toCurrent"|"toMouse"|"onCurrent"|int,
|
||||
"desktop": "any"|"toCurrent"|"onCurrent"
|
||||
```
|
||||
|
||||
The way these settings can be combined is in a table below. As an overview:
|
||||
|
||||
* `monitor`: This controls the monitor that the window will be summoned from/to
|
||||
- `"any"`: Summon the MRU window, regardless of which monitor it's currently
|
||||
on.
|
||||
- `"toCurrent"`/omitted: (_default_): Summon the MRU window **TO** the monitor
|
||||
with the current **foreground** window.
|
||||
- `"toMouse"`: Summon the MRU window **TO** the monitor where the **mouse**
|
||||
cursor is.
|
||||
- `"onCurrent"`: Summon the MRU window **ALREADY ON** the current monitor.
|
||||
- `int`: Summon the MRU window for the given monitor (as identified by the
|
||||
"Identify" displays feature in the OS settings)
|
||||
|
||||
* `desktop`: This controls how the terminal should interact with virtual desktops.
|
||||
- `"any"`: Leave the window on whatever desktop it's already on - we'll switch
|
||||
to that desktop as we activate the window.
|
||||
- > NOTE: A previous version of this spec had this enum value as `null`.
|
||||
This was changed to `"any"` for parity with the `monitor` property.
|
||||
- `"toCurrent"`/omitted: (_default_): Move the window **to** the current
|
||||
virtual desktop
|
||||
- `"onCurrent"`: Only summon the window if it's **already on** the current
|
||||
virtual desktop
|
||||
|
||||
Neither `desktop` nor `monitor` is a required parameter - if either is omitted,
|
||||
the omitted property will default to `toCurrent`.
|
||||
|
||||
Together, these settings interact in the following ways:
|
||||
|
||||
<!-- This table is formatted for viewing as rendered HTML. It's too complicated
|
||||
for pure markdown, sorry. -->
|
||||
<table>
|
||||
<tr>
|
||||
<td></td>
|
||||
<th colspan=3><code>"desktop"</code></th>
|
||||
</tr>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<tr>
|
||||
<th><code>"monitor"</code></th>
|
||||
<td><code>any</code><br><strong>Leave where it is</strong></td>
|
||||
<td><code>"toCurrent"</code><br><strong>Move to current desktop</strong></td>
|
||||
<td><code>"onCurrent"</code><br><strong>On current desktop only</strong></td>
|
||||
</tr>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<tr>
|
||||
<td><code>"any"</code><br> Summon the MRU window</td>
|
||||
|
||||
<td>Go to the desktop the window is on (leave position alone)</td>
|
||||
<td>Move the window to this desktop (leave position alone)</td>
|
||||
<td>
|
||||
|
||||
If there isn't one on this desktop:
|
||||
* create a new one (default position)
|
||||
|
||||
Else:
|
||||
* activate the one on this desktop (don't move it)
|
||||
</td>
|
||||
</tr>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<tr>
|
||||
<td><code>"toCurrent"</code><br> Summon the MRU window TO the monitor with the foreground window</td>
|
||||
<td>Go to the desktop the window is on, move to the monitor with the foreground window</td>
|
||||
<td>Move the window to this desktop, move to the monitor with the foreground window</td>
|
||||
<td>
|
||||
|
||||
If there isn't one on this desktop:
|
||||
* create a new one (on the monitor with the foreground window)
|
||||
|
||||
Else:
|
||||
* activate the one on this desktop, move to the monitor with the foreground window
|
||||
</td>
|
||||
</tr>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<tr>
|
||||
<td>
|
||||
<code>"toMouse"</code>
|
||||
<sup><a href="#footnote-2">[2]</a></sup> <br>
|
||||
Summon the MRU window TO the monitor with the mouse</td>
|
||||
<td>Go to the desktop the window is on, move to the monitor with the mouse</td>
|
||||
<td>Move the window to this desktop, move to the monitor with the mouse</td>
|
||||
<td>
|
||||
|
||||
If there isn't one on this desktop:
|
||||
* create a new one (on the monitor with the mouse)
|
||||
|
||||
Else:
|
||||
* activate the one on this desktop, move to the monitor with the mouse
|
||||
</td>
|
||||
</tr>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<tr>
|
||||
<td><code>"onCurrent"</code><br> Summon the MRU window for the current monitor</td>
|
||||
<td>
|
||||
|
||||
If there is a window on this monitor on any desktop,
|
||||
* Go to the desktop the window is on (leave position alone)
|
||||
|
||||
else
|
||||
* Create a new window on this monitor & desktop
|
||||
</td>
|
||||
<td>
|
||||
|
||||
If there is a window on this monitor on any desktop,
|
||||
* Move the window to this desktop (leave position alone)
|
||||
|
||||
else
|
||||
* Create a new window on this monitor & desktop
|
||||
</td>
|
||||
<td>
|
||||
|
||||
If there isn't one on this desktop, (even if there is one on this monitor on
|
||||
another desktop),
|
||||
* create a new one on this monitor
|
||||
|
||||
Else if ( there is one on this desktop, not this monitor)
|
||||
* create a new one on this monitor
|
||||
|
||||
Else (one on this desktop & monitor)
|
||||
* Activate the one on this desktop (don't move)
|
||||
</td>
|
||||
</tr>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
<tr>
|
||||
<td><code>int</code><br> Summon the MRU window for monitor N</td>
|
||||
<td>
|
||||
|
||||
If there is a window on monitor N on any desktop,
|
||||
* Go to the desktop the window is on (leave position alone)
|
||||
|
||||
else
|
||||
* Create a new window on this monitor & desktop
|
||||
</td>
|
||||
<td>
|
||||
|
||||
If there is a window on monitor N on any desktop,
|
||||
* Move the window to this desktop (leave position alone)
|
||||
|
||||
else
|
||||
* Create a new window on this monitor & desktop
|
||||
</td>
|
||||
<td>
|
||||
|
||||
If there isn't one on this desktop, (even if there is one on monitor N on
|
||||
another desktop),
|
||||
* create a new one on monitor N
|
||||
|
||||
Else if ( there is one on this desktop, not monitor N)
|
||||
* create a new one on monitor N
|
||||
|
||||
Else (one on this desktop & monitor N)
|
||||
* Activate the one on this desktop (don't move)
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
##### Stories, revisited
|
||||
|
||||
With the above settings, let's re-examine the original user stories, and see how
|
||||
they fit into the above settings. (_Stories that are omitted aren't relevant to
|
||||
the discussion of these settings_)
|
||||
|
||||
> When the `desktop` param is omitted below, that can be interpreted as "any
|
||||
> `desktop` value will make sense here"
|
||||
|
||||
* **Story A** Press a hotkey anywhere to activate the single Terminal window
|
||||
wherever it was
|
||||
- This is `{ "monitor": "any", "desktop": "any" }`
|
||||
* **Story B** Press a hotkey anywhere to activate the single Terminal window _on
|
||||
the current monitor_. If it wasn't previously on that monitor, move it there.
|
||||
- This is `{ "monitor": "toCurrent" }`
|
||||
* **Story D** <kbd>Ctrl+1</kbd> to activate the terminal on monitor 1,
|
||||
<kbd>Ctrl+2</kbd> to activate the terminal on monitor 2.
|
||||
- This is `[ { "keys": "ctrl+1", monitor": 1 }, { "keys": "ctrl+2", monitor": 2 } ]`
|
||||
|
||||
As some additional examples:
|
||||
|
||||
```json
|
||||
// Go to the MRU window, wherever it is
|
||||
{ "keys": "win+1", "command":{ "action":"globalSummon", "monitor":"any", "desktop": "any" } },
|
||||
|
||||
// activate the MRU window, and move it to this desktop & this monitor
|
||||
{ "keys": "win+2", "command":{ "action":"globalSummon", "monitor":"toCurrent", "desktop": "toCurrent" } },
|
||||
// Since "toCurrent" & "toCurrent" are the default values, just placing a single
|
||||
// entry here will bind the same behavior:
|
||||
{ "keys": "win+2", "command": "globalSummon" },
|
||||
|
||||
// activate the MRU window on this desktop
|
||||
{ "keys": "win+3", "command":{ "action":"globalSummon", "monitor":"any", "desktop": "onCurrent" } },
|
||||
|
||||
// Activate the MRU window on monitor 2 (from any desktop), and place it on the
|
||||
// current desktop. If there isn't one on monitor 2, make a new one.
|
||||
{ "keys": "win+4", "command":{ "action":"globalSummon", "monitor": 2, "desktop": "toCurrent" } },
|
||||
|
||||
// Activate the MRU window on monitor 3 (ONLY THIS desktop), or make a new one.
|
||||
{ "keys": "win+5", "command":{ "action":"globalSummon", "monitor": 3, "desktop": "onCurrent" } },
|
||||
|
||||
// Activate the MRU window on this monitor (from any desktop), and place it on
|
||||
// the current desktop. If there isn't one on this monitor, make a new one.
|
||||
{ "keys": "win+6", "command":{ "action":"globalSummon", "monitor": "onCurrent", "desktop": "toCurrent" } },
|
||||
```
|
||||
|
||||
#### Summoning a specific window
|
||||
|
||||
What if you want to press a keybinding to always summon a specific, named
|
||||
window? This window might not be the most recent terminal window, nor one that
|
||||
would be selected by the `monitor` and `desktop` selectors. You could name a
|
||||
window "Epona", and press `win+e` to always summon the "Epona" window.
|
||||
|
||||
We'll add the following property to address this scenario
|
||||
|
||||
* `"window": string|int`
|
||||
- When omitted (_default_): Use monitor and desktop to find the appropriate
|
||||
MRU window to summon.
|
||||
- When provided: Always summon the window who's name or ID matches the given
|
||||
`window` value. If no such window exists, then create a new window with that
|
||||
name/id.
|
||||
|
||||
When provided _with_ `monitor` and `desktop`, `window` behaves in the following
|
||||
ways:
|
||||
* `desktop`
|
||||
- `"any"`: Go to the desktop the given window is already on.
|
||||
- `"toCurrent"`: If the window is on another virtual desktop, then move it to
|
||||
the currently active one.
|
||||
- `"onCurrent"`: If the window is on another virtual desktop, then move it to
|
||||
the currently active one.
|
||||
* `monitor`
|
||||
- `"any"`: Leave the window on the monitor it is already on.
|
||||
- `"toCurrent"`: If the window is on another monitor, then move it to the
|
||||
currently active one.
|
||||
- `"onCurrent"`: If the window is on another monitor, then move it to the
|
||||
currently active one.
|
||||
- `<int>`: If the window is on another monitor, then move it to the specified
|
||||
monitor.
|
||||
|
||||
> NOTE: You read that right, `onCurrent` and `toCurrent` both do the same thing
|
||||
> when `window` is provided. They both already know which window to select, the
|
||||
> context of moving to the "current" monitor is all that those parameters add.
|
||||
|
||||
#### Other properties
|
||||
|
||||
Some users would like the terminal to just appear when the global hotkey is
|
||||
pressed. Others would like the true quake-like experience, where the terminal
|
||||
window "slides-in" from the top of the monitor. Furthermore, some users would
|
||||
like to configure the speed at which that dropdown happens. To support this
|
||||
functionality, the `globalSummon` action will support the following property:
|
||||
|
||||
* `"dropdownDuration": float`
|
||||
- When omitted, `0`, or a negative number: No animation is used
|
||||
when summoning the window. The summoned window is focused immediately where
|
||||
it is.
|
||||
- When a positive number is provided, the terminal will use that value as a
|
||||
duration (in seconds) to slide the terminal into position when activated.
|
||||
- The default would be some sensible value. The pane animation is .2s, so
|
||||
`0.2` might be a reasonable default here.
|
||||
|
||||
We could have alternatively provided a `"dropdownSpeed"` setting, that provided
|
||||
a number of pixels per second. In my opinion, that would be harder for users to
|
||||
use correctly. I believe that it's easier for users to mentally picture "I'd
|
||||
like the dropdown to last 100ms" vs "My monitor is 1504px tall, so I need to set
|
||||
this to 15040 to make the window traverse the entire display in .1s"
|
||||
|
||||
> NOTE: `dropdownDuration` will be ignored when the user has animations disabled
|
||||
> in the OS. In that case, the terminal will just appear, as if it was set to 0.
|
||||
|
||||
Some users might want to be able to use the global hotkey to hide the window
|
||||
when the window is already visible. This would let the hotkey act as a sort of
|
||||
global toggle for the Terminal window. Others might not like that behavior, and
|
||||
just want the action to always bring the Terminal into focus, and do nothing if
|
||||
the terminal is already focused. To facilitate both these use cases, we'll add
|
||||
the following property:
|
||||
|
||||
* `"toggleVisibility": bool`
|
||||
- When `true`: (_default_) When this hotkey is pressed, and the terminal
|
||||
window is currently active, minimize the window.
|
||||
- When `dropdownDuration` is not `0`, then the window will slide back off
|
||||
the top at the same speed as it would come down.
|
||||
- When `false`: When this hotkey is pressed, and the terminal window is
|
||||
currently active, do nothing.
|
||||
|
||||
### Quake Mode
|
||||
|
||||
In addition to just summoning the window from anywhere, some terminals also
|
||||
support a special "quake mode" buffer or window. This window is one that closely
|
||||
emulates the console from quake:
|
||||
* It's docked to the top of the screen
|
||||
* It takes the full width of the monitor, and only the bottom can be resized
|
||||
* It often doesn't have any other UI elements, like tabs
|
||||
|
||||
For fun, we'll also be adding a special `"_quake"` window with the same
|
||||
behavior. If the user names a window `_quake`, then it will behave in the
|
||||
following special ways:
|
||||
|
||||
* On launch, it will ignore the `initialPosition` and
|
||||
`initialRows`/`initialCols` setting, and instead resize to the top half of the
|
||||
monitor.
|
||||
* On launch, it will ignore the `launchMode` setting, and always launch in focus
|
||||
mode.
|
||||
- Users can disable focus mode on the `_quake` window if they do want tabs.
|
||||
* It will not be resizable from any side except the bottom of the window, nor
|
||||
will it be drag-able.
|
||||
* It will not be a valid target for the "most recent window" for window
|
||||
glomming. If it's the only open window, with `"windowingBehavior":
|
||||
"useExisting*"`, then a new window will be created instead.
|
||||
- It _is_ still a valid target for something like `wt -w _quake new-tab`
|
||||
|
||||
A window at runtime can be renamed to become the `_quake` window (if no other
|
||||
`_quake` window exists). When it does, it will resize to the position of the
|
||||
quake window, and enter focus mode.
|
||||
|
||||
We'll also be adding a special action `quakeMode`. This action is a special case
|
||||
of the `globalSummon` action, to specifically invoke the quake window in the
|
||||
current place. It is basically the same thing as the more elaborate:
|
||||
|
||||
```json
|
||||
{
|
||||
"monitor": "toCurrent",
|
||||
"desktop": "toCurrent",
|
||||
"window": "_quake",
|
||||
"toggleVisibility": true,
|
||||
"dropdownDuration": 0.5
|
||||
},
|
||||
```
|
||||
|
||||
### Minimize to Tray
|
||||
|
||||
Many users have requested that the terminal additionally supports minimizing the
|
||||
window "to the tray icon". This is a bit like when you close the Teams window,
|
||||
but Teams is actually still running in the system tray, or the "notification
|
||||
area".
|
||||
|
||||

|
||||
|
||||
_fig 1: an example of the Teams tray icon in the notification area_.
|
||||
|
||||
When users want to be able to "minimize to the tray", they want:
|
||||
* The window to no longer appear on the taskbar
|
||||
* The window to no longer appear in the alt-tab order
|
||||
|
||||
When minimized to the tray, it's almost as if there's no window for the Terminal
|
||||
at all. This can be combined with the global hotkey (or the tray icon's context
|
||||
menu) to quickly restore the window.
|
||||
|
||||
The tray icon could be used for a variety of purposes. As a simple start, we
|
||||
could include the following three options:
|
||||
|
||||
```
|
||||
Focus Terminal
|
||||
---
|
||||
Windows > Window 1 - <un-named window>
|
||||
Window 2 - "This-window-does-have-a-name"
|
||||
---
|
||||
Quit
|
||||
```
|
||||
|
||||
Just clicking on the icon would summon the recent terminal window. Right
|
||||
clicking would show the menu with "Focus Terminal", "Windows" and "Quit" in it, and
|
||||
"Windows" would have nested entries for each Terminal window.
|
||||
|
||||
* "Focus Terminal" would do just that - summon the most recent terminal window,
|
||||
wherever it is.
|
||||
* "Windows" would have nested popups for each open Terminal window. Each of
|
||||
these nested entries would display the name and ID of the window. Clicking
|
||||
them would summon that window (wherever it may be)
|
||||
* "Quit" would be akin to quit in browsers - close all open windows
|
||||
<sup>[[1]](#footnote-1)</sup>.
|
||||
|
||||
The tray notification would be visible always when the user has
|
||||
`"minimizeToTray": true` set in their settings. If the user has that set to
|
||||
false, but would still like the tray, they can specify `"alwaysShowTrayIcon":
|
||||
true`. That will cause the tray icon to always be added to the system tray.
|
||||
|
||||
There's not a combination of settings where the Terminal is "minimized to the
|
||||
tray", and there's _no tray icon visible_. We don't want to let users get into a
|
||||
state where the Terminal is running, but is totally hidden from their control.
|
||||
|
||||
From a technical standpoint, the tray icon is managed similar to the global
|
||||
hotkey. The Monarch process is responsible for setting it up, and processing the
|
||||
messages. When a Monarch dies and a new process becomes the Monarch, then it
|
||||
will re-create the tray icon.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
To summarize, we're proposing the following set of settings:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"minimizeToTray": bool,
|
||||
"alwaysShowTrayIcon": bool,
|
||||
"actions": [
|
||||
{
|
||||
"keys": KeyChord,
|
||||
"command": {
|
||||
"action": "globalSummon",
|
||||
"dropdownDuration": float,
|
||||
"toggleVisibility": bool,
|
||||
"monitor": "any"|"toCurrent"|"onCurrent"|int,
|
||||
"desktop": "any"|"toCurrent"|"onCurrent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"keys": KeyChord,
|
||||
"command": {
|
||||
"action": "quakeMode"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Potential Issues
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
|
||||
As part of this set of changes, we'll also be allowing the <kbd>Win</kbd> key in
|
||||
keybindings. Generally, the OS reserves the Windows key for its own shortcuts.
|
||||
For example, <kbd>Win+R</kbd> for the run dialog, <kbd>Win+A</kbd> for the
|
||||
Action Center, <kbd>Win+V</kbd> for the cloud clipboard, etc. Users will now be
|
||||
able to use the win key themselves, but they should be aware that the OS has
|
||||
"first dibs" on any hotkeys involving the Windows key.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Mixed elevation</strong></td>
|
||||
<td>
|
||||
|
||||
Only one app at a time gets to register for global hotkeys. However, from the
|
||||
Terminal's perspective, unelevated and elevated windows will act like different
|
||||
apps. Each privilege level has its own Monarch. The two are unable to
|
||||
communicate across the elevation boundary.
|
||||
|
||||
This means that if the user often runs terminals in both contexts, then only one
|
||||
will have the global hotkeys bound. The naive implementation would have the
|
||||
first elevation level "win" the keybindings.
|
||||
|
||||
A different option would be to have elevated windows not register global hotkeys
|
||||
_at all_. I don't believe that there's any sort of security implication for
|
||||
having a global hotkey for an elevated window.
|
||||
|
||||
A third option would be to have some sort of `"whenElevated": bool?` property
|
||||
for global hotkeys. This would explicitly enable a given hotkey for unelevated
|
||||
vs elevated windows.
|
||||
* `"whenElevated": null`: behave as normal - the first context level to run wins
|
||||
* `"whenElevated": true`: only register the hotkey when running elevated
|
||||
* `"whenElevated": false`: only register the hotkey when running unelevated
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>OneCore / Windows 10X</strong></td>
|
||||
<td>
|
||||
|
||||
I'm fairly certain that none of these APIs would work on Windows 10X at all.
|
||||
These features would have to initially be disabled in a pure UWP version of the
|
||||
Terminal, until we could find workarounds. Since the window layer is the one
|
||||
responsible for the management of the hotkeys and the tray icon, we're not too
|
||||
worried about this.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
* If there are any other applications running that have already registered
|
||||
hotkeys with `RegisterHotKey`, then it's possible that the Terminal's attempt
|
||||
to register that hotkey will fail. If that should happen, then we should
|
||||
display a warning dialog to the user indicating which hotkey will not work
|
||||
(because it's already used for something else).
|
||||
|
||||
* Which is the "current" monitor? The one with the mouse or the one with the
|
||||
active window? This isn't something that has an obvious answer. Guake
|
||||
implements this feature where the "current monitor" is the one with the mouse
|
||||
on it. At least for the first iterations of this action, that's what we'll
|
||||
use.
|
||||
`monitor: onCurrent|onCurrentWindow|toCurrent|<int>`
|
||||
|
||||
* Currently, running both the Release and Preview versions of the Terminal at
|
||||
the same time side-by-side is not generally supported. (For example, `wt.exe`
|
||||
can only ever point at one of two.) If a user binds the same key to a
|
||||
`globalSummon` or `quakeMode` action, then only one of the apps will actually
|
||||
be able to successfully claim the global hotkey.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
Currently, in [`dev/migrie/f/653-QUAKE-MODE`], I have some sample rudimentary
|
||||
code to implement quake mode support. It allows for only a single global hotkey
|
||||
that summons the MRU window, without dropdown. That would be a good place for
|
||||
anyone starting to work on this feature. From there, I imagine the following
|
||||
work would be needed:
|
||||
|
||||
* [ ] Add a `globalSummon` action. `AppHost` would need to be able to get _all_
|
||||
of these actions, and register all of them. Each one would need to be assigned
|
||||
a unique ID, so `WM_HOTKEY` can identify which hotkey was pressed.
|
||||
- This could be committed without any other args to the `globalHotkeys`. In
|
||||
this initial version, the behavior would be summoning the MRU window,
|
||||
where it is, no dropdown, to start with. From there, we'd add the
|
||||
remaining properties:
|
||||
* [ ] Add support for the `toggleVisibility` property
|
||||
* [ ] Add support for the `desktop` property to control how window summoning
|
||||
interacts with virtual desktops
|
||||
* [ ] Add support for the `monitor` which monitor the window appears on.
|
||||
* [ ] Add support for the `dropdownDuration` property
|
||||
* [ ] Add the `minimizeToTray` setting, and implement it without any sort of flyout
|
||||
* [ ] Add a list of windows to the right-click flyout on the tray icon
|
||||
* [ ] Add support for the `alwaysShowTrayIcon` setting
|
||||
* [ ] When the user creates a window named `_quake`, ignore the initial size,
|
||||
position, and launch mode, and create the window in quake mode instead.
|
||||
* [ ] Exempt the `_quake` window from window glomming
|
||||
* [ ] Add the `quakeMode` action, which `globalSummon`'s the `_quake` window.
|
||||
* [ ] Prevent the `_quake` window from being dragged or resized on the
|
||||
top/left/right.
|
||||
|
||||
|
||||
### Future Considerations
|
||||
|
||||
I don't believe there are any other tracked requests that are planned that
|
||||
aren't already included in this spec.
|
||||
|
||||
* Should the tray icon's list of windows include window titles? Both the name
|
||||
and title? Maybe something like `({name}|{id}): {title}`? I'd bet that most
|
||||
people don't end up naming their windows.
|
||||
* Dropdown duration could be a `float|bool`, with `true`->(whatever the default
|
||||
is), `false`->0.
|
||||
- We could have the setting appear as a pair of radio buttons, with the first
|
||||
disabling dropdown, and the second enabling a text box for inputting an
|
||||
animation duration.
|
||||
* It might be an interesting idea to have the ability to dock the quake window
|
||||
to a specific side of the monitor, not just the top. We could probably do that
|
||||
with a global setting `"quakeModeDockSide": "top"|"left"|"bottom"|"right"` or
|
||||
something like that.
|
||||
* We might want to pre-load the quake window into the tray icon as an entry for
|
||||
"Quake Mode", and otherwise exclude it from the list of windows in that menu.
|
||||
* We might think of other things for the Quake Mode window in the future - this
|
||||
spec is by no means comprehensive. For example, it might make sense for the
|
||||
quake mode window to automatically open in "always on top" mode.
|
||||
* It was suggested that the quake mode window could auto-hide when it loses
|
||||
focus. That's a really neat idea, but we're a little worried about the
|
||||
implementation. What happens when the IME window gets focus? Or the Start
|
||||
Menu? Would those end up causing the quake window to prematurely minimize
|
||||
itself? For that reason, we're leaving this as a future consideration.
|
||||
* Perhaps there could be a top-level object in the settings like
|
||||
```json
|
||||
{
|
||||
"quakeMode": {
|
||||
"hideOnFocusLost": true,
|
||||
"useFocusMode": false,
|
||||
"profile": "my quake mode profile" ,
|
||||
"quakeModeDockSide": "bottom"
|
||||
}
|
||||
}
|
||||
```
|
||||
That would allow the user some further customizations on the quake mode
|
||||
behaviors.
|
||||
- This was later converted to the idea in [#9992] - Add per-window-name global
|
||||
settings
|
||||
* Another proposed idea was a simplification of some of the summoning modes. `{
|
||||
"monitor": "any", "desktop": "any" }` is a little long, and maybe not the most
|
||||
apparent naming. Perhaps we could add another property like `summonMode` that
|
||||
would act like an alias for a `monitor`, `desktop` combo.
|
||||
- `"summonMode": "activateInMyFace"`: `{ "monitor": "toCurrent", "desktop": "toCurrent" }`
|
||||
- `"summonMode": "activateWherever"`: `{ "monitor": "any", "desktop": "any" }`
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
Docs on adding a system tray item:
|
||||
* https://docs.microsoft.com/en-us/windows/win32/shell/notification-area
|
||||
* https://www.codeproject.com/Articles/18783/Example-of-a-SysTray-App-in-Win32
|
||||
|
||||
Docs regarding hiding a window from the taskbar:
|
||||
* https://docs.microsoft.com/en-us/previous-versions//bb776822(v=vs.85)#managing-taskbar-buttons
|
||||
|
||||
### Footnotes
|
||||
|
||||
<a name="footnote-1"><a>[1]: Quitting the terminal is different than closing the
|
||||
windows one-by-one. Quiting implies an atomic action, for closing all the
|
||||
windows. Once [#766] lands, this will give us a chance to persist the state of
|
||||
_all_ open windows. This will allow us to re-open with all the user's windows,
|
||||
not just the one that happened to be closed last.
|
||||
|
||||
<a name="footnote-2"><a>[2]: **Addenda, May 2021**: In the course of
|
||||
implementation, it became apparent that there's an important UX difference
|
||||
between summoning _to the monitor with the cursor_ vs _to the monitor with the
|
||||
foreground window_. `"monitor": "toMouse"` was added as an option, to allow the
|
||||
user to differentiate between the two behaviors.
|
||||
|
||||
[#653]: https://github.com/microsoft/terminal/issues/653
|
||||
[#766]: https://github.com/microsoft/terminal/issues/766
|
||||
[#5727]: https://github.com/microsoft/terminal/issues/5727
|
||||
[#9992]: https://github.com/microsoft/terminal/issues/9992
|
||||
|
||||
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md
|
||||
[Quake 3 sample]: https://youtu.be/ZmR6HQbuHPA?t=27
|
||||
[`RegisterHotKey`]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey
|
||||
[`dev/migrie/f/653-QUAKE-MODE`]: https://github.com/microsoft/terminal/tree/dev/migrie/f/653-QUAKE-MODE
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,344 +0,0 @@
|
||||
---
|
||||
author: Carlos Zamora @carlos-zamora
|
||||
created on: 2021-03-12
|
||||
last updated: 2021-03-17
|
||||
issue id: [#885]
|
||||
---
|
||||
|
||||
# Actions in the Settings Model
|
||||
|
||||
## Abstract
|
||||
|
||||
This spec proposes a refactor of how Windows Terminal actions are stored in the settings model.
|
||||
The new representation would mainly allow the serialization and deserialization of commands and keybindings.
|
||||
|
||||
## Inspiration
|
||||
|
||||
A major component that is missing from the Settings UI is the representation of keybindings and commands.
|
||||
The JSON represents both of these as a combined entry as follows:
|
||||
```js
|
||||
{ "icon": "path/to/icon.png", "name": "Copy the selected text", "command": "copy", "keys": "ctrl+c" },
|
||||
```
|
||||
In the example above, the copy action is...
|
||||
- bound to <kbd>ctrl+c</kbd>
|
||||
- presented as "Copy the selected text" with the "path/to/icon.png" icon
|
||||
|
||||
However, at the time of writing this spec, the settings model represents it as...
|
||||
- (key binding) a `KeyChord` to `ActionAndArgs` entry in a `KeyMapping`
|
||||
- (command) a `Command` with an associated icon, name, and action (`ActionAndArgs`)
|
||||
|
||||
This introduces the following issues:
|
||||
1. Serialization
|
||||
- We have no way of knowing when a command and a key binding point to the same action. Thus, we don't
|
||||
know when to write a "name" to the json.
|
||||
- We also don't know if the name was auto-generated or set by the user. This can make the JSON much more bloated by
|
||||
actions with names that would normally be autogenerated.
|
||||
2. Handling Duplicates
|
||||
- The same action can be bound to multiple key chords. The command palette combines all of these actions into one entry
|
||||
because they have the same name. In reality, this same action is just being referenced in different ways.
|
||||
|
||||
## Solution Design
|
||||
|
||||
I propose that the issues stated above be handled via the following approach.
|
||||
|
||||
### Step 1: Consolidating actions
|
||||
|
||||
`Command` will be updated to look like the following:
|
||||
|
||||
```c++
|
||||
runtimeclass Command
|
||||
{
|
||||
// The path to the icon (or icon itself, if it's an emoji)
|
||||
String IconPath;
|
||||
|
||||
// The associated name. If none is defined, one is auto-generated.
|
||||
String Name;
|
||||
|
||||
// The key binding that can be used to invoke this action.
|
||||
// NOTE: We're actually holding the KeyChord instead of just the text.
|
||||
// KeyChordText just serializes the relevant keychord
|
||||
Microsoft.Terminal.Control.KeyChord Keys;
|
||||
String KeyChordText;
|
||||
|
||||
// The action itself.
|
||||
ActionAndArgs ActionAndArgs;
|
||||
|
||||
// NOTE: nested and iterable command logic will still be here,
|
||||
// But they are omitted to make this section seem cleaner.
|
||||
|
||||
// Future Considerations:
|
||||
// - [#6899]: Action IDs --> add an identifier here
|
||||
}
|
||||
```
|
||||
|
||||
The goal here is to consolidate key binding actions and command palette actions into a single class.
|
||||
This will also require the following supplemental changes:
|
||||
- `Command::LayerJson`
|
||||
- This must combine the logic of `KeyMapping::LayerJson` and `Command::LayerJson`.
|
||||
- Key Chord data
|
||||
- Internally, store a `vector<KeyChord> _keyMappings` to keep track of all the key chords associated with this action.
|
||||
- `RegisterKey` and `EraseKey` update `_keyMappings`, and ensure that the latest key registered is at the end of the list.
|
||||
- `Keys()` simply returns the last entry of `_keyMappings`, which is the latest key chord this action is bound to.
|
||||
- `KeyChordText())` is exposed to pass the text directly to the command palette.
|
||||
This depends on `Keys` and, thus, propagate changes automatically.
|
||||
- Observable properties
|
||||
- `Command` has observable properties today, but does not need them because `Command` will never change while the app is running.
|
||||
- Nested and iterable commands
|
||||
- `HasNestedCommands`, `NestedCommands{ get; }`, `IterateOn` will continue to be exposed.
|
||||
- A setter for these customizations will not be exposed until we find it necessary (i.e. adding support for customizing it in the Settings UI)
|
||||
- Command expansion can continue to be exposed here to reduce implementation cost.
|
||||
- An additional `IsNestedCommand` is necessary to record a case where a nested command is being unbound `{ "commands": null, "name": "foo" }`.
|
||||
|
||||
Overall, the `Command` class is simply being promoted to include the `KeyChord` it has.
|
||||
This allows the implementation cost of this step to be relatively small.
|
||||
|
||||
Completion of this step should only cause relatively minor changes to anything that depends on `Command`, because
|
||||
it is largely the same class. However, key bindings will largely be impacted because we represent key bindings as
|
||||
a map of `KeyChord`s to `ActionAndArgs`. This leads us to step 2 of this process.
|
||||
|
||||
|
||||
### Step 2: Querying actions
|
||||
|
||||
Key bindings and commands are deserialized by basically storing individual actions to a map.
|
||||
- `KeyMapping` is basically an `IMap<KeyChord, ActionAndArgs>` with a few extra functions. In fact, it actually
|
||||
stores key binding data to a `std::map<KeyChord, ActionAndArgs>` and directly interacts with it.
|
||||
- `Command::LayerJson` populates an `IMap<String, Command>` during deserialization as it iterates over every action.
|
||||
Note that `Command` can be interpreted as a wrapper for `ActionAndArgs` with more stuff here.
|
||||
|
||||
It makes sense to store these actions as maps. So, following step 1 above, we can also store and expose actions
|
||||
something like the following:
|
||||
|
||||
```c++
|
||||
runtimeclass ActionMap
|
||||
{
|
||||
ActionAndArgs GetActionByKeyChord(KeyChord keys);
|
||||
|
||||
KeyChord GetKeyBindingForAction(ShortcutAction action);
|
||||
KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs);
|
||||
|
||||
IMapView<String, Command> NameMap { get; };
|
||||
|
||||
// Future Considerations:
|
||||
// - [#6899]: Action IDs --> GetActionByID()
|
||||
}
|
||||
```
|
||||
|
||||
The getters will return null if a matching action or key chord is not found. Since iterable commands need to be expanded at in TerminalApp, we'll just expose `NameMap`, then let TerminalApp perform the expansion as they do now. Internally, we can store the actions as follows:
|
||||
|
||||
```c++
|
||||
std::map<KeyChord, InternalActionID> _KeyMap;
|
||||
std::map<InternalActionID, Command> _ActionMap;
|
||||
```
|
||||
|
||||
`InternalActionID` will be a hash of `ActionAndArgs` such that two `ActionAndArgs` with the same `ShortcutAction` and `IActionArgs` output the same hash value.
|
||||
|
||||
`GetActionByKeyChord` will use `_KeyMap` to find the `InternalActionID`, then use the `_ActionMap` to find the bound `Command`.
|
||||
`GetKeyBindingForAction` will hash the provided `ActionAndArgs` (constructed by the given parameters) and check `_ActionMap` for the given `InternalActionID`.
|
||||
|
||||
`NameMap` will need to ensure every action in `_ActionMap` is added to the output name map if it has an associated name. This is done by simply iterating over `_ActionMap`. Nested commands must be added separately because they cannot be hashed.
|
||||
|
||||
`ActionMap` will have an `AddAction(Command cmd)` that will update the internal state whenever a command is registered. If the given command is valid, we will check for collisions and resolve them. Otherwise, we will consider this an "unbound" action and update the internal state normally. It is important that we don't handle "unbound" actions differently because this ensures that we are explicitly unbinding a key chord.
|
||||
|
||||
### Step 3: Settings UI needs
|
||||
|
||||
After the former two steps are completed, the new representation of actions in the settings model is now on-par with
|
||||
what we have today. In order to bind these new actions to the Settings UI, we need the following:
|
||||
|
||||
1. Exposing the maps
|
||||
- `ActionMap::KeyBindings` and `ActionMap::Commands` may need to be added to pass the full list of actions to the Settings UI.
|
||||
- In doing this, we can already update the Settings UI to include a better view of our actions.
|
||||
2. Creating a copy of the settings model
|
||||
- The Settings UI operates by binding the XAML controls to a copy of the settings model.
|
||||
- Copying the `ActionMap` is fairly simple. Just copy the internal state and ensure that `Command::Copy` is called such that no reference to the original WinRT objects persist. Since we are using `InternalActionID`, we will not have to worry about multiple `Command` references existing within the same `ActionMap`.
|
||||
3. Modifying the `Command`s
|
||||
- `ActionMap` must be responsible for changing `Command`s so that we can ensure `ActionMap` always has a correct internal state:
|
||||
- It is important that `Command` only exposes getters (not setters) to ensure `ActionMap` is up to date.
|
||||
- If a key chord is being changed, update the `_KeyMap` and the `Command` itself.
|
||||
- If a key binding is being deleted, add an unbound action to the given key chord.
|
||||
- This is similar to how color schemes are maintained today.
|
||||
- In the event that name/key-chord is set to something that's already taken, we need to propagate those changes to
|
||||
the rest of `ActionMap`. As we do with the JSON, we respect the last name/key-chord set by the user. See [Modifying Actions](#modifying-actions)
|
||||
in potential issues.
|
||||
- For the purposes of the key bindings page, we will introduce a `KeyBindingViewModel` to serve as an intermediator between the settings UI and the settings model. The view model will be responsible for things like...
|
||||
- exposing relevant information to the UI controls
|
||||
- converting UI control interactions into proper API calls to the settings model
|
||||
4. Serialization
|
||||
- `Command::ToJson()` and `ActionMap::ToJson()` should perform most of the work for us. Simply iterate over the `_ActionMap` and call `Command::ToJson` on each action.
|
||||
- See [Unbinding actions](#unbinding-actions) in potential issues.
|
||||
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
N/A
|
||||
|
||||
## Capabilities
|
||||
|
||||
N/A
|
||||
|
||||
### Accessibility
|
||||
|
||||
N/A
|
||||
|
||||
### Security
|
||||
|
||||
N/A
|
||||
|
||||
### Reliability
|
||||
|
||||
N/A
|
||||
|
||||
### Compatibility
|
||||
|
||||
N/A
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
## Potential Issues
|
||||
|
||||
### Layering Actions
|
||||
|
||||
We need a way to determine where an action came from to minimize how many actions we serialize when we
|
||||
write to disk. This is a two part approach that happens as we're loading the settings
|
||||
1. Load defaults.json
|
||||
- For each of the actions in the JSON...
|
||||
- Construct the `Command` (basically the `Command::LayerJson` we have today)
|
||||
- Add it to the `ActionMap`
|
||||
- this should update the internal state of `ActionMap` appropriately
|
||||
- if the newly added key chord conflicts with a pre-existing one,
|
||||
redirect `_KeyMap` to the newly added `Command` instead,
|
||||
and update the conflicting one.
|
||||
2. Load settings.json
|
||||
- Create a child for the `ActionMap`
|
||||
- The purpose of a parent is to continue a search when the current `ActionMap` can't find a `Command` for a query. The parent is intended to be immutable.
|
||||
- Load the actions array like normal into the child (see step 1)
|
||||
|
||||
Introducing a parent mechanism to `ActionMap` allows it to understand where a `Command` came from. This allows us to minimize the number of actions we serialize when we write to disk, as opposed to serializing the entire list of actions.
|
||||
|
||||
`ActionMap` queries will need to check their parent when they cannot find a matching `InternalActionID` in their `_ActionMap`.
|
||||
|
||||
Since `NameMap` is generated upon request, we will need to use a `std::set<InternalActionID>` as we generate the `NameMap`. This will ensure that each `Command` is only added to the `NameMap` once. The name map will be generated as follows:
|
||||
1. Get an accumulated list of `Command`s from our parents
|
||||
2. Iterate over the list...
|
||||
- Update `NameMap` with any new `Command`s (tracked by the `std::set<InternalActionID>`)
|
||||
|
||||
Nested commands will be saved to their own map since they do not have an `InternalActionID`.
|
||||
- During `ActionMap`'s population, we must ensure to resolve any conflicts immediately. This means that any new `Command`s that generate a name conflicting with a nested command will take priority (and we'll remove the nested command from its own map). Conversely, if a new nested command conflicts with an existing standard `Command`, we can ignore it because our generation of `NameMap` will handle it.
|
||||
- When populating `NameMap`, we must first add all of the standard `Command`s. To ensure layering is accomplished correctly, we will need to start from the top-most parent and update `NameMap`. As we go down the inheritance tree, any conflicts are resolved by prioritizing the current layer (the child). This ensures that the current layer takes priority.
|
||||
- After adding all of the standard `Command`s to the `NameMap`, we can then register all of the nested commands. Since nested commands have no identifier other than a name, we cannot use the `InternalActionID` heuristic. However, as mentioned earlier, we are updating our internal nested command map as we add new actions. So when we are generating the name map, we can assume that all of these nested commands now have priority. Thus, we simply add all of these nested commands to the name map. Any conflicts are resolved in favor of th nested command.
|
||||
|
||||
### Modifying Actions
|
||||
|
||||
There are several ways a command can be modified:
|
||||
- change/remove the key chord
|
||||
- change the name
|
||||
- change the icon
|
||||
- change the action
|
||||
|
||||
It is important that these modifications are done through `ActionMap` instead of `Command`.
|
||||
This is to ensure that the `ActionMap` is always aligned with `Command`'s values. `Command` should only expose getters in the projected type to enforce this. Thus, we can add the following functions to `ActionMap`:
|
||||
|
||||
```c++
|
||||
runtimeclass ActionMap
|
||||
{
|
||||
void SetKeyChord(Command cmd, KeyChord keys);
|
||||
void SetName(Command cmd, String name);
|
||||
void SetIcon(Command cmd, String iconPath);
|
||||
void SetAction(Command cmd, ShortcutAction action, IActionArgs actionArgs);
|
||||
}
|
||||
```
|
||||
|
||||
`SetKeyChord` will need to make sure to modify the `_KeyMap` and the provided `Command`.
|
||||
If the new key chord was already taken, we also need to update the conflicting `Command`
|
||||
and remove its key chord.
|
||||
|
||||
`SetName` will need to make sure to modify the `Command` in `_ActionMap` and regenerate `NameMap`.
|
||||
|
||||
`SetIcon` will only need to modify the provided `Command`. We can choose to not expose this in the `ActionMap`, but doing so makes the API consistent.
|
||||
|
||||
`SetAction` will need to begin by updating the provided `Command`'s `ActionAndArgs`.
|
||||
If the generated name is being used, the name will need to be updated. `_ActionMap` will need to be updated with a new `InternalActionID` for the new action. This is a major operation and so all views exposed will need to be regenerated.
|
||||
|
||||
Regarding [Layering Actions](#layering-actions), if the `Command` does not exist in the current layer,
|
||||
but exists in a parent layer, we need to...
|
||||
0. check if it exists
|
||||
- use the hash `InternalActionID` to see if it exists in the current layer
|
||||
- if it doesn't (which is the case we're trying to solve here), call `_GetActionByID(InternalActionID)` to retrieve the `Command` wherever it may be. This helper function simply checks the current layer, if none is found, it recursively checks its parents until a match is found.
|
||||
1. duplicate it with `Command::Copy`
|
||||
2. store the duplicate in the current layer
|
||||
- `ActionMap::AddAction(duplicate)`
|
||||
3. make the modification to the duplicate
|
||||
|
||||
This ensures that the change persists post-serialization.
|
||||
|
||||
TerminalApp has no reason to ever call these setters. To ensure that relationship, we will introduce an `IActionMapView` interface that will only expose `ActionMap` query functions. Conversely, `ActionMap` will be exposed to the TerminalSettingsEditor to allow for modifications.
|
||||
|
||||
### Unbinding actions
|
||||
|
||||
Removing a name is currently omitted from this spec because there
|
||||
is no Settings UI use case for it at the moment. This scenario is
|
||||
designed for Command Palette customization.
|
||||
|
||||
The only kind of unbinding currently in scope is freeing a key chord such that
|
||||
no action is executed on that key stroke. To do this, simply `ActionMap::AddAction` a `Command` with...
|
||||
- `ActionAndArgs`: `ShortcutAction = Invalid` and `IActionArgs = nullptr`
|
||||
- `Keys` being the provided key chord
|
||||
|
||||
In explicitly storing an "unbound" action, we are explicitly saying that this key chord
|
||||
must be passed through and this string must be removed from the command palette. `AddAction` automatically handles updating the internal state of `ActionMap` and any conflicting `Commands`.
|
||||
|
||||
This allows us to output something like this in the JSON:
|
||||
|
||||
```js
|
||||
{ "command": "unbound", "keys": "ctrl+c" }
|
||||
```
|
||||
|
||||
### Consolidated Actions
|
||||
|
||||
`AddAction` must be a bit smarter when it comes to the following scenario:
|
||||
- Given a command that unbinds a key chord: `{ "command": "unbound", "keys": "ctrl+c" }`
|
||||
- And... that key chord was set in a parent layer `{ "command": "copy", "keys": "ctrl+c" }`
|
||||
- But... the action has another key chord from a parent layer `{ "command": "copy", "keys": "ctrl+shift+c" }`
|
||||
|
||||
`_ActionMap` does not contain any information about a parent layer; it only contains actions introduced in the current layer. Thus, in the scenario above, unbinding `ctrl+c` is what is relevant to `_ActionMap`. However, this may cause some complications for `GetKeyChordForAction`. We cannot simply check our internal `_ActionMap`, because the primary key chord in the entry may be incorrect. Again, this is because `_ActionMap` is only aware of what was bound in the current layer.
|
||||
|
||||
To get around this issue, we've introduced `_ConsolidatedActions`. In a way, `_ConsolidatedActions` is similar to `_ActionMap`, except that it consolidates the `Command` data into one entry constructed across the current layer and the parent layers. Specifically, in the scenario above, `_ActionMap` will say that `copy` has no key chords. In fact, `_ActionMap` has no reason to have `copy` at all, because it was not introduced in this layer. Conversely, `_ConsolidatedActions` holds `copy` with a `ctrl+shift+c` binding, which is then returned to `GetKeyChordForAction`.
|
||||
|
||||
To maintain `_ConsolidatedActions`, any new action added to the Action Map must also update `_ConsolidatedActions`. It is especially important to handle and propagate collisions to `_ConsolidatedActions`.
|
||||
|
||||
When querying Action Map for an ID, we should always check in the following order:
|
||||
- `_ConsolidatedActions`
|
||||
- `_ActionMap`
|
||||
- repeat this process for each parent
|
||||
|
||||
This is to ensure that we are returning the correct and wholistic view of a `Command` on a query. Rather than acquiring a `Command` constructed in this layer, we receive one that contains all of the data acquired across the entire Action Map and its parents.
|
||||
|
||||
## Future considerations
|
||||
|
||||
There are a number of ideas regarding actions that would be fairly trivial to implement given this refactor:
|
||||
- [#6899]: Action IDs
|
||||
- As actions grow to become more widespread within Windows Terminal (i.e. dropdown and jumplist integration),
|
||||
a formal ID system would help users reference the same action throughout the app. With the internal
|
||||
ID system introduced earlier, we would simply introduce a new
|
||||
`std:map<string, InternalActionID> _ExternalIDMap` that is updated like the others, and add a `String ID`
|
||||
property to `Action`.
|
||||
- [#8100] Source Tracking
|
||||
- Identifying where a setting came from can be very beneficial in the settings model and UI. For example,
|
||||
profile settings now have an `OverrideSource` getter that describes what `Profile` object the setting
|
||||
came from (i.e. base layer, profile generator, etc...). A similar system can be used for `Action` in
|
||||
that we record if the action was last modified in defaults.json or settings.json.
|
||||
- There seems to be no desire for action inheritance (i.e. inheriting the name/key-chord from the parent).
|
||||
So this should be sufficient.
|
||||
|
||||
## Resources
|
||||
|
||||
[#885]: https://github.com/microsoft/terminal/issues/885
|
||||
[#6899]: https://github.com/microsoft/terminal/issues/6899
|
||||
[#8100]: https://github.com/microsoft/terminal/issues/8100
|
||||
[#8767]: https://github.com/microsoft/terminal/issues/8767
|
||||
|
||||
Other references:
|
||||
[Settings UI: Actions Page]: https://github.com/microsoft/terminal/issues/6900
|
||||
|
||||
[Settings UI: Actions Page Design]: https://github.com/microsoft/terminal/pulls/9427
|
||||
|
||||
[Action ID Spec]: https://github.com/microsoft/terminal/issues/7175
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the roadmap towards delivering Windows Terminal 2.0 by Winter 2021.
|
||||
This document outlines the roadmap towards delivering Windows Terminal 2.0 by Spring 2021.
|
||||
|
||||
|
||||
## Milestones
|
||||
|
||||
Windows Terminal is engineered and delivered as a set of 6-week milestones. New features will go into [Windows Terminal Preview](https://aka.ms/terminal-preview) first, then a month after they've been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
|
||||
The Windows Terminal project is engineered and delivered as a set of 4-week milestones. New features will go into [Windows Terminal Preview](https://aka.ms/terminal-preview) first, then a month after they've been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
|
||||
|
||||
| Duration | Activity | Releases |
|
||||
| --- | --- | --- |
|
||||
| 4 weeks | Dev Work<br/> <ul><li>Fixes / Features for future Windows Releases</li><li>Fixes / Features for Windows Terminal</li></ul> | Release to Internal Selfhosters at end of week 4 |
|
||||
| 1 week | Quality & Stability<br/> <ul><li>Bug Fixes</li><li>Perf & Stability</li><li>UI Polish</li><li>Tests</li><li>etc.</li></ul>| Push to Microsoft Store at end of week 5 |
|
||||
| 2 weeks | Dev Work<br/> <ul><li>Fixes / Features for future Windows Releases</li><li>Fixes / Features for Windows Terminal</li></ul> | Release to Internal Selfhosters at end of week 2 |
|
||||
| 1 week | Quality & Stability<br/> <ul><li>Bug Fixes</li><li>Perf & Stability</li><li>UI Polish</li><li>Tests</li><li>etc.</li></ul>| Push to Microsoft Store at end of week 3 |
|
||||
| 1 week | Release <br/> <ul><li>Available from [Microsoft Store](https://aka.ms/terminal) & [GitHub Releases](https://github.com/microsoft/terminal/releases)</li><li>Release Notes & Announcement Blog published</li><li>Engineering System Maintenance</li><li>Community Engagement</li><li>Docs</li><li>Future Milestone Planning</li></ul> | Release available from Microsoft Store & GitHub Releases |
|
||||
|
||||
## Terminal Roadmap / Timeline
|
||||
@@ -25,15 +26,12 @@ Below is the schedule for when milestones will be included in release builds of
|
||||
| 2020-08-31 | [1.3] in Windows Terminal Preview<br>[1.2] in Windows Terminal | [Windows Terminal Preview 1.3 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-3-release/) |
|
||||
| 2020-09-30 | [1.4] in Windows Terminal Preview<br>[1.3] in Windows Terminal | [Windows Terminal Preview 1.4 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-4-release/) |
|
||||
| 2020-11-30 | [1.5] in Windows Terminal Preview<br>[1.4] in Windows Terminal | [Windows Terminal Preview 1.5 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-5-release/) |
|
||||
| 2021-01-31 | [1.6] in Windows Terminal Preview<br>[1.5] in Windows Terminal | [Windows Terminal Preview 1.6 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-6-release/) |
|
||||
| 2021-03-01 | [1.7] in Windows Terminal Preview<br>[1.6] in Windows Terminal | [Windows Terminal Preview 1.7 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-7-release/) |
|
||||
| 2021-04-14 | [1.8] in Windows Terminal Preview<br>[1.7] in Windows Terminal | [Windows Terminal Preview 1.8 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-8-release/) |
|
||||
| 2021-05-31 | [1.9] in Windows Terminal Preview<br>[1.8] in Windows Terminal | |
|
||||
| 2021-07-31 | 1.10 in Windows Terminal Preview<br>[1.9] in Windows Terminal | |
|
||||
| 2021-08-30 | 1.11 in Windows Terminal Preview<br>1.10 in Windows Terminal | |
|
||||
| 2021-10-31 | 1.12 in Windows Terminal Preview<br>1.11 in Windows Terminal | |
|
||||
| 2021-11-30 | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
|
||||
| 2021-12-31 | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
|
||||
| 2020-12-31 | [1.6] in Windows Terminal Preview<br>[1.5] in Windows Terminal | |
|
||||
| 2021-01-31 | 1.7 in Windows Terminal Preview<br>[1.6] in Windows Terminal | |
|
||||
| 2021-02-28 | 1.8 in Windows Terminal Preview<br>1.8 in Windows Terminal | |
|
||||
| 2021-03-31 | 1.9 in Windows Terminal Preview<br>1.9 in Windows Terminal | |
|
||||
| 2021-04-30 | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
|
||||
| 2021-05-31 | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
|
||||
|
||||
## Issue Triage & Prioritization
|
||||
|
||||
@@ -86,9 +84,6 @@ Feature Notes:
|
||||
[1.4]: https://github.com/microsoft/terminal/milestone/28
|
||||
[1.5]: https://github.com/microsoft/terminal/milestone/30
|
||||
[1.6]: https://github.com/microsoft/terminal/milestone/31
|
||||
[1.7]: https://github.com/microsoft/terminal/milestone/32
|
||||
[1.8]: https://github.com/microsoft/terminal/milestone/33
|
||||
[1.9]: https://github.com/microsoft/terminal/milestone/34
|
||||
[2.0]: https://github.com/microsoft/terminal/milestone/22
|
||||
[#1564]: https://github.com/microsoft/terminal/issues/1564
|
||||
[#6720]: https://github.com/microsoft/terminal/pull/6720
|
||||
|
||||
BIN
res/Cascadia.ttf
Normal file
BIN
res/Cascadia.ttf
Normal file
Binary file not shown.
BIN
res/CascadiaMono.ttf
Normal file
BIN
res/CascadiaMono.ttf
Normal file
Binary file not shown.
@@ -6,3 +6,16 @@ The images in this directory do not fall under the same [license](https://raw.gi
|
||||
of the Windows Terminal code.
|
||||
|
||||
Please consult the [license](./LICENSE) in this directory for terms applicable to the image assets in this directory.
|
||||
|
||||
## Fonts
|
||||
|
||||
The fonts in this directory do not fall under the same [license](https://raw.githubusercontent.com/microsoft/terminal/main/LICENSE) as the rest
|
||||
of the Windows Terminal code.
|
||||
|
||||
Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadia-code/main/LICENSE) in the
|
||||
[microsoft/cascadia-code](https://github.com/microsoft/cascadia-code) repository for terms applicable to the fonts in this directory.
|
||||
|
||||
### Fonts Included
|
||||
|
||||
* Cascadia Code, Cascadia Mono (2102.25)
|
||||
* from microsoft/cascadia-code@911dc421f333e3b72b97381d16fee5b71eb48f04
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,12 +0,0 @@
|
||||
# Windows Terminal and Console Assets (Fonts)
|
||||
|
||||
The fonts in this directory do not fall under the same [license](https://raw.githubusercontent.com/microsoft/terminal/main/LICENSE) as the rest
|
||||
of the Windows Terminal code.
|
||||
|
||||
Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadia-code/main/LICENSE) in the
|
||||
[microsoft/cascadia-code](https://github.com/microsoft/cascadia-code) repository for terms applicable to the fonts in this directory.
|
||||
|
||||
### Fonts Included
|
||||
|
||||
* Cascadia Code, Cascadia Mono (2106.17)
|
||||
* from microsoft/cascadia-code@fb0bce69c1c12f6c298b8bc1c1d181868f5daa9a
|
||||
@@ -23,8 +23,7 @@
|
||||
<ProjectGuid>{96274800-9574-423E-892A-909FBE2AC8BE}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>EchoCon</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace GUIConsole.ConPTY
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the pseudoconsole and run the process as shown in
|
||||
/// Start the psuedoconsole and run the process as shown in
|
||||
/// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole
|
||||
/// </summary>
|
||||
/// <param name="command">the command to run, e.g. cmd.exe</param>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<Application x:Class="GUIConsole.Wpf.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources />
|
||||
</Application>
|
||||
<Application x:Class="GUIConsole.Wpf.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -1,100 +1,84 @@
|
||||
<Window x:Class="GUIConsole.Wpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="MainWindow"
|
||||
Width="800"
|
||||
Height="450"
|
||||
AllowsTransparency="True"
|
||||
Background="#C7000000"
|
||||
BorderBrush="LightSlateGray"
|
||||
BorderThickness="1"
|
||||
KeyDown="Window_KeyDown"
|
||||
Loaded="Window_Loaded"
|
||||
MouseDown="Window_MouseDown"
|
||||
WindowStyle="None"
|
||||
mc:Ignorable="d">
|
||||
<Window.Resources>
|
||||
<Style x:Key="TitleBarButtonStyle"
|
||||
TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="Segoe MDL2 Assets" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Height" Value="32" />
|
||||
<Setter Property="Width" Value="46" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CloseButtonStyle"
|
||||
BasedOn="{StaticResource TitleBarButtonStyle}"
|
||||
TargetType="{x:Type Button}">
|
||||
<Setter Property="Content" Value="" />
|
||||
<!-- Remove the default Button template's Triggers, otherwise they'll override our trigger below. -->
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Button.Background" Value="Red" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="TitleBarTitle"
|
||||
Grid.Column="0"
|
||||
Padding="10,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="White">
|
||||
GUIConsole
|
||||
</TextBlock>
|
||||
<StackPanel x:Name="TitleBarButtons"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="MinimizeButton"
|
||||
Click="MinimizeButton_Click"
|
||||
Content=""
|
||||
Style="{StaticResource TitleBarButtonStyle}" />
|
||||
<Button x:Name="MaximizeRestoreButton"
|
||||
Click="MaximizeRestoreButton_Click"
|
||||
Content=""
|
||||
FontSize="12"
|
||||
Style="{StaticResource TitleBarButtonStyle}" />
|
||||
<Button x:Name="CloseButton"
|
||||
Click="CloseButton_Click"
|
||||
FontSize="12"
|
||||
Style="{StaticResource CloseButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<ScrollViewer x:Name="TerminalHistoryViewer"
|
||||
Grid.Row="1"
|
||||
ScrollChanged="ScrollViewer_ScrollChanged">
|
||||
<TextBlock x:Name="TerminalHistoryBlock"
|
||||
FontFamily="Consolas"
|
||||
Foreground="White"
|
||||
TextWrapping="Wrap" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
<Window x:Class="GUIConsole.Wpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="MainWindow" Height="450" Width="800"
|
||||
Background="#C7000000"
|
||||
AllowsTransparency="True"
|
||||
WindowStyle="None"
|
||||
MouseDown="Window_MouseDown"
|
||||
BorderThickness="1"
|
||||
BorderBrush="LightSlateGray"
|
||||
|
||||
KeyDown="Window_KeyDown"
|
||||
Loaded="Window_Loaded">
|
||||
<Window.Resources>
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Height" Value="32"/>
|
||||
<Setter Property="Width" Value="46"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource TitleBarButtonStyle}">
|
||||
<Setter Property="Content" Value=""/>
|
||||
<!--Remove the default Button template's Triggers, otherwise they'll override our trigger below.-->
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Button.Background" Value="Red"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="TitleBarTitle" Grid.Column="0" VerticalAlignment="Center" Padding="10 0" Foreground="White">
|
||||
GUIConsole
|
||||
</TextBlock>
|
||||
<StackPanel x:Name="TitleBarButtons" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Top">
|
||||
<Button x:Name="MinimizeButton"
|
||||
Click="MinimizeButton_Click"
|
||||
Content=""
|
||||
Style="{StaticResource TitleBarButtonStyle}"/>
|
||||
<Button x:Name="MaximizeRestoreButton"
|
||||
Click="MaximizeRestoreButton_Click"
|
||||
Content=""
|
||||
FontSize="12"
|
||||
Style="{StaticResource TitleBarButtonStyle}"/>
|
||||
<Button x:Name="CloseButton"
|
||||
Click="CloseButton_Click"
|
||||
FontSize="12"
|
||||
Style="{StaticResource CloseButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<ScrollViewer x:Name="TerminalHistoryViewer" Grid.Row="1" ScrollChanged="ScrollViewer_ScrollChanged">
|
||||
<TextBlock x:Name="TerminalHistoryBlock" FontFamily="Consolas" TextWrapping="Wrap" Foreground="White"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace MiniTerm
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the pseudoconsole and run the process as shown in
|
||||
/// Start the psuedoconsole and run the process as shown in
|
||||
/// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole
|
||||
/// </summary>
|
||||
/// <param name="command">the command to run, e.g. cmd.exe</param>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
// A minimal pixel shader that outlines text
|
||||
|
||||
// The terminal graphics as a texture
|
||||
Texture2D shaderTexture;
|
||||
SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
// Resolution of the shaderTexture
|
||||
float2 Resolution;
|
||||
// Background color as rgba
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
|
||||
// Just ignore the pos parameter.
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
// float4 is tuple of 4 floats, rgba
|
||||
float4 color = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// Read the color value at some offset, will be used as shadow. For the best
|
||||
// effect, read the colors offset on the left, right, top, bottom of this
|
||||
// fragment, as well as on the corners of this fragment.
|
||||
//
|
||||
// You could get away with fewer samples, but the resulting outlines will be
|
||||
// blurrier.
|
||||
|
||||
//left, right, top, bottom:
|
||||
float4 leftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, 0.0)/Resolution.y);
|
||||
float4 rightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, 0.0)/Resolution.y);
|
||||
float4 topColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 0.0, 1.0)/Resolution.y);
|
||||
float4 bottomColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 0.0, -1.0)/Resolution.y);
|
||||
|
||||
// Corners
|
||||
float4 topLeftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, 1.0)/Resolution.y);
|
||||
float4 topRightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, 1.0)/Resolution.y);
|
||||
float4 bottomLeftColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2( 1.0, -1.0)/Resolution.y);
|
||||
float4 bottomRightColor = shaderTexture.Sample(samplerState, tex+1.0*Scale*float2(-1.0, -1.0)/Resolution.y);
|
||||
|
||||
|
||||
// Now, if any of those adjacent cells has text in it, then the *color vec4
|
||||
// will have a non-zero .w (which is used for alpha). Use that alpha value
|
||||
// to add some black to the current fragment.
|
||||
//
|
||||
// This will result in only coloring fragments adjacent to text, but leaving
|
||||
// background images (for example) untouched.
|
||||
float3 outlineColor = float3(0, 0, 0);
|
||||
float4 result = color;
|
||||
result = result + float4(outlineColor, leftColor.w);
|
||||
result = result + float4(outlineColor, rightColor.w);
|
||||
result = result + float4(outlineColor, topColor.w);
|
||||
result = result + float4(outlineColor, bottomColor.w);
|
||||
|
||||
result = result + float4(outlineColor, topLeftColor.w);
|
||||
result = result + float4(outlineColor, topRightColor.w);
|
||||
result = result + float4(outlineColor, bottomLeftColor.w);
|
||||
result = result + float4(outlineColor, bottomRightColor.w);
|
||||
return result;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<WindowsPerformanceRecorder Version="1.0" Author="Microsoft Corporation" Copyright="Microsoft Corporation" Company="Microsoft Corporation">
|
||||
<Profiles>
|
||||
<EventCollector Id="EventCollector_Terminal" Name="Terminal">
|
||||
<BufferSize Value="64" />
|
||||
<Buffers Value="4" />
|
||||
</EventCollector>
|
||||
<EventProvider Id="EventProvider_TerminalControl" Name="28c82e50-57af-5a86-c25b-e39cd990032b" />
|
||||
<EventProvider Id="EventProvider_TerminalConnection" Name="e912fe7b-eeb6-52a5-c628-abe388e5f792" />
|
||||
<EventProvider Id="EventProvider_TerminalSettingsModel" Name="be579944-4d33-5202-e5d6-a7a57f1935cb" />
|
||||
<EventProvider Id="EventProvider_TerminalApp" Name="24a1622f-7da7-5c77-3303-d850bd1ab2ed" />
|
||||
<EventProvider Id="EventProvider_TerminalWin32Host" Name="56c06166-2e2e-5f4d-7ff3-74f4b78c87d6" />
|
||||
<EventProvider Id="EventProvider_TerminalRemoting" Name="d6f04aad-629f-539a-77c1-73f5c3e4aa7b" />
|
||||
<EventProvider Id="EventProvider_TerminalDirectX" Name="c93e739e-ae50-5a14-78e7-f171e947535d" />
|
||||
<Profile Id="Terminal.Verbose.File" Name="Terminal" Description="Terminal" LoggingMode="File" DetailLevel="Verbose">
|
||||
<Collectors>
|
||||
<EventCollectorId Value="EventCollector_Terminal">
|
||||
<EventProviders>
|
||||
<EventProviderId Value="EventProvider_TerminalControl" />
|
||||
<EventProviderId Value="EventProvider_TerminalConnection" />
|
||||
<EventProviderId Value="EventProvider_TerminalSettingsModel" />
|
||||
<EventProviderId Value="EventProvider_TerminalApp" />
|
||||
<EventProviderId Value="EventProvider_TerminalWin32Host" />
|
||||
<EventProviderId Value="EventProvider_TerminalRemoting" />
|
||||
<EventProviderId Value="EventProvider_TerminalDirectX" />
|
||||
</EventProviders>
|
||||
</EventCollectorId>
|
||||
</Collectors>
|
||||
</Profile>
|
||||
<Profile Id="Terminal.Light.File" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="File" DetailLevel="Light" />
|
||||
<Profile Id="Terminal.Verbose.Memory" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="Memory" DetailLevel="Verbose" />
|
||||
<Profile Id="Terminal.Light.Memory" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="Memory" DetailLevel="Light" />
|
||||
</Profiles>
|
||||
</WindowsPerformanceRecorder>
|
||||
@@ -11,8 +11,18 @@
|
||||
// - attr - the default text attribute
|
||||
// Return Value:
|
||||
// - constructed object
|
||||
ATTR_ROW::ATTR_ROW(const uint16_t width, const TextAttribute attr) :
|
||||
_data(width, attr) {}
|
||||
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
_list.emplace_back(TextAttributeRun(cchRowWidth, attr));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
FAIL_FAST_CAUGHT_EXCEPTION();
|
||||
}
|
||||
_cchRowWidth = cchRowWidth;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets all properties of the ATTR_ROW to default values
|
||||
@@ -20,7 +30,8 @@ ATTR_ROW::ATTR_ROW(const uint16_t width, const TextAttribute attr) :
|
||||
// - attr - The default text attributes to use on text in this row.
|
||||
void ATTR_ROW::Reset(const TextAttribute attr)
|
||||
{
|
||||
_data.replace(0, _data.size(), attr);
|
||||
_list.clear();
|
||||
_list.emplace_back(TextAttributeRun(_cchRowWidth, attr));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -32,9 +43,48 @@ void ATTR_ROW::Reset(const TextAttribute attr)
|
||||
// - newWidth - The new width of the row.
|
||||
// Return Value:
|
||||
// - <none>, throws exceptions on failures.
|
||||
void ATTR_ROW::Resize(const uint16_t newWidth)
|
||||
void ATTR_ROW::Resize(const size_t newWidth)
|
||||
{
|
||||
_data.resize_trailing_extent(newWidth);
|
||||
THROW_HR_IF(E_INVALIDARG, 0 == newWidth);
|
||||
|
||||
// Easy case. If the new row is longer, increase the length of the last run by how much new space there is.
|
||||
if (newWidth > _cchRowWidth)
|
||||
{
|
||||
// Get the attribute that covers the final column of old width.
|
||||
const auto runPos = FindAttrIndex(_cchRowWidth - 1, nullptr);
|
||||
auto& run = _list.at(runPos);
|
||||
|
||||
// Extend its length by the additional columns we're adding.
|
||||
run.SetLength(run.GetLength() + newWidth - _cchRowWidth);
|
||||
|
||||
// Store that the new total width we represent is the new width.
|
||||
_cchRowWidth = newWidth;
|
||||
}
|
||||
// harder case: new row is shorter.
|
||||
else
|
||||
{
|
||||
// Get the attribute that covers the final column of the new width
|
||||
size_t CountOfAttr = 0;
|
||||
const auto runPos = FindAttrIndex(newWidth - 1, &CountOfAttr);
|
||||
auto& run = _list.at(runPos);
|
||||
|
||||
// CountOfAttr was given to us as "how many columns left from this point forward are covered by the returned run"
|
||||
// So if the original run was B5 covering a 5 size OldWidth and we have a NewWidth of 3
|
||||
// then when we called FindAttrIndex, it returned the B5 as the pIndexedRun and a 2 for how many more segments it covers
|
||||
// after and including the 3rd column.
|
||||
// B5-2 = B3, which is what we desire to cover the new 3 size buffer.
|
||||
run.SetLength(run.GetLength() - CountOfAttr + 1);
|
||||
|
||||
// Store that the new total width we represent is the new width.
|
||||
_cchRowWidth = newWidth;
|
||||
|
||||
// Erase segments after the one we just updated.
|
||||
_list.erase(_list.cbegin() + runPos + 1, _list.cend());
|
||||
|
||||
// NOTE: Under some circumstances here, we have leftover run segments in memory or blank run segments
|
||||
// in memory. We're not going to waste time redimensioning the array in the heap. We're just noting that the useful
|
||||
// portions of it have changed.
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -45,23 +95,104 @@ void ATTR_ROW::Resize(const uint16_t newWidth)
|
||||
// - the text attribute at column
|
||||
// Note:
|
||||
// - will throw on error
|
||||
TextAttribute ATTR_ROW::GetAttrByColumn(const uint16_t column) const
|
||||
TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column) const
|
||||
{
|
||||
return _data.at(column);
|
||||
return GetAttrByColumn(column, nullptr);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - returns a copy of the TextAttribute at the specified column
|
||||
// Arguments:
|
||||
// - column - the column to get the attribute for
|
||||
// - pApplies - if given, fills how long this attribute will apply for
|
||||
// Return Value:
|
||||
// - the text attribute at column
|
||||
// Note:
|
||||
// - will throw on error
|
||||
TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column,
|
||||
size_t* const pApplies) const
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _cchRowWidth);
|
||||
const auto runPos = FindAttrIndex(column, pApplies);
|
||||
return _list.at(runPos).GetAttributes();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - reports how many runs we have stored (to be used for some optimizations
|
||||
// Return Value:
|
||||
// - Count of runs. 1 means we have 1 color to represent the entire row.
|
||||
size_t ATTR_ROW::GetNumberOfRuns() const noexcept
|
||||
{
|
||||
return _list.size();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine finds the nth attribute in this ATTR_ROW.
|
||||
// Arguments:
|
||||
// - index - which attribute to find
|
||||
// - applies - on output, contains corrected length of indexed attr.
|
||||
// for example, if the attribute string was { 5, BLUE } and the requested
|
||||
// index was 3, CountOfAttr would be 2.
|
||||
// Return Value:
|
||||
// - const reference to attribute run object
|
||||
size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
|
||||
{
|
||||
FAIL_FAST_IF(!(index < _cchRowWidth)); // The requested index cannot be longer than the total length described by this set of Attrs.
|
||||
|
||||
size_t cTotalLength = 0;
|
||||
|
||||
FAIL_FAST_IF(!(_list.size() > 0)); // There should be a non-zero and positive number of items in the array.
|
||||
|
||||
// Scan through the internal array from position 0 adding up the lengths that each attribute applies to
|
||||
auto runPos = _list.cbegin();
|
||||
do
|
||||
{
|
||||
cTotalLength += runPos->GetLength();
|
||||
|
||||
if (cTotalLength > index)
|
||||
{
|
||||
// If we've just passed up the requested index with the length we added, break early
|
||||
break;
|
||||
}
|
||||
|
||||
runPos++;
|
||||
} while (runPos < _list.cend());
|
||||
|
||||
// we should have broken before falling out the while case.
|
||||
// if we didn't break, then this ATTR_ROW wasn't filled with enough attributes for the entire row of characters
|
||||
FAIL_FAST_IF(runPos >= _list.cend());
|
||||
|
||||
// The remaining iterator position is the position of the attribute that is applicable at the position requested (index)
|
||||
// Calculate its remaining applicability if requested
|
||||
|
||||
// The length on which the found attribute applies is the total length seen so far minus the index we were searching for.
|
||||
FAIL_FAST_IF(!(cTotalLength > index)); // The length of all attributes we counted up so far should be longer than the index requested or we'll underflow.
|
||||
|
||||
if (nullptr != pApplies)
|
||||
{
|
||||
const auto attrApplies = cTotalLength - index;
|
||||
FAIL_FAST_IF(!(attrApplies > 0)); // An attribute applies for >0 characters
|
||||
// MSFT: 17130145 - will restore this and add a better assert to catch the real issue.
|
||||
//FAIL_FAST_IF(!(attrApplies <= _cchRowWidth)); // An attribute applies for a maximum of the total length available to us
|
||||
|
||||
*pApplies = attrApplies;
|
||||
}
|
||||
|
||||
return runPos - _list.cbegin();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Finds the hyperlink IDs present in this row and returns them
|
||||
// Return value:
|
||||
// - The hyperlink IDs present in this row
|
||||
std::vector<uint16_t> ATTR_ROW::GetHyperlinks() const
|
||||
std::vector<uint16_t> ATTR_ROW::GetHyperlinks()
|
||||
{
|
||||
std::vector<uint16_t> ids;
|
||||
for (const auto& run : _data.runs())
|
||||
for (const auto& run : _list)
|
||||
{
|
||||
if (run.value.IsHyperlink())
|
||||
if (run.GetAttributes().IsHyperlink())
|
||||
{
|
||||
ids.emplace_back(run.value.GetHyperlinkId());
|
||||
ids.emplace_back(run.GetAttributes().GetHyperlinkId());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
@@ -74,10 +205,12 @@ std::vector<uint16_t> ATTR_ROW::GetHyperlinks() const
|
||||
// - attr - Attribute (color) to fill remaining characters with
|
||||
// Return Value:
|
||||
// - <none>
|
||||
bool ATTR_ROW::SetAttrToEnd(const uint16_t beginIndex, const TextAttribute attr)
|
||||
bool ATTR_ROW::SetAttrToEnd(const UINT iStart, const TextAttribute attr)
|
||||
{
|
||||
_data.replace(gsl::narrow<uint16_t>(beginIndex), _data.size(), attr);
|
||||
return true;
|
||||
size_t const length = _cchRowWidth - iStart;
|
||||
|
||||
const TextAttributeRun run(length, attr);
|
||||
return SUCCEEDED(InsertAttrRuns({ &run, 1 }, iStart, _cchRowWidth - 1, _cchRowWidth));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -88,47 +221,419 @@ bool ATTR_ROW::SetAttrToEnd(const uint16_t beginIndex, const TextAttribute attr)
|
||||
// - replaceWith - the new value for the matching runs' attributes.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith)
|
||||
void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept
|
||||
{
|
||||
_data.replace_values(toBeReplacedAttr, replaceWith);
|
||||
for (auto& run : _list)
|
||||
{
|
||||
if (run.GetAttributes() == toBeReplacedAttr)
|
||||
{
|
||||
run.SetAttributes(replaceWith);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Takes an attribute, and merges it into this row from beginIndex (inclusive) to endIndex (exclusive).
|
||||
// - For example, if the current row was was [{4, BLUE}], the merge arguments were
|
||||
// { beginIndex = 1, endIndex = 3, newAttr = RED }, then the row would modified to be
|
||||
// [{ 1, BLUE}, {2, RED}, {1, BLUE}].
|
||||
// - Takes a array of attribute runs, and inserts them into this row from startIndex to endIndex.
|
||||
// - For example, if the current row was was [{4, BLUE}], the merge string
|
||||
// was [{ 2, RED }], with (StartIndex, EndIndex) = (1, 2),
|
||||
// then the row would modified to be = [{ 1, BLUE}, {2, RED}, {1, BLUE}].
|
||||
// Arguments:
|
||||
// - beginIndex, endIndex: The [beginIndex, endIndex) range that's to be replaced with newAttr.
|
||||
// - newAttr: The attribute to merge into this row.
|
||||
// - rgInsertAttrs - The array of attrRuns to merge into this row.
|
||||
// - cInsertAttrs - The number of elements in rgInsertAttrs
|
||||
// - iStart - The index in the row to place the array of runs.
|
||||
// - iEnd - the final index of the merge runs
|
||||
// - BufferWidth - the width of the row.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void ATTR_ROW::Replace(const uint16_t beginIndex, const uint16_t endIndex, const TextAttribute& newAttr)
|
||||
// - 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,
|
||||
const size_t iStart,
|
||||
const size_t iEnd,
|
||||
const size_t cBufferWidth)
|
||||
{
|
||||
_data.replace(beginIndex, endIndex, newAttr);
|
||||
// Definitions:
|
||||
// Existing Run = The run length encoded color array we're already storing in memory before this was called.
|
||||
// Insert Run = The run length encoded color array that someone is asking us to inject into our stored memory run.
|
||||
// New Run = The run length encoded color array that we have to allocate and rebuild to store internally
|
||||
// which will replace Existing Run at the end of this function.
|
||||
// Example:
|
||||
// cBufferWidth = 10.
|
||||
// Existing Run: R3 -> G5 -> B2
|
||||
// Insert Run: Y1 -> N1 at iStart = 5 and iEnd = 6
|
||||
// (rgInsertAttrs is a 2 length array with Y1->N1 in it and cInsertAttrs = 2)
|
||||
// Final Run: R3 -> G2 -> Y1 -> N1 -> G1 -> B2
|
||||
|
||||
// We'll need to know what the last valid column is for some calculations versus iEnd
|
||||
// because iEnd is specified to us as an inclusive index value.
|
||||
// Do the -1 math here now so we don't have to have -1s scattered all over this function.
|
||||
const size_t iLastBufferCol = cBufferWidth - 1;
|
||||
|
||||
// If the insertion size is 1, do some pre-processing to
|
||||
// see if we can get this done quickly.
|
||||
if (newAttrs.size() == 1)
|
||||
{
|
||||
// Get the new color attribute we're trying to apply
|
||||
const TextAttribute NewAttr = til::at(newAttrs, 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.
|
||||
if (_list.size() == 1 && _list.at(0).GetAttributes() == NewAttr)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
// .. otherwise if we internally have a list of 2 or more and we're about to insert a single color
|
||||
// it's possible that we just walk left-to-right through the row and find a quick exit.
|
||||
else if (iStart >= 0 && iStart == iEnd)
|
||||
{
|
||||
// First we try to find the run where the insertion happens, using lowerBound and upperBound to track
|
||||
// where we are currently at.
|
||||
const auto begin = _list.begin();
|
||||
size_t lowerBound = 0;
|
||||
size_t upperBound = 0;
|
||||
for (size_t i = 0; i < _list.size(); i++)
|
||||
{
|
||||
const auto curr = begin + i;
|
||||
upperBound += curr->GetLength();
|
||||
|
||||
if (iStart >= lowerBound && iStart < upperBound)
|
||||
{
|
||||
// The run that we try to insert into has the same color as the new one.
|
||||
// e.g.
|
||||
// AAAAABBBBBBBCCC
|
||||
// ^
|
||||
// AAAAABBBBBBBCCC
|
||||
//
|
||||
// 'B' is the new color and '^' represents where iStart is. We don't have to
|
||||
// do anything.
|
||||
if (curr->GetAttributes() == NewAttr)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// If the current run has length of exactly one, we can simply change the attribute
|
||||
// of the current run.
|
||||
// e.g.
|
||||
// AAAAABCCCCCCCCC
|
||||
// ^
|
||||
// AAAAADCCCCCCCCC
|
||||
//
|
||||
// Here 'D' is the new color.
|
||||
if (curr->GetLength() == 1)
|
||||
{
|
||||
curr->SetAttributes(NewAttr);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// If the insertion happens at current run's lower boundary...
|
||||
if (iStart == lowerBound && i > 0)
|
||||
{
|
||||
const auto prev = std::prev(curr, 1);
|
||||
// ... and the previous run has the same color as the new one, we can
|
||||
// just adjust the counts in the existing two elements in our internal list.
|
||||
// e.g.
|
||||
// AAAAABBBBBBBCCC
|
||||
// ^
|
||||
// AAAAAABBBBBBCCC
|
||||
//
|
||||
// Here 'A' is the new color.
|
||||
if (NewAttr == prev->GetAttributes())
|
||||
{
|
||||
prev->IncrementLength();
|
||||
curr->DecrementLength();
|
||||
|
||||
// If we just reduced the right half to zero, just erase it out of the list.
|
||||
if (curr->GetLength() == 0)
|
||||
{
|
||||
_list.erase(curr);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// If the insertion happens at current run's upper boundary...
|
||||
if (iStart == upperBound - 1 && i + 1 < _list.size())
|
||||
{
|
||||
// ...then let's try our luck with the next run if possible. This is basically the opposite
|
||||
// of what we did with the previous run.
|
||||
// e.g.
|
||||
// AAAAAABBBBBBCCC
|
||||
// ^
|
||||
// AAAAABBBBBBBCCC
|
||||
//
|
||||
// Here 'B' is the new color.
|
||||
const auto next = std::next(curr, 1);
|
||||
if (NewAttr == next->GetAttributes())
|
||||
{
|
||||
curr->DecrementLength();
|
||||
next->IncrementLength();
|
||||
|
||||
if (curr->GetLength() == 0)
|
||||
{
|
||||
_list.erase(curr);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Advance one run in the _list.
|
||||
lowerBound = upperBound;
|
||||
|
||||
// The lowerBound is larger than iStart, which means we fail to find an early exit at the run
|
||||
// where the insertion happens. We can just break out.
|
||||
if (lowerBound > iStart)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're about to cover the entire existing run with a new one, we can also make an optimization.
|
||||
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());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// In the worst case scenario, we will need a new run that is the length of
|
||||
// The existing run in memory + The new run in memory + 1.
|
||||
// This worst case occurs when we inject a new item in the middle of an existing run like so
|
||||
// Existing R3->B5->G2, Insertion Y2 starting at 5 (in the middle of the B5)
|
||||
// becomes R3->B2->Y2->B1->G2.
|
||||
// The original run was 3 long. The insertion run was 1 long. We need 1 more for the
|
||||
// fact that an existing piece of the run was split in half (to hold the latter half).
|
||||
const size_t cNewRun = _list.size() + newAttrs.size() + 1;
|
||||
decltype(_list) newRun;
|
||||
newRun.reserve(cNewRun);
|
||||
|
||||
// We will start analyzing from the beginning of our existing run.
|
||||
// Use some pointers to keep track of where we are in walking through our runs.
|
||||
|
||||
// Get the existing run that we'll be updating/manipulating.
|
||||
const auto existingRun = _list.begin();
|
||||
auto pExistingRunPos = existingRun;
|
||||
const auto pExistingRunEnd = _list.end();
|
||||
auto pInsertRunPos = newAttrs.begin();
|
||||
size_t cInsertRunRemaining = newAttrs.size();
|
||||
size_t iExistingRunCoverage = 0;
|
||||
|
||||
// Copy the existing run into the new buffer up to the "start index" where the new run will be injected.
|
||||
// If the new run starts at 0, we have nothing to copy from the beginning.
|
||||
if (iStart != 0)
|
||||
{
|
||||
// While we're less than the desired insertion position...
|
||||
while (iExistingRunCoverage < iStart)
|
||||
{
|
||||
// Add up how much length we can cover by copying an item from the existing run.
|
||||
iExistingRunCoverage += pExistingRunPos->GetLength();
|
||||
|
||||
// Copy it to the new run buffer and advance both pointers.
|
||||
newRun.push_back(*pExistingRunPos++);
|
||||
}
|
||||
|
||||
// When we get to this point, we've copied full segments from the original existing run
|
||||
// into our new run buffer. We will have 1 or more full segments of color attributes and
|
||||
// we MIGHT have to cut the last copied segment's length back depending on where the inserted
|
||||
// attributes will fall in the final/new run.
|
||||
// Some examples:
|
||||
// - Starting with the original string R3 -> G5 -> B2
|
||||
// - 1. If the insertion is Y5 at start index 3
|
||||
// We are trying to get a result/final/new run of R3 -> Y5 -> B2.
|
||||
// We just copied R3 to the new destination buffer and we cang skip down and start inserting the new attrs.
|
||||
// - 2. If the insertion is Y3 at start index 5
|
||||
// We are trying to get a result/final/new run of R3 -> G2 -> Y3 -> B2.
|
||||
// We just copied R3 -> G5 to the new destination buffer with the code above.
|
||||
// But the insertion is going to cut out some of the length of the G5.
|
||||
// We need to fix this up below so it says G2 instead to leave room for the Y3 to fit in
|
||||
// the new/final run.
|
||||
|
||||
// Fetch out the length so we can fix it up based on the below conditions.
|
||||
size_t length = newRun.back().GetLength();
|
||||
|
||||
// If we've covered more cells already than the start of the attributes to be inserted...
|
||||
if (iExistingRunCoverage > iStart)
|
||||
{
|
||||
// ..then subtract some of the length of the final cell we copied.
|
||||
// We want to take remove the difference in distance between the cells we've covered in the new
|
||||
// run and the insertion point.
|
||||
// (This turns G5 into G2 from Example 2 just above)
|
||||
length -= (iExistingRunCoverage - iStart);
|
||||
}
|
||||
|
||||
// Now we're still on that "last cell copied" into the new run.
|
||||
// If the color of that existing copied cell matches the color of the first segment
|
||||
// of the run we're about to insert, we can just increment the length to extend the coverage.
|
||||
if (newRun.back().GetAttributes() == pInsertRunPos->GetAttributes())
|
||||
{
|
||||
length += pInsertRunPos->GetLength();
|
||||
|
||||
// Since the color matched, we have already "used up" part of the insert run
|
||||
// and can skip it in our big "memcopy" step below that will copy the bulk of the insert run.
|
||||
cInsertRunRemaining--;
|
||||
pInsertRunPos++;
|
||||
}
|
||||
|
||||
// We're done manipulating the length. Store it back.
|
||||
newRun.back().SetLength(length);
|
||||
}
|
||||
|
||||
// Bulk copy the majority (or all, depending on circumstance) of the insert run into the final run buffer.
|
||||
std::copy_n(pInsertRunPos, cInsertRunRemaining, std::back_inserter(newRun));
|
||||
|
||||
// We're technically done with the insert run now and have 0 remaining, but won't bother updating its pointers
|
||||
// and counts any further because we won't use them.
|
||||
|
||||
// Now we need to move our pointer for the original existing run forward and update our counts
|
||||
// on how many cells we could have copied from the source before finishing off the new run.
|
||||
while (iExistingRunCoverage <= iEnd)
|
||||
{
|
||||
FAIL_FAST_IF(!(pExistingRunPos != pExistingRunEnd));
|
||||
iExistingRunCoverage += pExistingRunPos->GetLength();
|
||||
pExistingRunPos++;
|
||||
}
|
||||
|
||||
// If we still have original existing run cells remaining, copy them into the final new run.
|
||||
if (pExistingRunPos != pExistingRunEnd || iExistingRunCoverage != (iEnd + 1))
|
||||
{
|
||||
// We advanced the existing run pointer and its count to on or past the end of what the insertion run filled in.
|
||||
// If this ended up being past the end of what the insertion run covers, we have to account for the cells after
|
||||
// the insertion run but before the next piece of the original existing run.
|
||||
// The example in this case is if we had...
|
||||
// Existing Run = R3 -> G5 -> B2 -> X5
|
||||
// Insert Run = Y2 @ iStart = 7 and iEnd = 8
|
||||
// ... then at this point in time, our states would look like...
|
||||
// New Run so far = R3 -> G4 -> Y2
|
||||
// Existing Run Pointer is at X5
|
||||
// Existing run coverage count at 3 + 5 + 2 = 10.
|
||||
// However, in order to get the final desired New Run
|
||||
// (which is R3 -> G4 -> Y2 -> B1 -> X5)
|
||||
// we would need to grab a piece of that B2 we already skipped past.
|
||||
// iExistingRunCoverage = 10. iEnd = 8. iEnd+1 = 9. 10 > 9. So we skipped something.
|
||||
if (iExistingRunCoverage > (iEnd + 1))
|
||||
{
|
||||
// Back up the existing run pointer so we can grab the piece we skipped.
|
||||
pExistingRunPos--;
|
||||
|
||||
// If the color matches what's already in our run, just increment the count value.
|
||||
// This case is slightly off from the example above. This case is for if the B2 above was actually Y2.
|
||||
// That Y2 from the existing run is the same color as the Y2 we just filled a few columns left in the final run
|
||||
// so we can just adjust the final run's column count instead of adding another segment here.
|
||||
if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
{
|
||||
size_t length = newRun.back().GetLength();
|
||||
length += (iExistingRunCoverage - (iEnd + 1));
|
||||
newRun.back().SetLength(length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the color didn't match, then we just need to copy the piece we skipped and adjust
|
||||
// its length for the discrepancy in columns not yet covered by the final/new run.
|
||||
|
||||
// Move forward to a blank spot in the new run
|
||||
newRun.emplace_back();
|
||||
|
||||
// Copy the existing run's color information to the new run
|
||||
newRun.back().SetAttributes(pExistingRunPos->GetAttributes());
|
||||
|
||||
// Adjust the length of that copied color to cover only the reduced number of columns needed
|
||||
// now that some have been replaced by the insert run.
|
||||
newRun.back().SetLength(iExistingRunCoverage - (iEnd + 1));
|
||||
}
|
||||
|
||||
// Now that we're done recovering a piece of the existing run we skipped, move the pointer forward again.
|
||||
pExistingRunPos++;
|
||||
}
|
||||
|
||||
// OK. In this case, we didn't skip anything. The end of the insert run fell right at a boundary
|
||||
// in columns that was in the original existing run.
|
||||
// However, the next piece of the original existing run might happen to have the same color attribute
|
||||
// as the final piece of what we just copied.
|
||||
// As an example...
|
||||
// Existing Run = R3 -> G5 -> B2.
|
||||
// Insert Run = B5 @ iStart = 3 and iEnd = 7
|
||||
// New Run so far = R3 -> B5
|
||||
// New Run desired when done = R3 -> B7
|
||||
// Existing run pointer is on B2.
|
||||
// We want to merge the 2 from the B2 into the B5 so we get B7.
|
||||
else if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
{
|
||||
// Add the value from the existing run into the current new run position.
|
||||
size_t length = newRun.back().GetLength();
|
||||
length += pExistingRunPos->GetLength();
|
||||
newRun.back().SetLength(length);
|
||||
|
||||
// Advance the existing run position since we consumed its value and merged it in.
|
||||
pExistingRunPos++;
|
||||
}
|
||||
|
||||
// Now bulk copy any segments left in the original existing run
|
||||
if (pExistingRunPos < pExistingRunEnd)
|
||||
{
|
||||
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), std::back_inserter(newRun));
|
||||
}
|
||||
}
|
||||
|
||||
// OK, phew. We're done. Now we just need to free the existing run and store the new run in its place.
|
||||
_list.swap(newRun);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - packs a vector of TextAttribute into a vector of TextAttributeRun
|
||||
// Arguments:
|
||||
// - attrs - text attributes to pack
|
||||
// Return Value:
|
||||
// - packed text attribute run
|
||||
std::vector<TextAttributeRun> ATTR_ROW::PackAttrs(const std::vector<TextAttribute>& attrs)
|
||||
{
|
||||
std::vector<TextAttributeRun> runs;
|
||||
if (attrs.empty())
|
||||
{
|
||||
return runs;
|
||||
}
|
||||
for (auto attr : attrs)
|
||||
{
|
||||
if (runs.empty() || runs.back().GetAttributes() != attr)
|
||||
{
|
||||
runs.emplace_back(TextAttributeRun(1, attr));
|
||||
}
|
||||
else
|
||||
{
|
||||
runs.back().SetLength(runs.back().GetLength() + 1);
|
||||
}
|
||||
}
|
||||
return runs;
|
||||
}
|
||||
|
||||
ATTR_ROW::const_iterator ATTR_ROW::begin() const noexcept
|
||||
{
|
||||
return _data.begin();
|
||||
return AttrRowIterator(this);
|
||||
}
|
||||
|
||||
ATTR_ROW::const_iterator ATTR_ROW::end() const noexcept
|
||||
{
|
||||
return _data.end();
|
||||
return AttrRowIterator::CreateEndIterator(this);
|
||||
}
|
||||
|
||||
ATTR_ROW::const_iterator ATTR_ROW::cbegin() const noexcept
|
||||
{
|
||||
return _data.cbegin();
|
||||
return AttrRowIterator(this);
|
||||
}
|
||||
|
||||
ATTR_ROW::const_iterator ATTR_ROW::cend() const noexcept
|
||||
{
|
||||
return _data.cend();
|
||||
return AttrRowIterator::CreateEndIterator(this);
|
||||
}
|
||||
|
||||
bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept
|
||||
{
|
||||
return a._data == b._data;
|
||||
return (a._list.size() == b._list.size() &&
|
||||
a._list.data() == b._list.data() &&
|
||||
a._cchRowWidth == b._cchRowWidth);
|
||||
}
|
||||
|
||||
@@ -20,17 +20,16 @@ Revision History:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "til/rle.h"
|
||||
#include "TextAttribute.hpp"
|
||||
#include "TextAttributeRun.hpp"
|
||||
#include "AttrRowIterator.hpp"
|
||||
|
||||
class ATTR_ROW final
|
||||
{
|
||||
using rle_vector = til::small_rle<TextAttribute, uint16_t, 1>;
|
||||
|
||||
public:
|
||||
using const_iterator = rle_vector::const_iterator;
|
||||
using const_iterator = typename AttrRowIterator;
|
||||
|
||||
ATTR_ROW(uint16_t width, TextAttribute attr);
|
||||
ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
|
||||
noexcept;
|
||||
|
||||
~ATTR_ROW() = default;
|
||||
|
||||
@@ -40,13 +39,28 @@ public:
|
||||
noexcept = default;
|
||||
ATTR_ROW& operator=(ATTR_ROW&&) noexcept = default;
|
||||
|
||||
TextAttribute GetAttrByColumn(uint16_t column) const;
|
||||
std::vector<uint16_t> GetHyperlinks() const;
|
||||
TextAttribute GetAttrByColumn(const size_t column) const;
|
||||
TextAttribute GetAttrByColumn(const size_t column,
|
||||
size_t* const pApplies) const;
|
||||
|
||||
bool SetAttrToEnd(uint16_t beginIndex, TextAttribute attr);
|
||||
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith);
|
||||
void Resize(uint16_t newWidth);
|
||||
void Replace(uint16_t beginIndex, uint16_t endIndex, const TextAttribute& newAttr);
|
||||
size_t GetNumberOfRuns() const noexcept;
|
||||
|
||||
size_t FindAttrIndex(const size_t index,
|
||||
size_t* const pApplies) const;
|
||||
|
||||
std::vector<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,
|
||||
const size_t iStart,
|
||||
const size_t iEnd,
|
||||
const size_t cBufferWidth);
|
||||
|
||||
static std::vector<TextAttributeRun> PackAttrs(const std::vector<TextAttribute>& attrs);
|
||||
|
||||
const_iterator begin() const noexcept;
|
||||
const_iterator end() const noexcept;
|
||||
@@ -55,14 +69,17 @@ public:
|
||||
const_iterator cend() const noexcept;
|
||||
|
||||
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
|
||||
friend class AttrRowIterator;
|
||||
friend class ROW;
|
||||
|
||||
private:
|
||||
void Reset(const TextAttribute attr);
|
||||
|
||||
rle_vector _data;
|
||||
boost::container::small_vector<TextAttributeRun, 1> _list;
|
||||
size_t _cchRowWidth;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class AttrRowTests;
|
||||
friend class CommonState;
|
||||
#endif
|
||||
};
|
||||
|
||||
136
src/buffer/out/AttrRowIterator.cpp
Normal file
136
src/buffer/out/AttrRowIterator.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "AttrRowIterator.hpp"
|
||||
#include "AttrRow.hpp"
|
||||
|
||||
AttrRowIterator AttrRowIterator::CreateEndIterator(const ATTR_ROW* const attrRow) noexcept
|
||||
{
|
||||
AttrRowIterator it{ attrRow };
|
||||
it._setToEnd();
|
||||
return it;
|
||||
}
|
||||
|
||||
AttrRowIterator::AttrRowIterator(const ATTR_ROW* const attrRow) noexcept :
|
||||
_pAttrRow{ attrRow },
|
||||
_run{ attrRow->_list.cbegin() },
|
||||
_currentAttributeIndex{ 0 },
|
||||
_exceeded{ false }
|
||||
{
|
||||
}
|
||||
|
||||
AttrRowIterator::operator bool() const noexcept
|
||||
{
|
||||
return !_exceeded && _run < _pAttrRow->_list.cend();
|
||||
}
|
||||
|
||||
bool AttrRowIterator::operator==(const AttrRowIterator& it) const noexcept
|
||||
{
|
||||
return (_pAttrRow == it._pAttrRow &&
|
||||
_run == it._run &&
|
||||
_currentAttributeIndex == it._currentAttributeIndex &&
|
||||
_exceeded == it._exceeded);
|
||||
}
|
||||
|
||||
bool AttrRowIterator::operator!=(const AttrRowIterator& it) const noexcept
|
||||
{
|
||||
return !(*this == it);
|
||||
}
|
||||
|
||||
AttrRowIterator& AttrRowIterator::operator+=(const ptrdiff_t& movement)
|
||||
{
|
||||
if (!_exceeded)
|
||||
{
|
||||
if (movement >= 0)
|
||||
{
|
||||
_increment(gsl::narrow<size_t>(movement));
|
||||
}
|
||||
else
|
||||
{
|
||||
_decrement(gsl::narrow<size_t>(-movement));
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AttrRowIterator& AttrRowIterator::operator-=(const ptrdiff_t& movement)
|
||||
{
|
||||
return this->operator+=(-movement);
|
||||
}
|
||||
|
||||
const TextAttribute* AttrRowIterator::operator->() const
|
||||
{
|
||||
THROW_HR_IF(E_BOUNDS, _exceeded);
|
||||
return &_run->GetAttributes();
|
||||
}
|
||||
|
||||
const TextAttribute& AttrRowIterator::operator*() const
|
||||
{
|
||||
THROW_HR_IF(E_BOUNDS, _exceeded);
|
||||
return _run->GetAttributes();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - increments the index the iterator points to
|
||||
// Arguments:
|
||||
// - count - the amount to increment by
|
||||
void AttrRowIterator::_increment(size_t count) noexcept
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
const size_t runLength = _run->GetLength();
|
||||
if (count + _currentAttributeIndex < runLength)
|
||||
{
|
||||
_currentAttributeIndex += count;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
count -= runLength - _currentAttributeIndex;
|
||||
++_run;
|
||||
_currentAttributeIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - decrements the index the iterator points to
|
||||
// Arguments:
|
||||
// - count - the amount to decrement by
|
||||
void AttrRowIterator::_decrement(size_t count) noexcept
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
// If there's still space within this color attribute to move left, do so.
|
||||
if (count <= _currentAttributeIndex)
|
||||
{
|
||||
_currentAttributeIndex -= count;
|
||||
return;
|
||||
}
|
||||
// If there's not space, move to the previous attribute run
|
||||
// We'll walk through above on the if branch to move left further (if necessary)
|
||||
else
|
||||
{
|
||||
// make sure we don't go out of bounds
|
||||
if (_run == _pAttrRow->_list.cbegin())
|
||||
{
|
||||
_exceeded = true;
|
||||
return;
|
||||
}
|
||||
count -= _currentAttributeIndex + 1;
|
||||
--_run;
|
||||
_currentAttributeIndex = _run->GetLength() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - sets fields on the iterator to describe the end() state of the ATTR_ROW
|
||||
void AttrRowIterator::_setToEnd() noexcept
|
||||
{
|
||||
_run = _pAttrRow->_list.cend();
|
||||
_currentAttributeIndex = 0;
|
||||
}
|
||||
80
src/buffer/out/AttrRowIterator.hpp
Normal file
80
src/buffer/out/AttrRowIterator.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- AttrRowIterator.hpp
|
||||
|
||||
Abstract:
|
||||
- iterator for ATTR_ROW to walk the TextAttributes of the run
|
||||
- read only iterator
|
||||
|
||||
Author(s):
|
||||
- Austin Diviness (AustDi) 04-Jun-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TextAttribute.hpp"
|
||||
#include "TextAttributeRun.hpp"
|
||||
|
||||
class ATTR_ROW;
|
||||
|
||||
class AttrRowIterator final
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using value_type = TextAttribute;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = TextAttribute*;
|
||||
using reference = TextAttribute&;
|
||||
|
||||
static AttrRowIterator CreateEndIterator(const ATTR_ROW* const attrRow) noexcept;
|
||||
|
||||
AttrRowIterator(const ATTR_ROW* const attrRow) noexcept;
|
||||
|
||||
operator bool() const noexcept;
|
||||
|
||||
bool operator==(const AttrRowIterator& it) const noexcept;
|
||||
bool operator!=(const AttrRowIterator& it) const noexcept;
|
||||
|
||||
AttrRowIterator& operator++() noexcept
|
||||
{
|
||||
_increment(1);
|
||||
return *this;
|
||||
}
|
||||
AttrRowIterator operator++(int) noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
_increment(1);
|
||||
return copy;
|
||||
}
|
||||
|
||||
AttrRowIterator& operator+=(const ptrdiff_t& movement);
|
||||
AttrRowIterator& operator-=(const ptrdiff_t& movement);
|
||||
|
||||
AttrRowIterator& operator--() noexcept
|
||||
{
|
||||
_decrement(1);
|
||||
return *this;
|
||||
}
|
||||
AttrRowIterator operator--(int) noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
_decrement(1);
|
||||
return copy;
|
||||
}
|
||||
|
||||
const TextAttribute* operator->() const;
|
||||
const TextAttribute& operator*() const;
|
||||
|
||||
private:
|
||||
boost::container::small_vector_base<TextAttributeRun>::const_iterator _run;
|
||||
const ATTR_ROW* _pAttrRow;
|
||||
size_t _currentAttributeIndex; // index of TextAttribute within the current TextAttributeRun
|
||||
bool _exceeded;
|
||||
|
||||
void _increment(size_t count) noexcept;
|
||||
void _decrement(size_t count) noexcept;
|
||||
void _setToEnd() noexcept;
|
||||
};
|
||||
@@ -16,7 +16,7 @@
|
||||
// - pParent - the text buffer that this row belongs to
|
||||
// Return Value:
|
||||
// - constructed object
|
||||
ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) :
|
||||
ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) noexcept :
|
||||
_id{ rowId },
|
||||
_rowWidth{ rowWidth },
|
||||
_charRow{ rowWidth, this },
|
||||
@@ -107,91 +107,103 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, index >= _charRow.size());
|
||||
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _charRow.size());
|
||||
size_t currentIndex = index;
|
||||
|
||||
// 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);
|
||||
|
||||
auto currentColor = it->TextAttr();
|
||||
uint16_t colorUses = 0;
|
||||
uint16_t colorStarts = gsl::narrow_cast<uint16_t>(index);
|
||||
uint16_t currentIndex = colorStarts;
|
||||
|
||||
while (it && currentIndex <= finalColumnInRow)
|
||||
if (it)
|
||||
{
|
||||
// Fill the color if the behavior isn't set to keeping the current color.
|
||||
if (it->TextAttrBehavior() != TextAttributeBehavior::Current)
|
||||
// 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)
|
||||
{
|
||||
// If the color of this cell is the same as the run we're currently on,
|
||||
// just increment the counter.
|
||||
if (currentColor == it->TextAttr())
|
||||
// Fill the color if the behavior isn't set to keeping the current color.
|
||||
if (it->TextAttrBehavior() != TextAttributeBehavior::Current)
|
||||
{
|
||||
++colorUses;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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())
|
||||
{
|
||||
_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);
|
||||
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.
|
||||
SetWrapForced(*wrap);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, commit this color into the run and save off the new one.
|
||||
// Now commit the new color runs into the attr row.
|
||||
_attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
currentColor = it->TextAttr();
|
||||
colorUses = 1;
|
||||
colorStarts = 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)
|
||||
{
|
||||
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);
|
||||
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.
|
||||
SetWrapForced(*wrap);
|
||||
}
|
||||
// Move to the next cell for the next time through the loop.
|
||||
++currentIndex;
|
||||
}
|
||||
else
|
||||
|
||||
// Now commit the final color into the attr row
|
||||
if (colorUses)
|
||||
{
|
||||
++it;
|
||||
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;
|
||||
}
|
||||
|
||||
// Now commit the final color into the attr row
|
||||
if (colorUses)
|
||||
{
|
||||
_attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
}
|
||||
|
||||
return it;
|
||||
|
||||
@@ -32,7 +32,8 @@ class TextBuffer;
|
||||
class ROW final
|
||||
{
|
||||
public:
|
||||
ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent);
|
||||
ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent)
|
||||
noexcept;
|
||||
|
||||
size_t size() const noexcept { return _rowWidth; }
|
||||
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
#include "TextAttribute.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
// Keeping TextColor compact helps us keeping TextAttribute compact,
|
||||
// which in turn ensures that our buffer memory usage is low.
|
||||
static_assert(sizeof(TextAttribute) == 14);
|
||||
static_assert(alignof(TextAttribute) == 2);
|
||||
// Ensure that we can memcpy() and memmove() the struct for performance.
|
||||
static_assert(std::is_trivially_copyable_v<TextAttribute>);
|
||||
|
||||
BYTE TextAttribute::s_legacyDefaultForeground = 7;
|
||||
BYTE TextAttribute::s_legacyDefaultBackground = 0;
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ Revision History:
|
||||
#include "WexTestClass.h"
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
class TextAttribute final
|
||||
{
|
||||
public:
|
||||
@@ -174,11 +176,12 @@ private:
|
||||
static BYTE s_legacyDefaultForeground;
|
||||
static BYTE s_legacyDefaultBackground;
|
||||
|
||||
uint16_t _wAttrLegacy; // sizeof: 2, alignof: 2
|
||||
uint16_t _hyperlinkId; // sizeof: 2, alignof: 2
|
||||
TextColor _foreground; // sizeof: 4, alignof: 1
|
||||
TextColor _background; // sizeof: 4, alignof: 1
|
||||
ExtendedAttributes _extendedAttrs; // sizeof: 1, alignof: 1
|
||||
WORD _wAttrLegacy;
|
||||
TextColor _foreground;
|
||||
TextColor _background;
|
||||
ExtendedAttributes _extendedAttrs;
|
||||
|
||||
uint16_t _hyperlinkId;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TextBufferTests;
|
||||
@@ -188,6 +191,13 @@ private:
|
||||
#endif
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
// 2 for _wAttrLegacy
|
||||
// 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");
|
||||
|
||||
enum class TextAttributeBehavior
|
||||
{
|
||||
Stored, // use contained text attribute
|
||||
|
||||
50
src/buffer/out/TextAttributeRun.hpp
Normal file
50
src/buffer/out/TextAttributeRun.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- TextAttributeRun.hpp
|
||||
|
||||
Abstract:
|
||||
- contains data structure for run-length-encoding of text attribute data
|
||||
|
||||
Author(s):
|
||||
- Michael Niksa (miniksa) 10-Apr-2014
|
||||
- Paul Campbell (paulcam) 10-Apr-2014
|
||||
|
||||
Revision History:
|
||||
- From components of output.h/.c
|
||||
by Therese Stowell (ThereseS) 1990-1991
|
||||
- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017)
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TextAttribute.hpp"
|
||||
|
||||
class TextAttributeRun final
|
||||
{
|
||||
public:
|
||||
TextAttributeRun() = default;
|
||||
TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept :
|
||||
_cchLength(gsl::narrow<unsigned int>(cchLength))
|
||||
{
|
||||
SetAttributes(attr);
|
||||
}
|
||||
|
||||
size_t GetLength() const noexcept { return _cchLength; }
|
||||
void SetLength(const size_t cchLength) noexcept { _cchLength = gsl::narrow<unsigned int>(cchLength); }
|
||||
void IncrementLength() noexcept { _cchLength++; }
|
||||
void DecrementLength() noexcept { _cchLength--; }
|
||||
|
||||
const TextAttribute& GetAttributes() const noexcept { return _attributes; }
|
||||
void SetAttributes(const TextAttribute textAttribute) noexcept { _attributes = textAttribute; }
|
||||
|
||||
private:
|
||||
unsigned int _cchLength{ 0 };
|
||||
TextAttribute _attributes{ 0 };
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class AttrRowTests;
|
||||
#endif
|
||||
};
|
||||
@@ -37,6 +37,8 @@ Revision History:
|
||||
#include "WexTestClass.h"
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
enum class ColorType : BYTE
|
||||
{
|
||||
IsIndex256 = 0x0,
|
||||
@@ -100,13 +102,13 @@ public:
|
||||
COLORREF GetRGB() const noexcept;
|
||||
|
||||
private:
|
||||
ColorType _meta : 2;
|
||||
union
|
||||
{
|
||||
BYTE _red, _index;
|
||||
};
|
||||
BYTE _green;
|
||||
BYTE _blue;
|
||||
ColorType _meta;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TextBufferTests;
|
||||
@@ -115,6 +117,8 @@ private:
|
||||
#endif
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
bool constexpr operator==(const TextColor& a, const TextColor& b) noexcept
|
||||
{
|
||||
return a._meta == b._meta &&
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\AttrRow.cpp" />
|
||||
<ClCompile Include="..\AttrRowIterator.cpp" />
|
||||
<ClCompile Include="..\cursor.cpp" />
|
||||
<ClCompile Include="..\OutputCell.cpp" />
|
||||
<ClCompile Include="..\OutputCellIterator.cpp" />
|
||||
@@ -33,6 +34,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\AttrRow.hpp" />
|
||||
<ClInclude Include="..\AttrRowIterator.hpp" />
|
||||
<ClInclude Include="..\cursor.h" />
|
||||
<ClInclude Include="..\DbcsAttribute.hpp" />
|
||||
<ClInclude Include="..\ICharRow.hpp" />
|
||||
@@ -45,6 +47,7 @@
|
||||
<ClInclude Include="..\search.h" />
|
||||
<ClInclude Include="..\TextColor.h" />
|
||||
<ClInclude Include="..\TextAttribute.h" />
|
||||
<ClInclude Include="..\TextAttributeRun.h" />
|
||||
<ClInclude Include="..\textBuffer.hpp" />
|
||||
<ClInclude Include="..\textBufferCellIterator.hpp" />
|
||||
<ClInclude Include="..\textBufferTextIterator.hpp" />
|
||||
|
||||
@@ -850,7 +850,7 @@ bool TextBuffer::IsDoubleWidthLine(const size_t row) const
|
||||
SHORT TextBuffer::GetLineWidth(const size_t row) const
|
||||
{
|
||||
// Use shift right to quickly divide the width by 2 for double width lines.
|
||||
const SHORT scale = IsDoubleWidthLine(row) ? 1 : 0;
|
||||
const auto scale = IsDoubleWidthLine(row) ? 1 : 0;
|
||||
return GetSize().Width() >> scale;
|
||||
}
|
||||
|
||||
@@ -863,14 +863,14 @@ COORD TextBuffer::ClampPositionWithinLine(const COORD position) const
|
||||
COORD TextBuffer::ScreenToBufferPosition(const COORD position) const
|
||||
{
|
||||
// Use shift right to quickly divide the X pos by 2 for double width lines.
|
||||
const SHORT scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
return { position.X >> scale, position.Y };
|
||||
}
|
||||
|
||||
COORD TextBuffer::BufferToScreenPosition(const COORD position) const
|
||||
{
|
||||
// Use shift left to quickly multiply the X pos by 2 for double width lines.
|
||||
const SHORT scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
return { position.X << scale, position.Y };
|
||||
}
|
||||
|
||||
@@ -2477,14 +2477,6 @@ const size_t TextBuffer::AddPatternRecognizer(const std::wstring_view regexStrin
|
||||
return _currentPatternId;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Clears the patterns we know of and resets the pattern ID counter
|
||||
void TextBuffer::ClearPatternRecognizers() noexcept
|
||||
{
|
||||
_idsAndPatterns.clear();
|
||||
_currentPatternId = 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Copies the patterns the other buffer knows about into this one
|
||||
// Arguments:
|
||||
|
||||
@@ -196,7 +196,6 @@ public:
|
||||
std::optional<std::reference_wrapper<PositionInformation>> positionInfo);
|
||||
|
||||
const size_t AddPatternRecognizer(const std::wstring_view regexString);
|
||||
void ClearPatternRecognizers() noexcept;
|
||||
void CopyPatterns(const TextBuffer& OtherBuffer);
|
||||
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const size_t firstRow, const size_t lastRow) const;
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ Author(s):
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AttrRowIterator.hpp"
|
||||
#include "CharRow.hpp"
|
||||
#include "AttrRow.hpp"
|
||||
#include "OutputCellView.hpp"
|
||||
#include "../../types/inc/viewport.hpp"
|
||||
|
||||
@@ -55,7 +55,7 @@ protected:
|
||||
OutputCellView _view;
|
||||
|
||||
const ROW* _pRow;
|
||||
ATTR_ROW::const_iterator _attrIter;
|
||||
AttrRowIterator _attrIter;
|
||||
const TextBuffer& _buffer;
|
||||
const Microsoft::Console::Types::Viewport _bounds;
|
||||
bool _exceeded;
|
||||
|
||||
731
src/buffer/out/ut_textbuffer/AttrRowTests.cpp
Normal file
731
src/buffer/out/ut_textbuffer/AttrRowTests.cpp
Normal file
@@ -0,0 +1,731 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "../textBuffer.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
namespace WEX
|
||||
{
|
||||
namespace TestExecution
|
||||
{
|
||||
template<>
|
||||
class VerifyOutputTraits<TextAttributeRun>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const TextAttributeRun& tar)
|
||||
{
|
||||
return WEX::Common::NoThrowString().Format(
|
||||
L"Length:%d, attr:%s",
|
||||
tar.GetLength(),
|
||||
VerifyOutputTraits<TextAttribute>::ToString(tar.GetAttributes()).GetBuffer());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class VerifyCompareTraits<TextAttributeRun, TextAttributeRun>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const TextAttributeRun& expected, const TextAttributeRun& actual)
|
||||
{
|
||||
return expected.GetAttributes() == actual.GetAttributes() &&
|
||||
expected.GetLength() == actual.GetLength();
|
||||
}
|
||||
|
||||
static bool AreSame(const TextAttributeRun& expected, const TextAttributeRun& actual)
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const TextAttributeRun&, const TextAttributeRun&) = delete;
|
||||
|
||||
static bool IsGreaterThan(const TextAttributeRun&, const TextAttributeRun&) = delete;
|
||||
|
||||
static bool IsNull(const TextAttributeRun& object)
|
||||
{
|
||||
return object.GetAttributes().IsLegacy() && object.GetAttributes().GetLegacyAttributes() == 0 &&
|
||||
object.GetLength() == 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class AttrRowTests
|
||||
{
|
||||
ATTR_ROW* pSingle;
|
||||
ATTR_ROW* pChain;
|
||||
|
||||
short _sDefaultLength = 80;
|
||||
short _sDefaultChainLength = 6;
|
||||
|
||||
short sChainSegLength;
|
||||
short sChainLeftover;
|
||||
short sChainSegmentsNeeded;
|
||||
|
||||
WORD __wDefaultAttr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
|
||||
WORD __wDefaultChainAttr = BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY;
|
||||
TextAttribute _DefaultAttr = TextAttribute(__wDefaultAttr);
|
||||
TextAttribute _DefaultChainAttr = TextAttribute(__wDefaultChainAttr);
|
||||
|
||||
TEST_CLASS(AttrRowTests);
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
pSingle = new ATTR_ROW(_sDefaultLength, _DefaultAttr);
|
||||
|
||||
// Segment length is the expected length divided by the row length
|
||||
// E.g. row of 80, 4 segments, 20 segment length each
|
||||
sChainSegLength = _sDefaultLength / _sDefaultChainLength;
|
||||
|
||||
// Leftover is spaces that don't fit evenly
|
||||
// E.g. row of 81, 4 segments, 1 leftover length segment
|
||||
sChainLeftover = _sDefaultLength % _sDefaultChainLength;
|
||||
|
||||
// Start with the number of segments we expect
|
||||
sChainSegmentsNeeded = _sDefaultChainLength;
|
||||
|
||||
// If we had a remainder, add one more segment
|
||||
if (sChainLeftover)
|
||||
{
|
||||
sChainSegmentsNeeded++;
|
||||
}
|
||||
|
||||
// Create the chain
|
||||
pChain = new ATTR_ROW(_sDefaultLength, _DefaultAttr);
|
||||
pChain->_list.resize(sChainSegmentsNeeded);
|
||||
|
||||
// Attach all chain segments that are even multiples of the row length
|
||||
for (short iChain = 0; iChain < _sDefaultChainLength; iChain++)
|
||||
{
|
||||
TextAttributeRun* pRun = &pChain->_list[iChain];
|
||||
|
||||
pRun->SetAttributes(TextAttribute{ gsl::narrow_cast<WORD>(iChain) }); // Just use the chain position as the value
|
||||
pRun->SetLength(sChainSegLength);
|
||||
}
|
||||
|
||||
if (sChainLeftover > 0)
|
||||
{
|
||||
// If we had a leftover, then this chain is one longer than we expected (the default length)
|
||||
// So use it as the index (because indices start at 0)
|
||||
TextAttributeRun* pRun = &pChain->_list[_sDefaultChainLength];
|
||||
|
||||
pRun->SetAttributes(_DefaultChainAttr);
|
||||
pRun->SetLength(sChainLeftover);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
delete pSingle;
|
||||
|
||||
delete pChain;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(TestInitialize)
|
||||
{
|
||||
// Properties needed for test
|
||||
const WORD wAttr = FOREGROUND_RED | BACKGROUND_BLUE;
|
||||
TextAttribute attr = TextAttribute(wAttr);
|
||||
// Cases to test
|
||||
ATTR_ROW* pTestItems[]{ pSingle, pChain };
|
||||
|
||||
// Loop cases
|
||||
for (UINT iIndex = 0; iIndex < ARRAYSIZE(pTestItems); iIndex++)
|
||||
{
|
||||
ATTR_ROW* pUnderTest = pTestItems[iIndex];
|
||||
|
||||
pUnderTest->Reset(attr);
|
||||
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list.size(), 1u);
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list[0].GetAttributes(), attr);
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list[0].GetLength(), (unsigned int)_sDefaultLength);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Packs an array of words representing attributes into the more compact storage form used by the row.
|
||||
// Arguments:
|
||||
// - rgAttrs - Array of words representing the attribute associated with each character position in the row.
|
||||
// - cRowLength - Length of preceding array.
|
||||
// - outAttrRun - reference to unique_ptr that will contain packed attr run on success.
|
||||
// Return Value:
|
||||
// - Success if success. Buffer too small if row length is incorrect.
|
||||
HRESULT PackAttrs(_In_reads_(cRowLength) const TextAttribute* const rgAttrs,
|
||||
const size_t cRowLength,
|
||||
_Inout_ std::unique_ptr<TextAttributeRun[]>& outAttrRun,
|
||||
_Out_ size_t* const cOutAttrRun)
|
||||
{
|
||||
RETURN_HR_IF(E_NOT_SUFFICIENT_BUFFER, cRowLength == 0);
|
||||
|
||||
// first count up the deltas in the array
|
||||
size_t cDeltas = 1;
|
||||
|
||||
const TextAttribute* pPrevAttr = &rgAttrs[0];
|
||||
|
||||
for (size_t i = 1; i < cRowLength; i++)
|
||||
{
|
||||
const TextAttribute* pCurAttr = &rgAttrs[i];
|
||||
|
||||
if (*pCurAttr != *pPrevAttr)
|
||||
{
|
||||
cDeltas++;
|
||||
}
|
||||
|
||||
pPrevAttr = pCurAttr;
|
||||
}
|
||||
|
||||
// This whole situation was too complicated with a one off holder for one row run
|
||||
// new method:
|
||||
// delete the old buffer
|
||||
// make a new buffer, one run + one run for each change
|
||||
// set the values for each run one run index at a time
|
||||
|
||||
std::unique_ptr<TextAttributeRun[]> attrRun = std::make_unique<TextAttributeRun[]>(cDeltas);
|
||||
RETURN_HR_IF_NULL(E_OUTOFMEMORY, attrRun);
|
||||
|
||||
TextAttributeRun* pCurrentRun = attrRun.get();
|
||||
pCurrentRun->SetAttributes(rgAttrs[0]);
|
||||
pCurrentRun->SetLength(1);
|
||||
for (size_t i = 1; i < cRowLength; i++)
|
||||
{
|
||||
if (pCurrentRun->GetAttributes() == rgAttrs[i])
|
||||
{
|
||||
pCurrentRun->SetLength(pCurrentRun->GetLength() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pCurrentRun++;
|
||||
pCurrentRun->SetAttributes(rgAttrs[i]);
|
||||
pCurrentRun->SetLength(1);
|
||||
}
|
||||
}
|
||||
attrRun.swap(outAttrRun);
|
||||
*cOutAttrRun = cDeltas;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
NoThrowString LogRunElement(_In_ TextAttributeRun& run)
|
||||
{
|
||||
return NoThrowString().Format(L"%wc%d", run.GetAttributes().GetLegacyAttributes(), run.GetLength());
|
||||
}
|
||||
|
||||
void LogChain(_In_ PCWSTR pwszPrefix,
|
||||
boost::container::small_vector_base<TextAttributeRun>& chain)
|
||||
{
|
||||
NoThrowString str(pwszPrefix);
|
||||
|
||||
if (chain.size() > 0)
|
||||
{
|
||||
str.Append(LogRunElement(chain[0]));
|
||||
|
||||
for (size_t i = 1; i < chain.size(); i++)
|
||||
{
|
||||
str.AppendFormat(L"->%s", (const wchar_t*)(LogRunElement(chain[i])));
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(str);
|
||||
}
|
||||
|
||||
void LogChain(_In_ PCWSTR pwszPrefix,
|
||||
std::vector<TextAttributeRun>& chain)
|
||||
{
|
||||
NoThrowString str(pwszPrefix);
|
||||
|
||||
if (chain.size() > 0)
|
||||
{
|
||||
str.Append(LogRunElement(chain[0]));
|
||||
|
||||
for (size_t i = 1; i < chain.size(); i++)
|
||||
{
|
||||
str.AppendFormat(L"->%s", (const wchar_t*)(LogRunElement(chain[i])));
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(str);
|
||||
}
|
||||
|
||||
void DoTestInsertAttrRuns(UINT& uiStartPos, WORD& ch1, UINT& uiChar1Length, WORD& ch2, UINT& uiChar2Length)
|
||||
{
|
||||
Log::Comment(String().Format(L"StartPos: %d, Char1: %wc, Char1Length: %d, Char2: %wc, Char2Length: %d",
|
||||
uiStartPos,
|
||||
ch1,
|
||||
uiChar1Length,
|
||||
ch2,
|
||||
uiChar2Length));
|
||||
|
||||
bool const fUseStr2 = (ch2 != L'0');
|
||||
|
||||
// Set up our "original row" that we are going to try to insert into.
|
||||
// This will represent a 10 column run of R3->B5->G2 that we will use for all tests.
|
||||
ATTR_ROW originalRow{ static_cast<UINT>(_sDefaultLength), _DefaultAttr };
|
||||
originalRow._list.resize(3);
|
||||
originalRow._cchRowWidth = 10;
|
||||
originalRow._list[0].SetAttributes(TextAttribute{ 'R' });
|
||||
originalRow._list[0].SetLength(3);
|
||||
originalRow._list[1].SetAttributes(TextAttribute{ 'B' });
|
||||
originalRow._list[1].SetLength(5);
|
||||
originalRow._list[2].SetAttributes(TextAttribute{ 'G' });
|
||||
originalRow._list[2].SetLength(2);
|
||||
LogChain(L"Original: ", originalRow._list);
|
||||
|
||||
// Set up our "insertion run"
|
||||
size_t cInsertRow = 1;
|
||||
if (fUseStr2)
|
||||
{
|
||||
cInsertRow++;
|
||||
}
|
||||
|
||||
std::vector<TextAttributeRun> insertRow;
|
||||
insertRow.resize(cInsertRow);
|
||||
insertRow[0].SetAttributes(TextAttribute{ ch1 });
|
||||
insertRow[0].SetLength(uiChar1Length);
|
||||
if (fUseStr2)
|
||||
{
|
||||
insertRow[1].SetAttributes(TextAttribute{ ch2 });
|
||||
insertRow[1].SetLength(uiChar2Length);
|
||||
}
|
||||
|
||||
LogChain(L"Insert: ", insertRow);
|
||||
Log::Comment(NoThrowString().Format(L"At Index: %d", uiStartPos));
|
||||
|
||||
UINT uiTotalLength = uiChar1Length;
|
||||
if (fUseStr2)
|
||||
{
|
||||
uiTotalLength += uiChar2Length;
|
||||
}
|
||||
|
||||
VERIFY_IS_TRUE((uiStartPos + uiTotalLength) >= 1); // assert we won't underflow.
|
||||
UINT const uiEndPos = uiStartPos + uiTotalLength - 1;
|
||||
|
||||
// Calculate our expected final/result run by unpacking original, laying our insertion on it at the index
|
||||
// then using our pack function to repack it.
|
||||
// This method is easy to understand and very reliable, but its performance is bad.
|
||||
// The InsertAttrRuns method we test against below is hard to understand but very high performance in production.
|
||||
|
||||
// - 1. Unpack
|
||||
std::vector<TextAttribute> unpackedOriginal = { originalRow.begin(), originalRow.end() };
|
||||
|
||||
// - 2. Overlay insertion
|
||||
UINT uiInsertedCount = 0;
|
||||
UINT uiInsertIndex = 0;
|
||||
|
||||
// --- Walk through the unpacked run from start to end....
|
||||
for (UINT uiUnpackedIndex = uiStartPos; uiUnpackedIndex <= uiEndPos; uiUnpackedIndex++)
|
||||
{
|
||||
// Pull the item from the insert run to analyze.
|
||||
TextAttributeRun run = insertRow[uiInsertIndex];
|
||||
|
||||
// Copy the attribute from the run into the unpacked array
|
||||
unpackedOriginal[uiUnpackedIndex] = run.GetAttributes();
|
||||
|
||||
// Increment how many times we've copied this particular portion of the run
|
||||
uiInsertedCount++;
|
||||
|
||||
// If we've now inserted enough of them to match the length, advance the insert index and reset the counter.
|
||||
if (uiInsertedCount >= run.GetLength())
|
||||
{
|
||||
uiInsertIndex++;
|
||||
uiInsertedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// - 3. Pack.
|
||||
std::unique_ptr<TextAttributeRun[]> packedRun;
|
||||
size_t cPackedRun = 0;
|
||||
VERIFY_SUCCEEDED(PackAttrs(unpackedOriginal.data(), originalRow._cchRowWidth, packedRun, &cPackedRun));
|
||||
|
||||
// Now send parameters into InsertAttrRuns and get its opinion on the subject.
|
||||
VERIFY_SUCCEEDED(originalRow.InsertAttrRuns({ insertRow.data(), insertRow.size() }, uiStartPos, uiEndPos, (UINT)originalRow._cchRowWidth));
|
||||
|
||||
// Compare and ensure that the expected and actual match.
|
||||
VERIFY_ARE_EQUAL(cPackedRun, originalRow._list.size(), L"Ensure that number of array elements required for RLE are the same.");
|
||||
|
||||
std::vector<TextAttributeRun> packedRunExpected;
|
||||
std::copy_n(packedRun.get(), cPackedRun, std::back_inserter(packedRunExpected));
|
||||
|
||||
LogChain(L"Expected: ", packedRunExpected);
|
||||
LogChain(L"Actual: ", originalRow._list);
|
||||
|
||||
for (size_t testIndex = 0; testIndex < cPackedRun; testIndex++)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(packedRun[testIndex], originalRow._list[testIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestInsertAttrRunsSingle)
|
||||
{
|
||||
UINT const uiTestRunLength = 10;
|
||||
|
||||
UINT uiStartPos = 0;
|
||||
WORD ch1 = L'0';
|
||||
UINT uiChar1Length = 0;
|
||||
WORD ch2 = L'0';
|
||||
UINT uiChar2Length = 0;
|
||||
|
||||
Log::Comment(L"Test inserting a single item of a variable length into the run.");
|
||||
WORD rgch1Options[] = { L'X', L'R', L'G', L'B' };
|
||||
for (size_t iCh1Option = 0; iCh1Option < ARRAYSIZE(rgch1Options); iCh1Option++)
|
||||
{
|
||||
ch1 = rgch1Options[iCh1Option];
|
||||
for (UINT iCh1Length = 1; iCh1Length <= uiTestRunLength; iCh1Length++)
|
||||
{
|
||||
uiChar1Length = iCh1Length;
|
||||
|
||||
// We can't try to insert a run that's longer than would fit.
|
||||
// If the run is of length 10 and we're trying to insert a length of 10,
|
||||
// we can only insert at position 0.
|
||||
// For the run length of 10 and an insert length of 9, we can try positions 0 and 1.
|
||||
// And so on...
|
||||
UINT const uiMaxPos = uiTestRunLength - uiChar1Length;
|
||||
|
||||
for (UINT iStartPos = 0; iStartPos < uiMaxPos; iStartPos++)
|
||||
{
|
||||
uiStartPos = iStartPos;
|
||||
|
||||
DoTestInsertAttrRuns(uiStartPos, ch1, uiChar1Length, ch2, uiChar2Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestInsertAttrRunsMultiple)
|
||||
{
|
||||
UINT const uiTestRunLength = 10;
|
||||
|
||||
UINT uiStartPos = 0;
|
||||
WORD ch1 = L'0';
|
||||
UINT uiChar1Length = 0;
|
||||
WORD ch2 = L'0';
|
||||
UINT uiChar2Length = 0;
|
||||
|
||||
Log::Comment(L"Test inserting a multiple item run with each piece having variable length into the existing run.");
|
||||
WORD rgch1Options[] = { L'X', L'R', L'G', L'B' };
|
||||
for (size_t iCh1Option = 0; iCh1Option < ARRAYSIZE(rgch1Options); iCh1Option++)
|
||||
{
|
||||
ch1 = rgch1Options[iCh1Option];
|
||||
|
||||
UINT const uiMaxCh1Length = uiTestRunLength - 1; // leave at least 1 space for the second piece of the insert run.
|
||||
for (UINT iCh1Length = 1; iCh1Length <= uiMaxCh1Length; iCh1Length++)
|
||||
{
|
||||
uiChar1Length = iCh1Length;
|
||||
|
||||
WORD rgch2Options[] = { L'Y' };
|
||||
for (size_t iCh2Option = 0; iCh2Option < ARRAYSIZE(rgch2Options); iCh2Option++)
|
||||
{
|
||||
ch2 = rgch2Options[iCh2Option];
|
||||
|
||||
// When choosing the length of the second item, it can't be bigger than the remaining space in the run
|
||||
// when accounting for the length of the first piece chosen.
|
||||
// For example if the total run length is 10 and the first piece chosen was 8 long,
|
||||
// the second piece can only be 1 or 2 long.
|
||||
UINT const uiMaxCh2Length = uiTestRunLength - uiMaxCh1Length;
|
||||
for (UINT iCh2Length = 1; iCh2Length <= uiMaxCh2Length; iCh2Length++)
|
||||
{
|
||||
uiChar2Length = iCh2Length;
|
||||
|
||||
// We can't try to insert a run that's longer than would fit.
|
||||
// If the run is of length 10 and we're trying to insert a total length of 10,
|
||||
// we can only insert at position 0.
|
||||
// For the run length of 10 and an insert length of 9, we can try positions 0 and 1.
|
||||
// And so on...
|
||||
UINT const uiMaxPos = uiTestRunLength - (uiChar1Length + uiChar2Length);
|
||||
|
||||
for (UINT iStartPos = 0; iStartPos <= uiMaxPos; iStartPos++)
|
||||
{
|
||||
uiStartPos = iStartPos;
|
||||
|
||||
DoTestInsertAttrRuns(uiStartPos, ch1, uiChar1Length, ch2, uiChar2Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestUnpackAttrs)
|
||||
{
|
||||
Log::Comment(L"Checking unpack of a single color for the entire length");
|
||||
{
|
||||
const std::vector<TextAttribute> attrs{ pSingle->begin(), pSingle->end() };
|
||||
|
||||
for (auto& attr : attrs)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(attr, _DefaultAttr);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"Checking unpack of the multiple color chain");
|
||||
|
||||
const std::vector<TextAttribute> attrs{ pChain->begin(), pChain->end() };
|
||||
|
||||
short cChainRun = 0; // how long we've been looking at the current piece of the chain
|
||||
short iChainSegIndex = 0; // which piece of the chain we should be on right now
|
||||
|
||||
for (auto& attr : attrs)
|
||||
{
|
||||
// by default the chain was assembled above to have the chain segment index be the attribute
|
||||
TextAttribute MatchingAttr = TextAttribute(iChainSegIndex);
|
||||
|
||||
// However, if the index is greater than the expected chain length, a remainder piece was made with a default attribute
|
||||
if (iChainSegIndex >= _sDefaultChainLength)
|
||||
{
|
||||
MatchingAttr = _DefaultChainAttr;
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(attr, MatchingAttr);
|
||||
|
||||
// Add to the chain run
|
||||
cChainRun++;
|
||||
|
||||
// If the chain run is greater than the length the segments were specified to be
|
||||
if (cChainRun >= sChainSegLength)
|
||||
{
|
||||
// reset to 0
|
||||
cChainRun = 0;
|
||||
|
||||
// move to the next chain segment down the line
|
||||
iChainSegIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestReverseIteratorWalkFromMiddle)
|
||||
{
|
||||
// GH #3409, walking backwards through color range runs out of bounds
|
||||
// We're going to create an attribute row with assorted colors and varying lengths
|
||||
// just like the row of text on the Ubuntu prompt line that triggered this bug being found.
|
||||
// Then we're going to walk backwards through the iterator like a selection-expand-to-left
|
||||
// operation and ensure we don't run off the bounds.
|
||||
|
||||
// walk the chain, from index, stepSize at a time
|
||||
// ensure we don't crash
|
||||
auto testWalk = [](ATTR_ROW* chain, size_t index, int stepSize) {
|
||||
// move to starting index
|
||||
auto iter = chain->cbegin();
|
||||
iter += index;
|
||||
|
||||
// Now walk backwards in a loop until 0.
|
||||
while (iter)
|
||||
{
|
||||
iter -= stepSize;
|
||||
}
|
||||
|
||||
Log::Comment(L"We made it through without crashing!");
|
||||
};
|
||||
|
||||
// take one step of size stepSize on the chain
|
||||
// index is where we start from
|
||||
// expectedAttribute is what we expect to read here
|
||||
auto verifyStep = [](ATTR_ROW* chain, size_t index, int stepSize, TextAttribute expectedAttribute) {
|
||||
// move to starting index
|
||||
auto iter = chain->cbegin();
|
||||
iter += index;
|
||||
|
||||
// Now step backwards
|
||||
iter -= stepSize;
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedAttribute, *iter);
|
||||
};
|
||||
|
||||
Log::Comment(L"Reverse iterate through ubuntu prompt");
|
||||
{
|
||||
// Create attr row representing a buffer that's 121 wide.
|
||||
auto chain = std::make_unique<ATTR_ROW>(121, _DefaultAttr);
|
||||
|
||||
// The repro case had 4 chain segments.
|
||||
chain->_list.resize(4);
|
||||
|
||||
// The color 10 went for the first 18.
|
||||
chain->_list[0].SetAttributes(TextAttribute(0xA));
|
||||
chain->_list[0].SetLength(18);
|
||||
|
||||
// Default color for the next 1
|
||||
chain->_list[1].SetAttributes(TextAttribute());
|
||||
chain->_list[1].SetLength(1);
|
||||
|
||||
// Color 12 for the next 29
|
||||
chain->_list[2].SetAttributes(TextAttribute(0xC));
|
||||
chain->_list[2].SetLength(29);
|
||||
|
||||
// Then default color to end the run
|
||||
chain->_list[3].SetAttributes(TextAttribute());
|
||||
chain->_list[3].SetLength(73);
|
||||
|
||||
// The sum of the lengths should be 121.
|
||||
VERIFY_ARE_EQUAL(chain->_cchRowWidth, chain->_list[0]._cchLength + chain->_list[1]._cchLength + chain->_list[2]._cchLength + chain->_list[3]._cchLength);
|
||||
|
||||
auto index = chain->_list[0].GetLength();
|
||||
auto stepSize = 1;
|
||||
testWalk(chain.get(), index, stepSize);
|
||||
}
|
||||
|
||||
Log::Comment(L"Reverse iterate across a text run in the chain");
|
||||
{
|
||||
// Create attr row representing a buffer that's 3 wide.
|
||||
auto chain = std::make_unique<ATTR_ROW>(3, _DefaultAttr);
|
||||
|
||||
// The repro case had 3 chain segments.
|
||||
chain->_list.resize(3);
|
||||
|
||||
// The color 10 went for the first 1.
|
||||
chain->_list[0].SetAttributes(TextAttribute(0xA));
|
||||
chain->_list[0].SetLength(1);
|
||||
|
||||
// The color 11 for the next 1
|
||||
chain->_list[1].SetAttributes(TextAttribute(0xB));
|
||||
chain->_list[1].SetLength(1);
|
||||
|
||||
// Color 12 for the next 1
|
||||
chain->_list[2].SetAttributes(TextAttribute(0xC));
|
||||
chain->_list[2].SetLength(1);
|
||||
|
||||
// The sum of the lengths should be 3.
|
||||
VERIFY_ARE_EQUAL(chain->_cchRowWidth, chain->_list[0]._cchLength + chain->_list[1]._cchLength + chain->_list[2]._cchLength);
|
||||
|
||||
// on 'ABC', step from B to A
|
||||
auto index = 1;
|
||||
auto stepSize = 1;
|
||||
verifyStep(chain.get(), index, stepSize, TextAttribute(0xA));
|
||||
}
|
||||
|
||||
Log::Comment(L"Reverse iterate across two text runs in the chain");
|
||||
{
|
||||
// Create attr row representing a buffer that's 3 wide.
|
||||
auto chain = std::make_unique<ATTR_ROW>(3, _DefaultAttr);
|
||||
|
||||
// The repro case had 3 chain segments.
|
||||
chain->_list.resize(3);
|
||||
|
||||
// The color 10 went for the first 1.
|
||||
chain->_list[0].SetAttributes(TextAttribute(0xA));
|
||||
chain->_list[0].SetLength(1);
|
||||
|
||||
// The color 11 for the next 1
|
||||
chain->_list[1].SetAttributes(TextAttribute(0xB));
|
||||
chain->_list[1].SetLength(1);
|
||||
|
||||
// Color 12 for the next 1
|
||||
chain->_list[2].SetAttributes(TextAttribute(0xC));
|
||||
chain->_list[2].SetLength(1);
|
||||
|
||||
// The sum of the lengths should be 3.
|
||||
VERIFY_ARE_EQUAL(chain->_cchRowWidth, chain->_list[0]._cchLength + chain->_list[1]._cchLength + chain->_list[2]._cchLength);
|
||||
|
||||
// on 'ABC', step from C to A
|
||||
auto index = 2;
|
||||
auto stepSize = 2;
|
||||
verifyStep(chain.get(), index, stepSize, TextAttribute(0xA));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestSetAttrToEnd)
|
||||
{
|
||||
const WORD wTestAttr = FOREGROUND_BLUE | BACKGROUND_GREEN;
|
||||
TextAttribute TestAttr = TextAttribute(wTestAttr);
|
||||
|
||||
Log::Comment(L"FIRST: Set index to > 0 to test making/modifying chains");
|
||||
const short iTestIndex = 50;
|
||||
VERIFY_IS_TRUE(iTestIndex >= 0 && iTestIndex < _sDefaultLength);
|
||||
|
||||
Log::Comment(L"SetAttrToEnd for single color applied to whole string.");
|
||||
pSingle->SetAttrToEnd(iTestIndex, TestAttr);
|
||||
|
||||
// Was 1 (single), should now have 2 segments
|
||||
VERIFY_ARE_EQUAL(pSingle->_list.size(), 2u);
|
||||
|
||||
VERIFY_ARE_EQUAL(pSingle->_list[0].GetAttributes(), _DefaultAttr);
|
||||
VERIFY_ARE_EQUAL(pSingle->_list[0].GetLength(), (unsigned int)(_sDefaultLength - (_sDefaultLength - iTestIndex)));
|
||||
|
||||
VERIFY_ARE_EQUAL(pSingle->_list[1].GetAttributes(), TestAttr);
|
||||
VERIFY_ARE_EQUAL(pSingle->_list[1].GetLength(), (unsigned int)(_sDefaultLength - iTestIndex));
|
||||
|
||||
Log::Comment(L"SetAttrToEnd for existing chain of multiple colors.");
|
||||
pChain->SetAttrToEnd(iTestIndex, TestAttr);
|
||||
|
||||
// From 7 segments down to 5.
|
||||
VERIFY_ARE_EQUAL(pChain->_list.size(), 5u);
|
||||
|
||||
// Verify chain colors and lengths
|
||||
VERIFY_ARE_EQUAL(TextAttribute(0), pChain->_list[0].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[0].GetLength(), (unsigned int)13);
|
||||
|
||||
VERIFY_ARE_EQUAL(TextAttribute(1), pChain->_list[1].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[1].GetLength(), (unsigned int)13);
|
||||
|
||||
VERIFY_ARE_EQUAL(TextAttribute(2), pChain->_list[2].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[2].GetLength(), (unsigned int)13);
|
||||
|
||||
VERIFY_ARE_EQUAL(TextAttribute(3), pChain->_list[3].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[3].GetLength(), (unsigned int)11);
|
||||
|
||||
VERIFY_ARE_EQUAL(TestAttr, pChain->_list[4].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[4].GetLength(), (unsigned int)30);
|
||||
|
||||
Log::Comment(L"SECOND: Set index to 0 to test replacing anything with a single");
|
||||
|
||||
ATTR_ROW* pTestItems[]{ pSingle, pChain };
|
||||
|
||||
for (UINT iIndex = 0; iIndex < ARRAYSIZE(pTestItems); iIndex++)
|
||||
{
|
||||
ATTR_ROW* pUnderTest = pTestItems[iIndex];
|
||||
|
||||
pUnderTest->SetAttrToEnd(0, TestAttr);
|
||||
|
||||
// should be down to 1 attribute set from beginning to end of string
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list.size(), 1u);
|
||||
|
||||
// singular pair should contain the color
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list[0].GetAttributes(), TestAttr);
|
||||
|
||||
// and its length should be the length of the whole string
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list[0].GetLength(), (unsigned int)_sDefaultLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestTotalLength)
|
||||
{
|
||||
ATTR_ROW* pTestItems[]{ pSingle, pChain };
|
||||
|
||||
for (UINT iIndex = 0; iIndex < ARRAYSIZE(pTestItems); iIndex++)
|
||||
{
|
||||
ATTR_ROW* pUnderTest = pTestItems[iIndex];
|
||||
|
||||
const size_t Result = pUnderTest->_cchRowWidth;
|
||||
|
||||
VERIFY_ARE_EQUAL((short)Result, _sDefaultLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestResize)
|
||||
{
|
||||
pSingle->Resize(240);
|
||||
pChain->Resize(240);
|
||||
|
||||
pSingle->Resize(255);
|
||||
pChain->Resize(255);
|
||||
|
||||
pSingle->Resize(255);
|
||||
pChain->Resize(255);
|
||||
|
||||
pSingle->Resize(60);
|
||||
pChain->Resize(60);
|
||||
|
||||
pSingle->Resize(60);
|
||||
pChain->Resize(60);
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(pSingle->Resize(0), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
|
||||
VERIFY_THROWS_SPECIFIC(pChain->Resize(0), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
|
||||
}
|
||||
};
|
||||
@@ -6,10 +6,11 @@
|
||||
<RootNamespace>TextBufferUnitTests</RootNamespace>
|
||||
<ProjectName>TextBuffer.Unit.Tests</ProjectName>
|
||||
<TargetName>TextBuffer.Unit.Tests</TargetName>
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AttrRowTests.cpp" />
|
||||
<ClCompile Include="ReflowTests.cpp" />
|
||||
<ClCompile Include="TextColorTests.cpp" />
|
||||
<ClCompile Include="TextAttributeTests.cpp" />
|
||||
|
||||
@@ -14,6 +14,7 @@ DLLDEF =
|
||||
|
||||
SOURCES = \
|
||||
$(SOURCES) \
|
||||
AttrRowTests.cpp \
|
||||
ReflowTests.cpp \
|
||||
TextColorTests.cpp \
|
||||
TextAttributeTests.cpp \
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\wap-common.build.pre.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
|
||||
<!--
|
||||
These two properties are very important!
|
||||
Without them, msbuild will stomp MinVersion and MaxVersionTested in the
|
||||
@@ -13,6 +15,10 @@
|
||||
<AppxOSMaxVersionTestedReplaceManifestVersion>false</AppxOSMaxVersionTestedReplaceManifestVersion>
|
||||
<OCExecutionAliasName Condition="'$(WindowsTerminalBranding)'==''">wtd</OCExecutionAliasName>
|
||||
<OCExecutionAliasName Condition="'$(OCExecutionAliasName)'==''">wt</OCExecutionAliasName>
|
||||
<!-- VS 16.8 causes WAP projects to accidentally package System.Core.dll (from the CLR).
|
||||
This has been fixed in VS 16.9 using the property below. It's safe for us to include
|
||||
it here, even after 16.9 comes out. -->
|
||||
<AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>CA5CAD1A-224A-4171-B13A-F16E576FDD12</ProjectGuid>
|
||||
@@ -64,7 +70,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WindowsTerminal\WindowsTerminal.vcxproj" />
|
||||
<ProjectReference Include="..\..\host\exe\Host.EXE.vcxproj" />
|
||||
<ProjectReference Include="..\..\host\proxy\Host.Proxy.vcxproj" />
|
||||
<ProjectReference Include="..\TerminalAzBridge\TerminalAzBridge.vcxproj" />
|
||||
<ProjectReference Include="..\ShellExtension\WindowsTerminalShellExt.vcxproj" />
|
||||
<ProjectReference Include="..\wt\wt.vcxproj" />
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
@@ -73,43 +73,9 @@
|
||||
<uap3:Name>com.microsoft.windows.terminal.settings</uap3:Name>
|
||||
</uap3:AppExtensionHost>
|
||||
</uap3:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.console.host"
|
||||
Id="OpenConsole-Dev"
|
||||
DisplayName="OpenConsole Dev"
|
||||
Description="Console host built from microsoft/terminal open source repository"
|
||||
PublicFolder="Public">
|
||||
<uap3:Properties>
|
||||
<Clsid>{1F9F2BF5-5BC3-4F17-B0E6-912413F1F451}</Clsid>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.terminal.host"
|
||||
Id="Terminal-Dev"
|
||||
DisplayName="Windows Terminal Dev"
|
||||
Description="Terminal host built from microsoft/terminal open source repository"
|
||||
PublicFolder="Public">
|
||||
<uap3:Properties>
|
||||
<Clsid>{051F34EE-C1FD-4B19-AF75-9BA54648434C}</Clsid>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
<com:Extension Category="windows.comInterface">
|
||||
<com:ComInterface>
|
||||
<com:ProxyStub Id="DEC4804D-56D1-4F73-9FBE-6828E7C85C56" DisplayName="OpenConsoleHandoffProxy" Path="OpenConsoleProxy.dll"/>
|
||||
<com:Interface Id="2B607BC1-43EB-40C3-95AE-2856ADDB7F23" ProxyStubClsid="DEC4804D-56D1-4F73-9FBE-6828E7C85C56"/>
|
||||
<com:Interface Id="59D55CCE-FC8A-48B4-ACE8-0A9286C6557F" ProxyStubClsid="DEC4804D-56D1-4F73-9FBE-6828E7C85C56"/>
|
||||
</com:ComInterface>
|
||||
</com:Extension>
|
||||
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer DisplayName="OpenConsole" Executable="OpenConsole.exe">
|
||||
<com:Class Id="1F9F2BF5-5BC3-4F17-B0E6-912413F1F451"/>
|
||||
</com:ExeServer>
|
||||
<com:ExeServer DisplayName="WindowsTerminal" Executable="WindowsTerminal.exe">
|
||||
<com:Class Id="051F34EE-C1FD-4B19-AF75-9BA54648434C"/>
|
||||
</com:ExeServer>
|
||||
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
|
||||
<com:Class Id="52065414-e077-47ec-a3ac-1cc5455e1b54" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
</com:SurrogateServer>
|
||||
@@ -122,7 +88,7 @@
|
||||
</desktop5:ItemType>
|
||||
<desktop5:ItemType Type="Directory\Background">
|
||||
<desktop5:Verb Id="OpenTerminalDev" Clsid="52065414-e077-47ec-a3ac-1cc5455e1b54" />
|
||||
</desktop5:ItemType>
|
||||
</desktop5:ItemType>
|
||||
</desktop4:FileExplorerContextMenus>
|
||||
</desktop4:Extension>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
@@ -74,43 +74,8 @@
|
||||
Enabled="false"
|
||||
DisplayName="ms-resource:AppNamePre" />
|
||||
</uap5:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.console.host"
|
||||
Id="OpenConsole-Pre"
|
||||
DisplayName="OpenConsole Preview"
|
||||
Description="Console host built from microsoft/terminal open source repository"
|
||||
PublicFolder="Public">
|
||||
<uap3:Properties>
|
||||
<Clsid>{06EC847C-C0A5-46B8-92CB-7C92F6E35CD5}</Clsid>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.terminal.host"
|
||||
Id="Terminal-Pre"
|
||||
DisplayName="Windows Terminal Preview"
|
||||
Description="Terminal host built from microsoft/terminal open source repository"
|
||||
PublicFolder="Public">
|
||||
<uap3:Properties>
|
||||
<Clsid>{86633F1F-6454-40EC-89CE-DA4EBA977EE2}</Clsid>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
<com:Extension Category="windows.comInterface">
|
||||
<com:ComInterface>
|
||||
<com:ProxyStub Id="1833E661-CC81-4DD0-87C6-C2F74BD39EFA" DisplayName="OpenConsoleHandoffProxy" Path="OpenConsoleProxy.dll"/>
|
||||
<com:Interface Id="2B607BC1-43EB-40C3-95AE-2856ADDB7F23" ProxyStubClsid="1833E661-CC81-4DD0-87C6-C2F74BD39EFA"/>
|
||||
<com:Interface Id="59D55CCE-FC8A-48B4-ACE8-0A9286C6557F" ProxyStubClsid="1833E661-CC81-4DD0-87C6-C2F74BD39EFA"/>
|
||||
</com:ComInterface>
|
||||
</com:Extension>
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer DisplayName="OpenConsole" Executable="OpenConsole.exe">
|
||||
<com:Class Id="06EC847C-C0A5-46B8-92CB-7C92F6E35CD5"/>
|
||||
</com:ExeServer>
|
||||
<com:ExeServer DisplayName="WindowsTerminal" Executable="WindowsTerminal.exe">
|
||||
<com:Class Id="86633F1F-6454-40EC-89CE-DA4EBA977EE2"/>
|
||||
</com:ExeServer>
|
||||
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
|
||||
<com:Class Id="02db545a-3e20-46de-83a5-1329b1e88b6b" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
</com:SurrogateServer>
|
||||
@@ -140,10 +105,8 @@
|
||||
<Extensions>
|
||||
<uap7:Extension Category="windows.sharedFonts">
|
||||
<uap7:SharedFonts>
|
||||
<uap4:Font File="CascadiaCode.ttf" />
|
||||
<uap4:Font File="CascadiaCodeItalic.ttf" />
|
||||
<uap4:Font File="Cascadia.ttf" />
|
||||
<uap4:Font File="CascadiaMono.ttf" />
|
||||
<uap4:Font File="CascadiaMonoItalic.ttf" />
|
||||
</uap7:SharedFonts>
|
||||
</uap7:Extension>
|
||||
</Extensions>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
@@ -74,43 +74,9 @@
|
||||
Enabled="false"
|
||||
DisplayName="ms-resource:AppName" />
|
||||
</uap5:Extension>
|
||||
<!-- <uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.console.host"
|
||||
Id="OpenConsole"
|
||||
DisplayName="OpenConsole"
|
||||
Description="Console host built from microsoft/terminal open source repository"
|
||||
PublicFolder="Public">
|
||||
<uap3:Properties>
|
||||
<Clsid>{2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69}</Clsid>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.terminal.host"
|
||||
Id="Terminal"
|
||||
DisplayName="Windows Terminal"
|
||||
Description="Terminal host built from microsoft/terminal open source repository"
|
||||
PublicFolder="Public">
|
||||
<uap3:Properties>
|
||||
<Clsid>{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}</Clsid>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
<com:Extension Category="windows.comInterface">
|
||||
<com:ComInterface>
|
||||
<com:ProxyStub Id="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F" DisplayName="OpenConsoleHandoffProxy" Path="OpenConsoleProxy.dll"/>
|
||||
<com:Interface Id="2B607BC1-43EB-40C3-95AE-2856ADDB7F23" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
<com:Interface Id="59D55CCE-FC8A-48B4-ACE8-0A9286C6557F" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
</com:ComInterface>
|
||||
</com:Extension> -->
|
||||
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<!-- <com:ExeServer DisplayName="OpenConsole" Executable="OpenConsole.exe">
|
||||
<com:Class Id="2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69"/>
|
||||
</com:ExeServer>
|
||||
<com:ExeServer DisplayName="WindowsTerminal" Executable="WindowsTerminal.exe">
|
||||
<com:Class Id="E12CFF52-A866-4C77-9A90-F570A7AA2C6B"/>
|
||||
</com:ExeServer> -->
|
||||
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
|
||||
<com:Class Id="9f156763-7844-4dc4-b2b1-901f640f5155" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
</com:SurrogateServer>
|
||||
@@ -140,10 +106,8 @@
|
||||
<Extensions>
|
||||
<uap7:Extension Category="windows.sharedFonts">
|
||||
<uap7:SharedFonts>
|
||||
<uap4:Font File="CascadiaCode.ttf" />
|
||||
<uap4:Font File="CascadiaCodeItalic.ttf" />
|
||||
<uap4:Font File="Cascadia.ttf" />
|
||||
<uap4:Font File="CascadiaMono.ttf" />
|
||||
<uap4:Font File="CascadiaMonoItalic.ttf" />
|
||||
</uap7:SharedFonts>
|
||||
</uap7:Extension>
|
||||
</Extensions>
|
||||
|
||||
@@ -12,9 +12,13 @@
|
||||
<Link>Images\%(RecursiveDir)%(FileName)%(Extension)</Link>
|
||||
</Content>
|
||||
<!-- Fonts -->
|
||||
<Content Include="$(OpenConsoleDir)res\fonts\*.ttf" Condition="'$(WindowsTerminalOfficialBuild)'=='true'">
|
||||
<Content Include="$(OpenConsoleDir)res\Cascadia.ttf" Condition="'$(WindowsTerminalOfficialBuild)'=='true'">
|
||||
<DeploymentContent>true</DeploymentContent>
|
||||
<Link>%(FileName)%(Extension)</Link>
|
||||
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
|
||||
</Content>
|
||||
<Content Include="$(OpenConsoleDir)res\CascadiaMono.ttf" Condition="'$(WindowsTerminalOfficialBuild)'=='true'">
|
||||
<DeploymentContent>true</DeploymentContent>
|
||||
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
|
||||
</Content>
|
||||
<!-- Profile Icons -->
|
||||
<Content Include="$(OpenConsoleDir)src\cascadia\CascadiaPackage\ProfileIcons\**\*">
|
||||
|
||||
@@ -338,34 +338,34 @@ namespace SettingsModelLocalTests
|
||||
|
||||
// verify profile defaults
|
||||
Log::Comment(L"Profile Defaults");
|
||||
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(settings->ProfileDefaults().HasColorSchemeName());
|
||||
|
||||
// verify all other profiles
|
||||
const auto& profiles{ settings->AllProfiles() };
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(0) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(1) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(2) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_FALSE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_FALSE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(3) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(L"Scheme 2", prof.DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Scheme 2", prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
@@ -106,9 +106,9 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
{
|
||||
@@ -117,9 +117,9 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.ActionAndArgs().Action());
|
||||
VERIFY_IS_NULL(command.ActionAndArgs().Args());
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action());
|
||||
VERIFY_IS_NULL(command.Action().Args());
|
||||
}
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
@@ -127,9 +127,9 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
{
|
||||
@@ -165,9 +165,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"command1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
@@ -176,9 +176,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"command2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
@@ -187,9 +187,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"command4");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Automatic, realArgs.SplitStyle());
|
||||
@@ -198,9 +198,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"command5");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Automatic, realArgs.SplitStyle());
|
||||
@@ -209,9 +209,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"command6");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Automatic, realArgs.SplitStyle());
|
||||
@@ -239,9 +239,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"command1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Automatic, realArgs.SplitStyle());
|
||||
@@ -268,9 +268,9 @@ namespace SettingsModelLocalTests
|
||||
// this test will break.
|
||||
auto command = commands.Lookup(L"Duplicate tab");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
}
|
||||
@@ -309,9 +309,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Automatic, realArgs.SplitStyle());
|
||||
@@ -319,9 +319,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane, split: vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
@@ -329,9 +329,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane, split: horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
@@ -355,9 +355,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
@@ -410,9 +410,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
@@ -423,9 +423,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"action1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
@@ -436,9 +436,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"action2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
@@ -449,9 +449,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"action3");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
@@ -462,9 +462,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"action4");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
@@ -477,9 +477,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"action5");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
@@ -492,9 +492,9 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
auto command = commands.Lookup(L"action6");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
|
||||
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
|
||||
@@ -15,7 +15,7 @@ using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
namespace SettingsModelLocalTests
|
||||
{
|
||||
@@ -88,8 +88,6 @@ namespace SettingsModelLocalTests
|
||||
|
||||
TEST_METHOD(TestValidDefaults);
|
||||
|
||||
TEST_METHOD(TestInheritedCommand);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -1313,9 +1311,9 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size());
|
||||
|
||||
VERIFY_ARE_EQUAL(L"schemeOne", settings->_allProfiles.GetAt(0).DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"InvalidSchemeName", settings->_allProfiles.GetAt(1).DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(2).DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"schemeOne", settings->_allProfiles.GetAt(0).ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"InvalidSchemeName", settings->_allProfiles.GetAt(1).ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(2).ColorSchemeName());
|
||||
|
||||
settings->_ValidateAllSchemesExist();
|
||||
|
||||
@@ -1325,9 +1323,9 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings->_globals->ColorSchemes().Size());
|
||||
|
||||
VERIFY_ARE_EQUAL(L"schemeOne", settings->_allProfiles.GetAt(0).DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(1).DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(2).DefaultAppearance().ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"schemeOne", settings->_allProfiles.GetAt(0).ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(1).ColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", settings->_allProfiles.GetAt(2).ColorSchemeName());
|
||||
}
|
||||
|
||||
void DeserializationTests::ValidateColorSchemeInCommands()
|
||||
@@ -1545,7 +1543,7 @@ namespace SettingsModelLocalTests
|
||||
settings->_ParseJsonString(settingsJson, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
VERIFY_ARE_NOT_EQUAL(0u, settings->_allProfiles.Size());
|
||||
VERIFY_ARE_EQUAL(expectedPath, settings->_allProfiles.GetAt(0).DefaultAppearance().ExpandedBackgroundImagePath());
|
||||
VERIFY_ARE_EQUAL(expectedPath, settings->_allProfiles.GetAt(0).ExpandedBackgroundImagePath());
|
||||
}
|
||||
void DeserializationTests::TestProfileBackgroundImageWithDesktopWallpaper()
|
||||
{
|
||||
@@ -1566,8 +1564,8 @@ namespace SettingsModelLocalTests
|
||||
auto settings = winrt::make_self<implementation::CascadiaSettings>();
|
||||
settings->_ParseJsonString(settingsJson, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
VERIFY_ARE_EQUAL(expectedBackgroundImagePath, settings->_allProfiles.GetAt(0).DefaultAppearance().BackgroundImagePath());
|
||||
VERIFY_ARE_NOT_EQUAL(expectedBackgroundImagePath, settings->_allProfiles.GetAt(0).DefaultAppearance().ExpandedBackgroundImagePath());
|
||||
VERIFY_ARE_EQUAL(expectedBackgroundImagePath, settings->_allProfiles.GetAt(0).BackgroundImagePath());
|
||||
VERIFY_ARE_NOT_EQUAL(expectedBackgroundImagePath, settings->_allProfiles.GetAt(0).ExpandedBackgroundImagePath());
|
||||
}
|
||||
void DeserializationTests::TestCloseOnExitParsing()
|
||||
{
|
||||
@@ -1920,12 +1918,7 @@ namespace SettingsModelLocalTests
|
||||
const auto settingsObject = VerifyParseSucceeded(badSettings);
|
||||
auto settings = implementation::CascadiaSettings::FromJson(settingsObject);
|
||||
|
||||
// KeyMap: ctrl+a/b are mapped to "invalid"
|
||||
// ActionMap: "splitPane" and "invalid" are the only deserialized actions
|
||||
// NameMap: "splitPane" has no key binding, but it is still added to the name map
|
||||
VERIFY_ARE_EQUAL(2u, settings->_globals->_actionMap->_KeyMap.size());
|
||||
VERIFY_ARE_EQUAL(2u, settings->_globals->_actionMap->_ActionMap.size());
|
||||
VERIFY_ARE_EQUAL(1u, settings->_globals->_actionMap->NameMap().Size());
|
||||
VERIFY_ARE_EQUAL(0u, settings->_globals->_keymap->_keyShortcuts.size());
|
||||
|
||||
VERIFY_ARE_EQUAL(4u, settings->_globals->_keybindingsWarnings.size());
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_globals->_keybindingsWarnings.at(0));
|
||||
@@ -1969,10 +1962,7 @@ namespace SettingsModelLocalTests
|
||||
|
||||
auto settings = implementation::CascadiaSettings::FromJson(settingsObject);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings->_globals->_actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast<int32_t>('a') }));
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast<int32_t>('b') }));
|
||||
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast<int32_t>('c') }));
|
||||
VERIFY_ARE_EQUAL(0u, settings->_globals->_keymap->_keyShortcuts.size());
|
||||
|
||||
for (const auto& warning : settings->_globals->_keybindingsWarnings)
|
||||
{
|
||||
@@ -2113,18 +2103,18 @@ namespace SettingsModelLocalTests
|
||||
const auto profile2Guid = settings->_allProfiles.GetAt(2).Guid();
|
||||
VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid);
|
||||
|
||||
auto actionMap = winrt::get_self<implementation::ActionMap>(settings->_globals->ActionMap());
|
||||
VERIFY_ARE_EQUAL(5u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::get_self<implementation::KeyMapping>(settings->_globals->KeyMap());
|
||||
VERIFY_ARE_EQUAL(5u, keymap->_keyShortcuts.size());
|
||||
|
||||
// A/D, B, C, E will be in the list of commands, for 4 total.
|
||||
// * A and D share the same name, so they'll only generate a single action.
|
||||
// * F's name is set manually to `null`
|
||||
const auto& nameMap{ actionMap->NameMap() };
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
auto commands = settings->_globals->Commands();
|
||||
VERIFY_ARE_EQUAL(4u, commands.Size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -2141,7 +2131,7 @@ namespace SettingsModelLocalTests
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -2155,7 +2145,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -2169,7 +2159,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -2183,7 +2173,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -2197,21 +2187,11 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
|
||||
Log::Comment(L"Now verify the commands");
|
||||
_logCommandNames(nameMap);
|
||||
_logCommandNames(commands);
|
||||
{
|
||||
// This was renamed to "ctrl+c" in C. So this does not exist.
|
||||
auto command = nameMap.TryLookup(L"Split pane, split: vertical");
|
||||
VERIFY_IS_NULL(command);
|
||||
}
|
||||
{
|
||||
// This was renamed to "ctrl+c" in C. So this does not exist.
|
||||
auto command = nameMap.TryLookup(L"ctrl+b");
|
||||
VERIFY_IS_NULL(command);
|
||||
}
|
||||
{
|
||||
auto command = nameMap.TryLookup(L"ctrl+c");
|
||||
auto command = commands.Lookup(L"Split pane, split: vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -2225,9 +2205,52 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
// This was renamed to null (aka removed from the name map) in F. So this does not exist.
|
||||
auto command = nameMap.TryLookup(L"Split pane, split: horizontal");
|
||||
VERIFY_IS_NULL(command);
|
||||
auto command = commands.Lookup(L"ctrl+b");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"ctrl+c");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"Split pane, split: horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
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(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2285,16 +2308,16 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
|
||||
auto commands = settings->_globals->Commands();
|
||||
settings->_ValidateSettings();
|
||||
const auto& nameMap{ settings->ActionMap().NameMap() };
|
||||
_logCommandNames(nameMap);
|
||||
_logCommandNames(commands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
|
||||
// Because the "parent" command didn't have a name, it couldn't be
|
||||
// placed into the list of commands. It and it's children are just
|
||||
// ignored.
|
||||
VERIFY_ARE_EQUAL(0u, nameMap.Size());
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
}
|
||||
|
||||
void DeserializationTests::TestNestedCommandWithBadSubCommands()
|
||||
@@ -2335,13 +2358,13 @@ namespace SettingsModelLocalTests
|
||||
auto settings = winrt::make_self<implementation::CascadiaSettings>();
|
||||
settings->_ParseJsonString(settingsJson, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
auto commands = settings->_globals->Commands();
|
||||
settings->_ValidateSettings();
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(1));
|
||||
const auto& nameMap{ settings->ActionMap().NameMap() };
|
||||
VERIFY_ARE_EQUAL(0u, nameMap.Size());
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
}
|
||||
|
||||
void DeserializationTests::TestUnbindNestedCommand()
|
||||
@@ -2409,23 +2432,22 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
|
||||
auto commands = settings->_globals->Commands();
|
||||
settings->_ValidateSettings();
|
||||
auto nameMap{ settings->ActionMap().NameMap() };
|
||||
_logCommandNames(nameMap);
|
||||
_logCommandNames(commands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
Log::Comment(L"Layer second bit of json, to unbind the original command.");
|
||||
|
||||
settings->_ParseJsonString(settings1Json, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
nameMap = settings->ActionMap().NameMap();
|
||||
_logCommandNames(nameMap);
|
||||
commands = settings->_globals->Commands();
|
||||
_logCommandNames(commands);
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(0u, nameMap.Size());
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
}
|
||||
|
||||
void DeserializationTests::TestRebindNestedCommand()
|
||||
@@ -2494,17 +2516,16 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
|
||||
const auto& actionMap{ settings->ActionMap() };
|
||||
auto commands = settings->_globals->Commands();
|
||||
settings->_ValidateSettings();
|
||||
auto nameMap{ actionMap.NameMap() };
|
||||
_logCommandNames(nameMap);
|
||||
_logCommandNames(commands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
winrt::hstring commandName{ L"parent" };
|
||||
auto commandProj = nameMap.TryLookup(commandName);
|
||||
auto commandProj = commands.Lookup(commandName);
|
||||
VERIFY_IS_NOT_NULL(commandProj);
|
||||
|
||||
winrt::com_ptr<implementation::Command> commandImpl;
|
||||
@@ -2518,18 +2539,17 @@ namespace SettingsModelLocalTests
|
||||
settings->_ParseJsonString(settings1Json, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
nameMap = settings->ActionMap().NameMap();
|
||||
_logCommandNames(nameMap);
|
||||
commands = settings->_globals->Commands();
|
||||
_logCommandNames(commands);
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
winrt::hstring commandName{ L"parent" };
|
||||
auto commandProj = nameMap.TryLookup(commandName);
|
||||
auto commandProj = commands.Lookup(commandName);
|
||||
|
||||
VERIFY_IS_NOT_NULL(commandProj);
|
||||
auto actionAndArgs = commandProj.ActionAndArgs();
|
||||
auto actionAndArgs = commandProj.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
@@ -2622,10 +2642,8 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(settings->_globals->_colorSchemes.HasKey(schemeName), copyImpl->_globals->_colorSchemes.HasKey(schemeName));
|
||||
|
||||
// test actions
|
||||
VERIFY_ARE_EQUAL(settings->_globals->_actionMap->_KeyMap.size(), copyImpl->_globals->_actionMap->_KeyMap.size());
|
||||
const auto& nameMapOriginal{ settings->_globals->_actionMap->NameMap() };
|
||||
const auto& nameMapCopy{ copyImpl->_globals->_actionMap->NameMap() };
|
||||
VERIFY_ARE_EQUAL(nameMapOriginal.Size(), nameMapCopy.Size());
|
||||
VERIFY_ARE_EQUAL(settings->_globals->_keymap->_keyShortcuts.size(), copyImpl->_globals->_keymap->_keyShortcuts.size());
|
||||
VERIFY_ARE_EQUAL(settings->_globals->_commands.Size(), copyImpl->_globals->_commands.Size());
|
||||
|
||||
// Test that changing the copy should not change the original
|
||||
VERIFY_ARE_EQUAL(settings->_globals->WordDelimiters(), copyImpl->_globals->WordDelimiters());
|
||||
@@ -2763,158 +2781,4 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(settings.ActiveProfiles().Size(), settings.AllProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(settings.AllProfiles().Size(), 2u);
|
||||
}
|
||||
|
||||
void DeserializationTests::TestInheritedCommand()
|
||||
{
|
||||
// Test unbinding a command's key chord or name that originated in another layer.
|
||||
|
||||
const std::string settings1Json{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile1",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"commandline": "pwsh.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile2",
|
||||
"historySize": 3,
|
||||
"commandline": "wsl.exe"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "foo",
|
||||
"command": "closePane",
|
||||
"keys": "ctrl+shift+w"
|
||||
}
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
const std::string settings2Json{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"actions": [
|
||||
{
|
||||
"command": null,
|
||||
"keys": "ctrl+shift+w"
|
||||
},
|
||||
],
|
||||
})" };
|
||||
|
||||
const std::string settings3Json{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"actions": [
|
||||
{
|
||||
"name": "bar",
|
||||
"command": "closePane"
|
||||
},
|
||||
],
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settings1Json);
|
||||
VerifyParseSucceeded(settings2Json);
|
||||
VerifyParseSucceeded(settings3Json);
|
||||
|
||||
auto settings = winrt::make_self<implementation::CascadiaSettings>();
|
||||
settings->_ParseJsonString(settings1Json, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size());
|
||||
|
||||
settings->_ValidateSettings();
|
||||
auto nameMap{ settings->ActionMap().NameMap() };
|
||||
_logCommandNames(nameMap);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
|
||||
const KeyChord expectedKeyChord{ true, false, true, static_cast<int>('W') };
|
||||
{
|
||||
// Verify NameMap returns correct value
|
||||
const auto& cmd{ nameMap.TryLookup(L"foo") };
|
||||
VERIFY_IS_NOT_NULL(cmd);
|
||||
VERIFY_IS_NOT_NULL(cmd.Keys());
|
||||
VERIFY_IS_TRUE(cmd.Keys().Modifiers() == expectedKeyChord.Modifiers() && cmd.Keys().Vkey() == expectedKeyChord.Vkey());
|
||||
}
|
||||
{
|
||||
// Verify ActionMap::GetActionByKeyChord API
|
||||
const auto& cmd{ settings->ActionMap().GetActionByKeyChord(expectedKeyChord) };
|
||||
VERIFY_IS_NOT_NULL(cmd);
|
||||
VERIFY_IS_NOT_NULL(cmd.Keys());
|
||||
VERIFY_IS_TRUE(cmd.Keys().Modifiers() == expectedKeyChord.Modifiers() && cmd.Keys().Vkey() == expectedKeyChord.Vkey());
|
||||
}
|
||||
{
|
||||
// Verify ActionMap::GetKeyBindingForAction API
|
||||
const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(ShortcutAction::ClosePane) };
|
||||
VERIFY_IS_NOT_NULL(actualKeyChord);
|
||||
VERIFY_IS_TRUE(actualKeyChord.Modifiers() == expectedKeyChord.Modifiers() && actualKeyChord.Vkey() == expectedKeyChord.Vkey());
|
||||
}
|
||||
|
||||
Log::Comment(L"Layer second bit of json, to unbind the key chord of the original command.");
|
||||
|
||||
settings->_ParseJsonString(settings2Json, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
nameMap = settings->ActionMap().NameMap();
|
||||
_logCommandNames(nameMap);
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
{
|
||||
// Verify NameMap returns correct value
|
||||
const auto& cmd{ nameMap.TryLookup(L"foo") };
|
||||
VERIFY_IS_NOT_NULL(cmd);
|
||||
VERIFY_IS_NULL(cmd.Keys());
|
||||
}
|
||||
{
|
||||
// Verify ActionMap::GetActionByKeyChord API
|
||||
const auto& cmd{ settings->ActionMap().GetActionByKeyChord(expectedKeyChord) };
|
||||
VERIFY_IS_NULL(cmd);
|
||||
}
|
||||
{
|
||||
// Verify ActionMap::GetKeyBindingForAction API
|
||||
const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(ShortcutAction::ClosePane) };
|
||||
VERIFY_IS_NULL(actualKeyChord);
|
||||
}
|
||||
|
||||
Log::Comment(L"Layer third bit of json, to unbind name of the original command.");
|
||||
|
||||
settings->_ParseJsonString(settings3Json, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
nameMap = settings->ActionMap().NameMap();
|
||||
_logCommandNames(nameMap);
|
||||
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
{
|
||||
// Verify NameMap returns correct value
|
||||
const auto& cmd{ nameMap.TryLookup(L"bar") };
|
||||
VERIFY_IS_NOT_NULL(cmd);
|
||||
VERIFY_IS_NULL(cmd.Keys());
|
||||
VERIFY_ARE_EQUAL(L"bar", cmd.Name());
|
||||
}
|
||||
{
|
||||
// Verify ActionMap::GetActionByKeyChord API
|
||||
const auto& cmd{ settings->ActionMap().GetActionByKeyChord(expectedKeyChord) };
|
||||
VERIFY_IS_NULL(cmd);
|
||||
}
|
||||
{
|
||||
// Verify ActionMap::GetKeyBindingForAction API
|
||||
const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(ShortcutAction::ClosePane) };
|
||||
VERIFY_IS_NULL(actualKeyChord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
#include "../TerminalSettingsModel/ColorScheme.h"
|
||||
#include "../TerminalSettingsModel/CascadiaSettings.h"
|
||||
#include "../TerminalSettingsModel/ActionMap.h"
|
||||
#include "../TerminalSettingsModel/KeyMapping.h"
|
||||
#include "JsonTestClass.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
@@ -51,8 +51,6 @@ namespace SettingsModelLocalTests
|
||||
TEST_METHOD(TestToggleCommandPaletteArgs);
|
||||
TEST_METHOD(TestMoveTabArgs);
|
||||
|
||||
TEST_METHOD(TestGetKeyBindingForAction);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -73,18 +71,18 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
|
||||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
|
||||
actionMap->LayerJson(bindings1Json);
|
||||
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings1Json);
|
||||
VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size());
|
||||
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(4u, keymap->_keyShortcuts.size());
|
||||
}
|
||||
|
||||
void KeyBindingsTests::LayerKeybindings()
|
||||
@@ -97,18 +95,18 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
|
||||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
|
||||
actionMap->LayerJson(bindings1Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings1Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size());
|
||||
}
|
||||
|
||||
void KeyBindingsTests::UnbindKeybindings()
|
||||
@@ -127,57 +125,52 @@ namespace SettingsModelLocalTests
|
||||
const auto bindings4Json = VerifyParseSucceeded(bindings4String);
|
||||
const auto bindings5Json = VerifyParseSucceeded(bindings5String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
|
||||
actionMap->LayerJson(bindings1Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings1Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key using `\"unbound\"` to unbind the key"));
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast<int32_t>('c') }));
|
||||
keymap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key using `null` to unbind the key"));
|
||||
// First add back a good binding
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings3Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast<int32_t>('c') }));
|
||||
keymap->LayerJson(bindings3Json);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_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
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings4Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast<int32_t>('c') }));
|
||||
keymap->LayerJson(bindings4Json);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_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
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
// Then try layering in the bad setting
|
||||
actionMap->LayerJson(bindings5Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast<int32_t>('c') }));
|
||||
keymap->LayerJson(bindings5Json);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try unbinding a key that wasn't bound at all"));
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast<int32_t>('c') }));
|
||||
keymap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestArbitraryArgs()
|
||||
@@ -201,17 +194,17 @@ namespace SettingsModelLocalTests
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(10u, keymap->_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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
@@ -222,7 +215,7 @@ namespace SettingsModelLocalTests
|
||||
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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
@@ -233,7 +226,7 @@ namespace SettingsModelLocalTests
|
||||
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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
@@ -244,7 +237,7 @@ namespace SettingsModelLocalTests
|
||||
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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -256,7 +249,7 @@ namespace SettingsModelLocalTests
|
||||
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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -270,7 +263,7 @@ namespace SettingsModelLocalTests
|
||||
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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -284,7 +277,7 @@ namespace SettingsModelLocalTests
|
||||
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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -296,7 +289,7 @@ namespace SettingsModelLocalTests
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that `copy` null as it's `args` parses as the default option"));
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -308,7 +301,7 @@ namespace SettingsModelLocalTests
|
||||
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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -320,7 +313,7 @@ namespace SettingsModelLocalTests
|
||||
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(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -340,15 +333,15 @@ namespace SettingsModelLocalTests
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(4u, keymap->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -357,7 +350,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -366,7 +359,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -375,7 +368,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -394,15 +387,15 @@ namespace SettingsModelLocalTests
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(3u, keymap->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -411,7 +404,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -422,7 +415,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -439,15 +432,15 @@ namespace SettingsModelLocalTests
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
@@ -468,15 +461,15 @@ namespace SettingsModelLocalTests
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(6u, keymap->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -485,7 +478,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -494,7 +487,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -503,7 +496,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -512,7 +505,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -522,7 +515,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_DOWN) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -533,10 +526,10 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "scrollDown", "rowsToScroll": -1 } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
auto invalidKeyMap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(invalidKeyMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidKeyMap->_keyShortcuts.size());
|
||||
VERIFY_THROWS(invalidKeyMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,15 +542,15 @@ namespace SettingsModelLocalTests
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -566,7 +559,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -575,17 +568,17 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": "moveTab" }])" };
|
||||
auto actionMapNoArgs = winrt::make_self<implementation::ActionMap>();
|
||||
actionMapNoArgs->LayerJson(bindingsInvalidString);
|
||||
VERIFY_ARE_EQUAL(0u, actionMapNoArgs->_KeyMap.size());
|
||||
auto keyMapNoArgs = winrt::make_self<implementation::KeyMapping>();
|
||||
keyMapNoArgs->LayerJson(bindingsInvalidString);
|
||||
VERIFY_ARE_EQUAL(0u, keyMapNoArgs->_keyShortcuts.size());
|
||||
}
|
||||
{
|
||||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
auto invalidKeyMap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(invalidKeyMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidKeyMap->_keyShortcuts.size());
|
||||
VERIFY_THROWS(invalidKeyMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,15 +592,15 @@ namespace SettingsModelLocalTests
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
|
||||
auto keymap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(keymap);
|
||||
VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size());
|
||||
keymap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(3u, keymap->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -616,7 +609,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -625,7 +618,7 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
@@ -635,79 +628,10 @@ namespace SettingsModelLocalTests
|
||||
{
|
||||
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "commandPalette", "launchMode": "bad" } }])" };
|
||||
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
|
||||
auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(invalidActionMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
|
||||
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestGetKeyBindingForAction()
|
||||
{
|
||||
const std::string bindings0String{ R"([ { "command": "closeWindow", "keys": "ctrl+a" } ])" };
|
||||
const std::string bindings1String{ R"([ { "command": { "action": "copy", "singleLine": true }, "keys": "ctrl+b" } ])" };
|
||||
const std::string bindings2String{ R"([ { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+c" } ])" };
|
||||
const std::string bindings3String{ R"([ { "command": "commandPalette", "keys": "ctrl+shift+p" } ])" };
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
|
||||
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
|
||||
const auto bindings3Json = VerifyParseSucceeded(bindings3String);
|
||||
|
||||
auto VerifyKeyChordEquality = [](const KeyChord& expected, const KeyChord& actual) {
|
||||
if (expected)
|
||||
{
|
||||
VERIFY_IS_NOT_NULL(actual);
|
||||
VERIFY_ARE_EQUAL(expected.Modifiers(), actual.Modifiers());
|
||||
VERIFY_ARE_EQUAL(expected.Vkey(), actual.Vkey());
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_IS_NULL(actual);
|
||||
}
|
||||
};
|
||||
|
||||
auto actionMap = winrt::make_self<implementation::ActionMap>();
|
||||
VERIFY_IS_NOT_NULL(actionMap);
|
||||
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
|
||||
|
||||
{
|
||||
Log::Comment(L"simple command: no args");
|
||||
actionMap->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) };
|
||||
VerifyKeyChordEquality({ KeyModifiers::Ctrl, static_cast<int32_t>('A') }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with args");
|
||||
actionMap->LayerJson(bindings1Json);
|
||||
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
|
||||
|
||||
auto args{ winrt::make_self<implementation::CopyTextArgs>() };
|
||||
args->SingleLine(true);
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) };
|
||||
VerifyKeyChordEquality({ KeyModifiers::Ctrl, static_cast<int32_t>('B') }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with new terminal args");
|
||||
actionMap->LayerJson(bindings2Json);
|
||||
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
|
||||
|
||||
auto newTerminalArgs{ winrt::make_self<implementation::NewTerminalArgs>() };
|
||||
newTerminalArgs->ProfileIndex(0);
|
||||
auto args{ winrt::make_self<implementation::NewTabArgs>(*newTerminalArgs) };
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) };
|
||||
VerifyKeyChordEquality({ KeyModifiers::Ctrl, static_cast<int32_t>('C') }, kbd);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"command with hidden args");
|
||||
actionMap->LayerJson(bindings3Json);
|
||||
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
|
||||
|
||||
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
|
||||
VerifyKeyChordEquality({ KeyModifiers::Ctrl | KeyModifiers::Shift, static_cast<int32_t>('P') }, kbd);
|
||||
auto invalidKeyMap = winrt::make_self<implementation::KeyMapping>();
|
||||
VERIFY_IS_NOT_NULL(invalidKeyMap);
|
||||
VERIFY_ARE_EQUAL(0u, invalidKeyMap->_keyShortcuts.size());
|
||||
VERIFY_THROWS(invalidKeyMap->LayerJson(bindingsInvalidJson);, std::exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace SettingsModelLocalTests
|
||||
TEST_METHOD(LayerProfileProperties);
|
||||
TEST_METHOD(LayerProfileIcon);
|
||||
TEST_METHOD(LayerProfilesOnArray);
|
||||
TEST_METHOD(DuplicateProfileTest);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
@@ -118,14 +117,14 @@ namespace SettingsModelLocalTests
|
||||
const auto profile2Json = VerifyParseSucceeded(profile2String);
|
||||
|
||||
auto profile0 = implementation::Profile::FromJson(profile0Json);
|
||||
VERIFY_IS_NOT_NULL(profile0->DefaultAppearance().Foreground());
|
||||
VERIFY_ARE_EQUAL(til::color(0, 0, 0), til::color{ profile0->DefaultAppearance().Foreground().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile0->Foreground());
|
||||
VERIFY_ARE_EQUAL(til::color(0, 0, 0), til::color{ profile0->Foreground().Value() });
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile0->DefaultAppearance().Background());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->DefaultAppearance().Background().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile0->Background());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->Background().Value() });
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile0->DefaultAppearance().SelectionBackground());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->DefaultAppearance().SelectionBackground().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile0->SelectionBackground());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile0->SelectionBackground().Value() });
|
||||
|
||||
VERIFY_ARE_EQUAL(L"profile0", profile0->Name());
|
||||
|
||||
@@ -136,14 +135,14 @@ namespace SettingsModelLocalTests
|
||||
auto profile1{ profile0->CreateChild() };
|
||||
profile1->LayerJson(profile1Json);
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile1->DefaultAppearance().Foreground());
|
||||
VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile1->DefaultAppearance().Foreground().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile1->Foreground());
|
||||
VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile1->Foreground().Value() });
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile1->DefaultAppearance().Background());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile1->DefaultAppearance().Background().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile1->Background());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile1->Background().Value() });
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile1->DefaultAppearance().Background());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile1->DefaultAppearance().Background().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile1->Background());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile1->Background().Value() });
|
||||
|
||||
VERIFY_ARE_EQUAL(L"profile1", profile1->Name());
|
||||
|
||||
@@ -155,14 +154,14 @@ namespace SettingsModelLocalTests
|
||||
auto profile2{ profile1->CreateChild() };
|
||||
profile2->LayerJson(profile2Json);
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile2->DefaultAppearance().Foreground());
|
||||
VERIFY_ARE_EQUAL(til::color(3, 3, 3), til::color{ profile2->DefaultAppearance().Foreground().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile2->Foreground());
|
||||
VERIFY_ARE_EQUAL(til::color(3, 3, 3), til::color{ profile2->Foreground().Value() });
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile2->DefaultAppearance().Background());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile2->DefaultAppearance().Background().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile2->Background());
|
||||
VERIFY_ARE_EQUAL(til::color(1, 1, 1), til::color{ profile2->Background().Value() });
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile2->DefaultAppearance().SelectionBackground());
|
||||
VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile2->DefaultAppearance().SelectionBackground().Value() });
|
||||
VERIFY_IS_NOT_NULL(profile2->SelectionBackground());
|
||||
VERIFY_ARE_EQUAL(til::color(2, 2, 2), til::color{ profile2->SelectionBackground().Value() });
|
||||
|
||||
VERIFY_ARE_EQUAL(L"profile2", profile2->Name());
|
||||
|
||||
@@ -308,22 +307,4 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(L"profile4", settings->_allProfiles.GetAt(0).Name());
|
||||
}
|
||||
|
||||
void ProfileTests::DuplicateProfileTest()
|
||||
{
|
||||
const std::string profile0String{ R"({
|
||||
"name" : "profile0",
|
||||
"backgroundImage" : "some//path"
|
||||
})" };
|
||||
|
||||
const auto profile0Json = VerifyParseSucceeded(profile0String);
|
||||
|
||||
auto settings = winrt::make_self<implementation::CascadiaSettings>();
|
||||
|
||||
settings->_LayerOrCreateProfile(profile0Json);
|
||||
auto duplicatedProfile = settings->DuplicateProfile(*settings->_FindMatchingProfile(profile0Json));
|
||||
duplicatedProfile.Name(L"profile0");
|
||||
|
||||
const auto duplicatedJson = winrt::get_self<implementation::Profile>(duplicatedProfile)->ToJson();
|
||||
VERIFY_ARE_EQUAL(profile0Json, duplicatedJson);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
namespace SettingsModelLocalTests
|
||||
{
|
||||
@@ -39,7 +39,6 @@ namespace SettingsModelLocalTests
|
||||
TEST_METHOD(GlobalSettings);
|
||||
TEST_METHOD(Profile);
|
||||
TEST_METHOD(ColorScheme);
|
||||
TEST_METHOD(Actions);
|
||||
TEST_METHOD(CascadiaSettings);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
@@ -107,15 +106,12 @@ namespace SettingsModelLocalTests
|
||||
|
||||
"experimental.input.forceVT": false,
|
||||
"experimental.rendering.forceFullRepaint": false,
|
||||
"experimental.rendering.software": false,
|
||||
|
||||
"actions": []
|
||||
"experimental.rendering.software": false
|
||||
})" };
|
||||
|
||||
const std::string smallGlobalsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
"actions": []
|
||||
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}"
|
||||
})" };
|
||||
|
||||
RoundtripTest<implementation::GlobalAppSettings>(globalsString);
|
||||
@@ -182,9 +178,7 @@ namespace SettingsModelLocalTests
|
||||
// - null should be acceptable even though we're working with colors
|
||||
const std::string weirdProfileString{ R"(
|
||||
{
|
||||
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
|
||||
"name": "Weird Profile",
|
||||
"hidden": false,
|
||||
"tabColor": null,
|
||||
"foreground": null,
|
||||
"source": "local"
|
||||
@@ -227,172 +221,6 @@ namespace SettingsModelLocalTests
|
||||
RoundtripTest<implementation::ColorScheme>(schemeString);
|
||||
}
|
||||
|
||||
void SerializationTests::Actions()
|
||||
{
|
||||
// simple command
|
||||
const std::string actionsString1{ R"([
|
||||
{ "command": "paste" }
|
||||
])" };
|
||||
|
||||
// complex command
|
||||
const std::string actionsString2A{ R"([
|
||||
{ "command": { "action": "setTabColor" } }
|
||||
])" };
|
||||
const std::string actionsString2B{ R"([
|
||||
{ "command": { "action": "setTabColor", "color": "#112233" } }
|
||||
])" };
|
||||
const std::string actionsString2C{ R"([
|
||||
{ "command": { "action": "copy" } },
|
||||
{ "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" } }
|
||||
])" };
|
||||
|
||||
// simple command with key chords
|
||||
const std::string actionsString3{ R"([
|
||||
{ "command": "toggleAlwaysOnTop", "keys": "ctrl+a" },
|
||||
{ "command": "toggleAlwaysOnTop", "keys": "ctrl+b" }
|
||||
])" };
|
||||
|
||||
// complex command with key chords
|
||||
const std::string actionsString4{ R"([
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" },
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
|
||||
])" };
|
||||
|
||||
// command with name and icon and multiple key chords
|
||||
const std::string actionsString5{ R"([
|
||||
{ "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "keys": "ctrl+e" },
|
||||
{ "command": "scrollToTop", "keys": "ctrl+f" }
|
||||
])" };
|
||||
|
||||
// complex command with new terminal args
|
||||
const std::string actionsString6{ R"([
|
||||
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+g" },
|
||||
])" };
|
||||
|
||||
// complex command with meaningful null arg
|
||||
const std::string actionsString7{ R"([
|
||||
{ "command": { "action": "renameWindow", "name": null }, "keys": "ctrl+h" }
|
||||
])" };
|
||||
|
||||
// nested command
|
||||
const std::string actionsString8{ R"([
|
||||
{
|
||||
"name": "Change font size...",
|
||||
"commands": [
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 } },
|
||||
{ "command": { "action": "adjustFontSize", "delta": -1 } },
|
||||
{ "command": "resetFontSize" },
|
||||
]
|
||||
}
|
||||
])" };
|
||||
|
||||
// iterable command
|
||||
const std::string actionsString9A{ R"([
|
||||
{
|
||||
"name": "New tab",
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": "${profile.name}",
|
||||
"command": { "action": "newTab", "profile": "${profile.name}" }
|
||||
}
|
||||
]
|
||||
}
|
||||
])" };
|
||||
const std::string actionsString9B{ R"([
|
||||
{
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"command":
|
||||
{
|
||||
"action": "sendInput",
|
||||
"input": "${profile.name}"
|
||||
},
|
||||
"iterateOn": "profiles"
|
||||
}
|
||||
],
|
||||
"name": "Send Input ..."
|
||||
}
|
||||
])" };
|
||||
const std::string actionsString9C{ R""([
|
||||
{
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"command":
|
||||
{
|
||||
"action": "sendInput",
|
||||
"input": "${profile.name} ${scheme.name}"
|
||||
},
|
||||
"iterateOn": "schemes"
|
||||
}
|
||||
],
|
||||
"iterateOn": "profiles",
|
||||
"name": "nest level (${profile.name})"
|
||||
}
|
||||
],
|
||||
"name": "Send Input (Evil) ..."
|
||||
}
|
||||
])"" };
|
||||
const std::string actionsString9D{ R""([
|
||||
{
|
||||
"command":
|
||||
{
|
||||
"action": "newTab",
|
||||
"profile": "${profile.name}"
|
||||
},
|
||||
"icon": "${profile.icon}",
|
||||
"iterateOn": "profiles",
|
||||
"name": "${profile.name}: New tab"
|
||||
}
|
||||
])"" };
|
||||
|
||||
// unbound command
|
||||
const std::string actionsString10{ R"([
|
||||
{ "command": "unbound", "keys": "ctrl+c" }
|
||||
])" };
|
||||
|
||||
Log::Comment(L"simple command");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString1);
|
||||
|
||||
Log::Comment(L"complex commands");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString2A);
|
||||
RoundtripTest<implementation::ActionMap>(actionsString2B);
|
||||
RoundtripTest<implementation::ActionMap>(actionsString2C);
|
||||
|
||||
Log::Comment(L"simple command with key chords");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString3);
|
||||
|
||||
Log::Comment(L"complex commands with key chords");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString4);
|
||||
|
||||
Log::Comment(L"command with name and icon and multiple key chords");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString5);
|
||||
|
||||
Log::Comment(L"complex command with new terminal args");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString6);
|
||||
|
||||
Log::Comment(L"complex command with meaningful null arg");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString7);
|
||||
|
||||
Log::Comment(L"nested command");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString8);
|
||||
|
||||
Log::Comment(L"iterable command");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString9A);
|
||||
RoundtripTest<implementation::ActionMap>(actionsString9B);
|
||||
RoundtripTest<implementation::ActionMap>(actionsString9C);
|
||||
RoundtripTest<implementation::ActionMap>(actionsString9D);
|
||||
|
||||
Log::Comment(L"unbound command");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString10);
|
||||
}
|
||||
|
||||
void SerializationTests::CascadiaSettings()
|
||||
{
|
||||
const std::string settingsString{ R"({
|
||||
@@ -449,9 +277,10 @@ namespace SettingsModelLocalTests
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{ "command": { "action": "renameTab", "title": "Liang Tab" }, "keys": "ctrl+t" },
|
||||
{ "command": { "action": "sendInput", "input": "VT Griese Mode" }, "keys": "ctrl+k" },
|
||||
{ "command": { "action": "renameWindow", "name": "Hecker Window" }, "keys": "ctrl+l" }
|
||||
{"command": { "action": "renameTab","input": "Liang Tab" },"keys": "ctrl+t" }
|
||||
],
|
||||
"keybindings": [
|
||||
{ "command": { "action": "sendInput","input": "VT Griese Mode" },"keys": "ctrl+k" }
|
||||
]
|
||||
})" };
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<ProjectName>LocalTests_SettingsModel</ProjectName>
|
||||
<TargetName>SettingsModel.LocalTests</TargetName>
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<WindowsTargetPlatformMinVersion>10.0.18362.0</WindowsTargetPlatformMinVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
|
||||
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -38,7 +40,6 @@
|
||||
<ClCompile Include="CommandTests.cpp" />
|
||||
<ClCompile Include="DeserializationTests.cpp" />
|
||||
<ClCompile Include="SerializationTests.cpp" />
|
||||
<ClCompile Include="TerminalSettingsTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -53,11 +54,10 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\Microsoft.Terminal.Settings.ModelLib.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\propslib\propslib.vcxproj" />
|
||||
|
||||
<!-- If you don't reference these projects here, the
|
||||
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
|
||||
</ItemGroup>
|
||||
@@ -75,13 +75,6 @@
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<!--
|
||||
SettingsModelLib contains a DllMain that we need to force the use of.
|
||||
If you don't have this, then you'll see an error like
|
||||
"(init.obj) : error LNK2005: DllMain already defined in MSVCRTD.lib(dll_dllmain_stub.obj)"
|
||||
-->
|
||||
<AdditionalOptions Condition="'$(Platform)'=='Win32'">/INCLUDE:_DllMain@12</AdditionalOptions>
|
||||
<AdditionalOptions Condition="'$(Platform)'!='Win32'">/INCLUDE:DllMain</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
|
||||
@@ -1,557 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalSettingsModel/CascadiaSettings.h"
|
||||
#include "../TerminalSettingsModel/TerminalSettings.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
|
||||
namespace SettingsModelLocalTests
|
||||
{
|
||||
// TODO:microsoft/terminal#3838:
|
||||
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
|
||||
// an updated TAEF that will let us install framework packages when the test
|
||||
// package is deployed. Until then, these tests won't deploy in CI.
|
||||
|
||||
class TerminalSettingsTests
|
||||
{
|
||||
// Use a custom AppxManifest to ensure that we can activate winrt types
|
||||
// from our test. This property will tell taef to manually use this as
|
||||
// the AppxManifest for this test class.
|
||||
// This does not yet work for anything XAML-y. See TabTests.cpp for more
|
||||
// details on that.
|
||||
BEGIN_TEST_CLASS(TerminalSettingsTests)
|
||||
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(TryCreateWinRTType);
|
||||
|
||||
TEST_METHOD(TestTerminalArgsForBinding);
|
||||
|
||||
TEST_METHOD(MakeSettingsForProfileThatDoesntExist);
|
||||
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
|
||||
|
||||
TEST_METHOD(TestLayerProfileOnColorScheme);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void TerminalSettingsTests::TryCreateWinRTType()
|
||||
{
|
||||
TerminalSettings settings;
|
||||
VERIFY_IS_NOT_NULL(settings);
|
||||
auto oldFontSize = settings.FontSize();
|
||||
settings.FontSize(oldFontSize + 5);
|
||||
auto newFontSize = settings.FontSize();
|
||||
VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize);
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::TestTerminalArgsForBinding()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile1",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"commandline": "pwsh.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile2",
|
||||
"historySize": 3,
|
||||
"commandline": "wsl.exe"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "vertical", "profile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" } },
|
||||
{ "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile1" } },
|
||||
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile2" } },
|
||||
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal", "commandline": "foo.exe" } },
|
||||
{ "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "horizontal", "profile": "profile1", "commandline": "foo.exe" } },
|
||||
{ "keys": ["ctrl+g"], "command": { "action": "newTab" } },
|
||||
{ "keys": ["ctrl+h"], "command": { "action": "newTab", "startingDirectory": "c:\\foo" } },
|
||||
{ "keys": ["ctrl+i"], "command": { "action": "newTab", "profile": "profile2", "startingDirectory": "c:\\foo" } },
|
||||
{ "keys": ["ctrl+j"], "command": { "action": "newTab", "tabTitle": "bar" } },
|
||||
{ "keys": ["ctrl+k"], "command": { "action": "newTab", "profile": "profile2", "tabTitle": "bar" } },
|
||||
{ "keys": ["ctrl+l"], "command": { "action": "newTab", "profile": "profile1", "tabTitle": "bar", "startingDirectory": "c:\\foo", "commandline":"foo.exe" } }
|
||||
]
|
||||
})" };
|
||||
|
||||
const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") };
|
||||
const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") };
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
auto actionMap = settings.GlobalSettings().ActionMap();
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid();
|
||||
VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid);
|
||||
|
||||
const auto& actionMapImpl{ winrt::get_self<implementation::ActionMap>(actionMap) };
|
||||
VERIFY_ARE_EQUAL(12u, actionMapImpl->_KeyMap.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('B') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('J') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('K') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('L') };
|
||||
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::MakeSettingsForProfileThatDoesntExist()
|
||||
{
|
||||
// Test that making settings throws when the GUID doesn't exist
|
||||
const std::string settingsString{ 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 settings{ til::u8u16(settingsString) };
|
||||
|
||||
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid1, nullptr) };
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(1, terminalSettings.DefaultSettings().HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid2, nullptr) };
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(2, terminalSettings.DefaultSettings().HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed");
|
||||
}
|
||||
|
||||
VERIFY_THROWS(auto terminalSettings = TerminalSettings::CreateWithProfileByID(settings, guid3, nullptr), wil::ResultException, L"This call to constructor should fail");
|
||||
|
||||
try
|
||||
{
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) };
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.DefaultSettings().HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to CreateWithNewTerminalArgs should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::MakeSettingsForDefaultProfileThatDoesntExist()
|
||||
{
|
||||
// Test that MakeSettings _doesnt_ throw when we load settings with a
|
||||
// defaultProfile that's not in the list, we validate the settings, and
|
||||
// then call MakeSettings(nullopt). The validation should ensure that
|
||||
// the default profile is something reasonable
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-3333-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 settings{ til::u8u16(settingsString) };
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings.ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(settings.GlobalSettings().DefaultProfile(), settings.ActiveProfiles().GetAt(0).Guid());
|
||||
try
|
||||
{
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) };
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.DefaultSettings().HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to CreateWithNewTerminalArgs should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::TestLayerProfileOnColorScheme()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly."));
|
||||
|
||||
const std::string settings0String{ R"(
|
||||
{
|
||||
"defaultProfile": "profile5",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"colorScheme": "schemeWithCursorColor"
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"colorScheme": "schemeWithoutCursorColor"
|
||||
},
|
||||
{
|
||||
"name" : "profile2",
|
||||
"colorScheme": "schemeWithCursorColor",
|
||||
"cursorColor": "#234567"
|
||||
},
|
||||
{
|
||||
"name" : "profile3",
|
||||
"colorScheme": "schemeWithoutCursorColor",
|
||||
"cursorColor": "#345678"
|
||||
},
|
||||
{
|
||||
"name" : "profile4",
|
||||
"cursorColor": "#456789"
|
||||
},
|
||||
{
|
||||
"name" : "profile5"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
{
|
||||
"name": "schemeWithCursorColor",
|
||||
"cursorColor": "#123456"
|
||||
},
|
||||
{
|
||||
"name": "schemeWithoutCursorColor"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settings0String) };
|
||||
|
||||
VERIFY_ARE_EQUAL(6u, settings.ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings.GlobalSettings().ColorSchemes().Size());
|
||||
|
||||
auto createTerminalSettings = [&](const auto& profile, const auto& schemes) {
|
||||
auto terminalSettings{ winrt::make_self<implementation::TerminalSettings>() };
|
||||
terminalSettings->_ApplyProfileSettings(profile);
|
||||
terminalSettings->_ApplyAppearanceSettings(profile.DefaultAppearance(), schemes);
|
||||
return terminalSettings;
|
||||
};
|
||||
|
||||
auto terminalSettings0 = createTerminalSettings(settings.ActiveProfiles().GetAt(0), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings1 = createTerminalSettings(settings.ActiveProfiles().GetAt(1), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings2 = createTerminalSettings(settings.ActiveProfiles().GetAt(2), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings3 = createTerminalSettings(settings.ActiveProfiles().GetAt(3), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings4 = createTerminalSettings(settings.ActiveProfiles().GetAt(4), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings5 = createTerminalSettings(settings.ActiveProfiles().GetAt(5), settings.GlobalSettings().ColorSchemes());
|
||||
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme
|
||||
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x23, 0x45, 0x67), terminalSettings2->CursorColor()); // from profile (trumps color scheme)
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x34, 0x56, 0x78), terminalSettings3->CursorColor()); // from profile (not set in color scheme)
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4->CursorColor()); // from profile (no color scheme)
|
||||
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5->CursorColor()); // default
|
||||
}
|
||||
}
|
||||
@@ -19,31 +19,31 @@ public:
|
||||
// - This is a helper to retrieve the ActionAndArgs from the keybindings
|
||||
// for a given chord.
|
||||
// Arguments:
|
||||
// - actionMap: The ActionMap to lookup the ActionAndArgs from.
|
||||
// - keymap: 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::Microsoft::Terminal::Settings::Model::ActionAndArgs GetActionAndArgs(const winrt::Microsoft::Terminal::Settings::Model::ActionMap& actionMap,
|
||||
const winrt::Microsoft::Terminal::Control::KeyChord& kc)
|
||||
static const winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs GetActionAndArgs(const winrt::Microsoft::Terminal::Settings::Model::KeyMapping& keymap,
|
||||
const winrt::Microsoft::Terminal::TerminalControl::KeyChord& kc)
|
||||
{
|
||||
std::wstring buffer{ L"" };
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Control::KeyModifiers::Ctrl))
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Ctrl))
|
||||
{
|
||||
buffer += L"Ctrl+";
|
||||
}
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Control::KeyModifiers::Shift))
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Shift))
|
||||
{
|
||||
buffer += L"Shift+";
|
||||
}
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Control::KeyModifiers::Alt))
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::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 cmd = actionMap.GetActionByKeyChord(kc);
|
||||
VERIFY_IS_NOT_NULL(cmd, L"Expected to find an action bound to the given KeyChord");
|
||||
return cmd.ActionAndArgs();
|
||||
const auto action = keymap.TryLookup(kc);
|
||||
VERIFY_IS_NOT_NULL(action, L"Expected to find an action bound to the given KeyChord");
|
||||
return action;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -56,8 +56,6 @@ Author(s):
|
||||
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
|
||||
#include <winrt/Microsoft.Terminal.Core.h>
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(ParseFocusTabArgs);
|
||||
TEST_METHOD(ParseMoveFocusArgs);
|
||||
TEST_METHOD(ParseArgumentsWithParsingTerminators);
|
||||
TEST_METHOD(ParseFocusPaneArgs);
|
||||
|
||||
TEST_METHOD(ParseNoCommandIsNewTab);
|
||||
|
||||
@@ -459,7 +458,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor());
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -481,7 +479,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex());
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"cmd", myArgs.TerminalArgs().Profile());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -503,7 +500,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"c:\\Foo", myArgs.TerminalArgs().StartingDirectory());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -525,7 +521,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -548,7 +543,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
|
||||
auto myCommand = myArgs.TerminalArgs().Commandline();
|
||||
VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", myCommand);
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -571,7 +565,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
|
||||
auto myCommand = myArgs.TerminalArgs().Commandline();
|
||||
VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\" another-arg \"more spaces in this one\"", myCommand);
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -593,7 +586,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex());
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"Windows PowerShell", myArgs.TerminalArgs().Profile());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -614,7 +606,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -637,7 +628,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline());
|
||||
VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -661,31 +651,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(til::color(myArgs.TerminalArgs().TabColor().Value()), expectedColor);
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"--colorScheme", L"Vintage" };
|
||||
const winrt::hstring expectedScheme{ L"Vintage" };
|
||||
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.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_NULL(myArgs.TerminalArgs().TabColor());
|
||||
VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
VERIFY_ARE_EQUAL(expectedScheme, myArgs.TerminalArgs().ColorScheme());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,7 +680,6 @@ namespace TerminalAppLocalTests
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
{
|
||||
@@ -735,7 +699,6 @@ namespace TerminalAppLocalTests
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
{
|
||||
@@ -757,28 +720,6 @@ namespace TerminalAppLocalTests
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-D" };
|
||||
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
|
||||
VERIFY_ARE_EQUAL(1u, commandlines.size());
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
// The one we actually want to test here is the SplitPane action we created
|
||||
auto actionAndArgs = appArgs._startupActions.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_ARE_EQUAL(SplitType::Duplicate, myArgs.SplitMode());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
{
|
||||
@@ -808,7 +749,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline());
|
||||
VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -837,7 +777,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline());
|
||||
VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -866,7 +805,6 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"wsl -d Alpine -H", myArgs.TerminalArgs().Commandline());
|
||||
VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile());
|
||||
VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1207,108 +1145,6 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::ParseFocusPaneArgs()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `fp` instead of `focus-pane`");
|
||||
const wchar_t* subcommand = useShortForm ? L"fp" : L"focus-pane";
|
||||
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Just the subcommand, without a target, should fail."));
|
||||
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left" };
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"focus-pane without a target should fail."));
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"1" };
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"focus-pane without a target should fail."));
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"--target", L"0" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::FocusPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<FocusPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(0u, myArgs.Id());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-t", L"100" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::FocusPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<FocusPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(100u, myArgs.Id());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"--target", L"-1" };
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"focus-pane with an invalid target should fail."));
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"move-focus", L"left", L";", subcommand, L"-t", L"1" };
|
||||
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
{
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.FocusDirection());
|
||||
}
|
||||
{
|
||||
auto actionAndArgs = appArgs._startupActions.at(2);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::FocusPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<FocusPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(1u, myArgs.Id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::ValidateFirstCommandIsNewTab()
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -1758,155 +1594,59 @@ namespace TerminalAppLocalTests
|
||||
void CommandlineTest::TestFindTargetWindow()
|
||||
{
|
||||
{
|
||||
Log::Comment(L"wt.exe with no args should always use the value from"
|
||||
L" the settings (passed as the second argument).");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseExisting, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseExisting,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseAnyExisting, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseAnyExisting,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"-w -1 should always result in a new window");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe", L"-w", L"-1" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"\"new\" should always result in a new window");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe", L"-w", L"new" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"-w with a negative number should always result in a "
|
||||
L"new window");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe", L"-w", L"-12345" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"-w with a positive number should result in us trying"
|
||||
L" to either make a new one or find an existing one "
|
||||
L"with that ID, depending on the provided argument");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe", L"-w", L"12345" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(12345, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(12345, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(12345, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"-w 0 should always use the \"current\" window");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe", L"-w", L"0" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"-w last should always use the most recent window on "
|
||||
L"this desktop");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe", L"-w", L"last" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseExisting, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseExisting, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseExisting, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Make sure we follow the provided argument when a "
|
||||
L"--window-id wasn't explicitly provided");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe", L"new-tab" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseExisting, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseExisting,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseAnyExisting, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Even if someone uses a subcommand as a window name, "
|
||||
L"that should work");
|
||||
|
||||
std::vector<winrt::hstring> args{ L"wt.exe", L"-w", L"new-tab" };
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseName, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"new-tab", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseName, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"new-tab", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseName, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"new-tab", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseAnyExisting,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1917,23 +1657,19 @@ namespace TerminalAppLocalTests
|
||||
// This is a little helper to make sure that these args _always_ return
|
||||
// UseNew, regardless of the windowing behavior.
|
||||
auto testHelper = [](auto&& args) {
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting));
|
||||
};
|
||||
|
||||
testHelper(std::vector<winrt::hstring>{ L"wt.exe", L"--help" });
|
||||
testHelper(std::vector<winrt::hstring>{ L"wt.exe", L"new-tab", L"--help" });
|
||||
testHelper(std::vector<winrt::hstring>{ L"wt.exe", L"-w", L"0", L"new-tab", L"--help" });
|
||||
testHelper(std::vector<winrt::hstring>{ L"wt.exe", L"-w", L"foo", L"new-tab", L"--help" });
|
||||
testHelper(std::vector<winrt::hstring>{ L"wt.exe", L"new-tab", L";", L"--help" });
|
||||
}
|
||||
|
||||
@@ -1944,17 +1680,14 @@ namespace TerminalAppLocalTests
|
||||
// This is a little helper to make sure that these args _always_ return
|
||||
// UseNew, regardless of the windowing behavior.
|
||||
auto testHelper = [](auto&& args) {
|
||||
auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting));
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseNew,
|
||||
appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting));
|
||||
};
|
||||
|
||||
testHelper(std::vector<winrt::hstring>{ L"wt.exe", L"--version" });
|
||||
|
||||
@@ -11,7 +11,7 @@ using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalApp/TerminalPage.h"
|
||||
#include "../TerminalApp/TerminalSettings.h"
|
||||
#include "../LocalTests_SettingsModel/TestUtils.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
@@ -12,7 +13,7 @@ using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
@@ -33,6 +34,15 @@ namespace TerminalAppLocalTests
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(TryCreateWinRTType);
|
||||
|
||||
TEST_METHOD(TestTerminalArgsForBinding);
|
||||
|
||||
TEST_METHOD(MakeSettingsForProfileThatDoesntExist);
|
||||
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
|
||||
|
||||
TEST_METHOD(TestLayerProfileOnColorScheme);
|
||||
|
||||
TEST_METHOD(TestIterateCommands);
|
||||
TEST_METHOD(TestIterateOnGeneratedNamedCommands);
|
||||
TEST_METHOD(TestIterateOnBadJson);
|
||||
@@ -73,6 +83,487 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
};
|
||||
|
||||
void SettingsTests::TryCreateWinRTType()
|
||||
{
|
||||
TerminalSettings settings;
|
||||
VERIFY_IS_NOT_NULL(settings);
|
||||
auto oldFontSize = settings.FontSize();
|
||||
settings.FontSize(oldFontSize + 5);
|
||||
auto newFontSize = settings.FontSize();
|
||||
VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize);
|
||||
}
|
||||
|
||||
void SettingsTests::TestTerminalArgsForBinding()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile1",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"commandline": "pwsh.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile2",
|
||||
"historySize": 3,
|
||||
"commandline": "wsl.exe"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "vertical", "profile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" } },
|
||||
{ "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile1" } },
|
||||
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile2" } },
|
||||
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal", "commandline": "foo.exe" } },
|
||||
{ "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "horizontal", "profile": "profile1", "commandline": "foo.exe" } },
|
||||
{ "keys": ["ctrl+g"], "command": { "action": "newTab" } },
|
||||
{ "keys": ["ctrl+h"], "command": { "action": "newTab", "startingDirectory": "c:\\foo" } },
|
||||
{ "keys": ["ctrl+i"], "command": { "action": "newTab", "profile": "profile2", "startingDirectory": "c:\\foo" } },
|
||||
{ "keys": ["ctrl+j"], "command": { "action": "newTab", "tabTitle": "bar" } },
|
||||
{ "keys": ["ctrl+k"], "command": { "action": "newTab", "profile": "profile2", "tabTitle": "bar" } },
|
||||
{ "keys": ["ctrl+l"], "command": { "action": "newTab", "profile": "profile1", "tabTitle": "bar", "startingDirectory": "c:\\foo", "commandline":"foo.exe" } }
|
||||
]
|
||||
})" };
|
||||
|
||||
const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") };
|
||||
const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") };
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
auto keymap = settings.GlobalSettings().KeyMap();
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid();
|
||||
VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid);
|
||||
|
||||
VERIFY_ARE_EQUAL(12u, keymap.Size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, 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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('B') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, 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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, 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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, 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(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, 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(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, 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(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('J') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid0, guid);
|
||||
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('K') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(profile2Guid, guid);
|
||||
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('L') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
|
||||
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
|
||||
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr);
|
||||
VERIFY_ARE_EQUAL(guid1, guid);
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
|
||||
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::MakeSettingsForProfileThatDoesntExist()
|
||||
{
|
||||
// Test that MakeSettings throws when the GUID doesn't exist
|
||||
const std::string settingsString{ 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 settings{ til::u8u16(settingsString) };
|
||||
|
||||
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings = winrt::make<winrt::TerminalApp::implementation::TerminalSettings>(settings, guid1, nullptr);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(1, terminalSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings = winrt::make<winrt::TerminalApp::implementation::TerminalSettings>(settings, guid2, nullptr);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(2, terminalSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
|
||||
VERIFY_THROWS(auto terminalSettings = winrt::make<winrt::TerminalApp::implementation::TerminalSettings>(settings, guid3, nullptr), wil::ResultException, L"This call to BuildSettings should fail");
|
||||
|
||||
try
|
||||
{
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, nullptr, nullptr);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::MakeSettingsForDefaultProfileThatDoesntExist()
|
||||
{
|
||||
// Test that MakeSettings _doesnt_ throw when we load settings with a
|
||||
// defaultProfile that's not in the list, we validate the settings, and
|
||||
// then call MakeSettings(nullopt). The validation should ensure that
|
||||
// the default profile is something reasonable
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-3333-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 settings{ til::u8u16(settingsString) };
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings.ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(settings.GlobalSettings().DefaultProfile(), settings.ActiveProfiles().GetAt(0).Guid());
|
||||
try
|
||||
{
|
||||
const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, nullptr, nullptr);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::TestLayerProfileOnColorScheme()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly."));
|
||||
|
||||
const std::string settings0String{ R"(
|
||||
{
|
||||
"defaultProfile": "profile5",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"colorScheme": "schemeWithCursorColor"
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"colorScheme": "schemeWithoutCursorColor"
|
||||
},
|
||||
{
|
||||
"name" : "profile2",
|
||||
"colorScheme": "schemeWithCursorColor",
|
||||
"cursorColor": "#234567"
|
||||
},
|
||||
{
|
||||
"name" : "profile3",
|
||||
"colorScheme": "schemeWithoutCursorColor",
|
||||
"cursorColor": "#345678"
|
||||
},
|
||||
{
|
||||
"name" : "profile4",
|
||||
"cursorColor": "#456789"
|
||||
},
|
||||
{
|
||||
"name" : "profile5"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
{
|
||||
"name": "schemeWithCursorColor",
|
||||
"cursorColor": "#123456"
|
||||
},
|
||||
{
|
||||
"name": "schemeWithoutCursorColor"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settings0String) };
|
||||
|
||||
VERIFY_ARE_EQUAL(6u, settings.ActiveProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(2u, settings.GlobalSettings().ColorSchemes().Size());
|
||||
|
||||
auto createTerminalSettings = [&](const auto& profile, const auto& schemes) {
|
||||
auto terminalSettings{ winrt::make_self<winrt::TerminalApp::implementation::TerminalSettings>() };
|
||||
terminalSettings->_ApplyProfileSettings(profile, schemes);
|
||||
return terminalSettings;
|
||||
};
|
||||
|
||||
auto terminalSettings0 = createTerminalSettings(settings.ActiveProfiles().GetAt(0), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings1 = createTerminalSettings(settings.ActiveProfiles().GetAt(1), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings2 = createTerminalSettings(settings.ActiveProfiles().GetAt(2), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings3 = createTerminalSettings(settings.ActiveProfiles().GetAt(3), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings4 = createTerminalSettings(settings.ActiveProfiles().GetAt(4), settings.GlobalSettings().ColorSchemes());
|
||||
auto terminalSettings5 = createTerminalSettings(settings.ActiveProfiles().GetAt(5), settings.GlobalSettings().ColorSchemes());
|
||||
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme
|
||||
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x23, 0x45, 0x67), terminalSettings2->CursorColor()); // from profile (trumps color scheme)
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x34, 0x56, 0x78), terminalSettings3->CursorColor()); // from profile (not set in color scheme)
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4->CursorColor()); // from profile (no color scheme)
|
||||
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5->CursorColor()); // default
|
||||
}
|
||||
|
||||
void SettingsTests::TestIterateCommands()
|
||||
{
|
||||
// For this test, put an iterable command with a given `name`,
|
||||
@@ -120,13 +611,13 @@ namespace TerminalAppLocalTests
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto nameMap{ settings.ActionMap().NameMap() };
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = nameMap.TryLookup(L"iterable command ${profile.name}");
|
||||
auto command = commands.Lookup(L"iterable command ${profile.name}");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -141,7 +632,7 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
@@ -150,7 +641,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -168,7 +659,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -186,7 +677,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -247,13 +738,13 @@ namespace TerminalAppLocalTests
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto nameMap{ settings.ActionMap().NameMap() };
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = nameMap.TryLookup(L"Split pane, profile: ${profile.name}");
|
||||
auto command = commands.Lookup(L"Split pane, profile: ${profile.name}");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -268,7 +759,7 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
@@ -277,7 +768,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -295,7 +786,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -313,7 +804,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -376,13 +867,13 @@ namespace TerminalAppLocalTests
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto nameMap{ settings.ActionMap().NameMap() };
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = nameMap.TryLookup(L"iterable command ${profile.name}");
|
||||
auto command = commands.Lookup(L"iterable command ${profile.name}");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -397,7 +888,7 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
@@ -406,7 +897,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -424,7 +915,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile1\"");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -442,7 +933,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -513,7 +1004,8 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
@@ -521,9 +1013,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"Connect to ssh...");
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
|
||||
auto rootActionAndArgs = rootCommand.Action();
|
||||
VERIFY_IS_NULL(rootActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, rootCommand.NestedCommands().Size());
|
||||
|
||||
@@ -531,7 +1022,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring commandName{ L"first.com" };
|
||||
auto command = rootCommand.NestedCommands().Lookup(commandName);
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
||||
VERIFY_IS_FALSE(command.HasNestedCommands());
|
||||
@@ -540,7 +1031,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring commandName{ L"second.com" };
|
||||
auto command = rootCommand.NestedCommands().Lookup(commandName);
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
||||
VERIFY_IS_FALSE(command.HasNestedCommands());
|
||||
@@ -608,7 +1099,8 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
@@ -616,25 +1108,23 @@ namespace TerminalAppLocalTests
|
||||
|
||||
auto grandparentCommand = expandedCommands.Lookup(L"grandparent");
|
||||
VERIFY_IS_NOT_NULL(grandparentCommand);
|
||||
auto grandparentActionAndArgs = grandparentCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(grandparentActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, grandparentActionAndArgs.Action());
|
||||
auto grandparentActionAndArgs = grandparentCommand.Action();
|
||||
VERIFY_IS_NULL(grandparentActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, grandparentCommand.NestedCommands().Size());
|
||||
|
||||
winrt::hstring parentName{ L"parent" };
|
||||
auto parent = grandparentCommand.NestedCommands().Lookup(parentName);
|
||||
VERIFY_IS_NOT_NULL(parent);
|
||||
auto parentActionAndArgs = parent.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(parentActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, parentActionAndArgs.Action());
|
||||
auto parentActionAndArgs = parent.Action();
|
||||
VERIFY_IS_NULL(parentActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, parent.NestedCommands().Size());
|
||||
{
|
||||
winrt::hstring childName{ L"child1" };
|
||||
auto child = parent.NestedCommands().Lookup(childName);
|
||||
VERIFY_IS_NOT_NULL(child);
|
||||
auto childActionAndArgs = child.ActionAndArgs();
|
||||
auto childActionAndArgs = child.Action();
|
||||
VERIFY_IS_NOT_NULL(childActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, childActionAndArgs.Action());
|
||||
@@ -654,7 +1144,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring childName{ L"child2" };
|
||||
auto child = parent.NestedCommands().Lookup(childName);
|
||||
VERIFY_IS_NOT_NULL(child);
|
||||
auto childActionAndArgs = child.ActionAndArgs();
|
||||
auto childActionAndArgs = child.Action();
|
||||
VERIFY_IS_NOT_NULL(childActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, childActionAndArgs.Action());
|
||||
@@ -732,7 +1222,8 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
@@ -744,9 +1235,8 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring commandName{ name + L"..." };
|
||||
auto command = expandedCommands.Lookup(commandName);
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, actionAndArgs.Action());
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NULL(actionAndArgs);
|
||||
|
||||
VERIFY_IS_TRUE(command.HasNestedCommands());
|
||||
VERIFY_ARE_EQUAL(3u, command.NestedCommands().Size());
|
||||
@@ -755,7 +1245,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
auto childActionAndArgs = childCommand.Action();
|
||||
VERIFY_IS_NOT_NULL(childActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action());
|
||||
@@ -776,7 +1266,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: horizontal, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
auto childActionAndArgs = childCommand.Action();
|
||||
VERIFY_IS_NOT_NULL(childActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action());
|
||||
@@ -797,7 +1287,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: vertical, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
auto childActionAndArgs = childCommand.Action();
|
||||
VERIFY_IS_NOT_NULL(childActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action());
|
||||
@@ -869,7 +1359,8 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
@@ -877,9 +1368,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"New Tab With Profile...");
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
|
||||
auto rootActionAndArgs = rootCommand.Action();
|
||||
VERIFY_IS_NULL(rootActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, rootCommand.NestedCommands().Size());
|
||||
|
||||
@@ -888,7 +1378,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring commandName{ fmt::format(L"New tab, profile: {}", name) };
|
||||
auto command = rootCommand.NestedCommands().Lookup(commandName);
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
@@ -972,7 +1462,8 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
@@ -980,9 +1471,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"New Pane...");
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
|
||||
auto rootActionAndArgs = rootCommand.Action();
|
||||
VERIFY_IS_NULL(rootActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, rootCommand.NestedCommands().Size());
|
||||
|
||||
@@ -991,9 +1481,8 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring commandName{ name + L"..." };
|
||||
auto command = rootCommand.NestedCommands().Lookup(commandName);
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, actionAndArgs.Action());
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NULL(actionAndArgs);
|
||||
|
||||
VERIFY_IS_TRUE(command.HasNestedCommands());
|
||||
VERIFY_ARE_EQUAL(3u, command.NestedCommands().Size());
|
||||
@@ -1003,7 +1492,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
auto childActionAndArgs = childCommand.Action();
|
||||
VERIFY_IS_NOT_NULL(childActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action());
|
||||
@@ -1024,7 +1513,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: horizontal, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
auto childActionAndArgs = childCommand.Action();
|
||||
VERIFY_IS_NOT_NULL(childActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action());
|
||||
@@ -1045,7 +1534,7 @@ namespace TerminalAppLocalTests
|
||||
winrt::hstring childCommandName{ fmt::format(L"Split pane, split: vertical, profile: {}", name) };
|
||||
auto childCommand = command.NestedCommands().Lookup(childCommandName);
|
||||
VERIFY_IS_NOT_NULL(childCommand);
|
||||
auto childActionAndArgs = childCommand.ActionAndArgs();
|
||||
auto childActionAndArgs = childCommand.Action();
|
||||
VERIFY_IS_NOT_NULL(childActionAndArgs);
|
||||
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action());
|
||||
@@ -1115,13 +1604,13 @@ namespace TerminalAppLocalTests
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto nameMap{ settings.ActionMap().NameMap() };
|
||||
VERIFY_ARE_EQUAL(1u, nameMap.Size());
|
||||
auto commands = settings.GlobalSettings().Commands();
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = nameMap.TryLookup(L"iterable command ${scheme.name}");
|
||||
auto command = commands.Lookup(L"iterable command ${scheme.name}");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -1136,7 +1625,7 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"${scheme.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
|
||||
// This is the same warning as above
|
||||
@@ -1150,7 +1639,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command scheme_0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -1168,7 +1657,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command scheme_1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
@@ -1186,7 +1675,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command scheme_2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
|
||||
@@ -85,14 +85,6 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(NextMRUTab);
|
||||
TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder);
|
||||
|
||||
TEST_METHOD(TestWindowRenameSuccessful);
|
||||
TEST_METHOD(TestWindowRenameFailure);
|
||||
|
||||
TEST_METHOD(TestControlSettingsHasParent);
|
||||
TEST_METHOD(TestPreviewCommitScheme);
|
||||
TEST_METHOD(TestPreviewDismissScheme);
|
||||
TEST_METHOD(TestPreviewSchemeWhilePreviewing);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
return true;
|
||||
@@ -268,15 +260,6 @@ namespace TerminalAppLocalTests
|
||||
page->Create();
|
||||
Log::Comment(L"Create()'d the page successfully");
|
||||
|
||||
// Build a NewTab action, to make sure we start with one. The real
|
||||
// Terminal will always get one from AppCommandlineArgs.
|
||||
NewTerminalArgs newTerminalArgs{};
|
||||
NewTabArgs args{ newTerminalArgs };
|
||||
ActionAndArgs newTabAction{ ShortcutAction::NewTab, args };
|
||||
// push the arg onto the front
|
||||
page->_startupActions.Append(newTabAction);
|
||||
Log::Comment(L"Added a single newTab action");
|
||||
|
||||
auto app = ::winrt::Windows::UI::Xaml::Application::Current();
|
||||
|
||||
winrt::TerminalApp::TerminalPage pp = *page;
|
||||
@@ -293,10 +276,9 @@ namespace TerminalAppLocalTests
|
||||
// In the real app, this isn't a problem, but doesn't happen
|
||||
// reliably in the unit tests.
|
||||
Log::Comment(L"Ensure we set the first tab as the selected one.");
|
||||
auto tab = page->_tabs.GetAt(0);
|
||||
auto tabImpl = page->_GetTerminalTabImpl(tab);
|
||||
page->_tabView.SelectedItem(tabImpl->TabViewItem());
|
||||
page->_UpdatedSelectedTab(tab);
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
page->_tabView.SelectedItem(tab->TabViewItem());
|
||||
page->_UpdatedSelectedTab(0);
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
@@ -348,7 +330,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
// * 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 _DuplicateFocusedTab on tab 1
|
||||
// * 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
|
||||
@@ -410,7 +392,7 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Duplicate the first tab");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateFocusedTab();
|
||||
page->_DuplicateTabViewItem();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
@@ -425,7 +407,7 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Duplicate the tab, and don't crash");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateFocusedTab();
|
||||
page->_DuplicateTabViewItem();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
@@ -583,75 +565,6 @@ namespace TerminalAppLocalTests
|
||||
"tabTitle" : "Profile 3",
|
||||
"historySize": 4
|
||||
}
|
||||
],
|
||||
"schemes":
|
||||
[
|
||||
{
|
||||
"name": "Campbell",
|
||||
"foreground": "#CCCCCC",
|
||||
"background": "#0C0C0C",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"black": "#0C0C0C",
|
||||
"red": "#C50F1F",
|
||||
"green": "#13A10E",
|
||||
"yellow": "#C19C00",
|
||||
"blue": "#0037DA",
|
||||
"purple": "#881798",
|
||||
"cyan": "#3A96DD",
|
||||
"white": "#CCCCCC",
|
||||
"brightBlack": "#767676",
|
||||
"brightRed": "#E74856",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightWhite": "#F2F2F2"
|
||||
},
|
||||
{
|
||||
"name": "Vintage",
|
||||
"foreground": "#C0C0C0",
|
||||
"background": "#000000",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"black": "#000000",
|
||||
"red": "#800000",
|
||||
"green": "#008000",
|
||||
"yellow": "#808000",
|
||||
"blue": "#000080",
|
||||
"purple": "#800080",
|
||||
"cyan": "#008080",
|
||||
"white": "#C0C0C0",
|
||||
"brightBlack": "#808080",
|
||||
"brightRed": "#FF0000",
|
||||
"brightGreen": "#00FF00",
|
||||
"brightYellow": "#FFFF00",
|
||||
"brightBlue": "#0000FF",
|
||||
"brightPurple": "#FF00FF",
|
||||
"brightCyan": "#00FFFF",
|
||||
"brightWhite": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"name": "One Half Light",
|
||||
"foreground": "#383A42",
|
||||
"background": "#FAFAFA",
|
||||
"cursorColor": "#4F525D",
|
||||
"black": "#383A42",
|
||||
"red": "#E45649",
|
||||
"green": "#50A14F",
|
||||
"yellow": "#C18301",
|
||||
"blue": "#0184BC",
|
||||
"purple": "#A626A4",
|
||||
"cyan": "#0997B3",
|
||||
"white": "#FAFAFA",
|
||||
"brightBlack": "#4F525D",
|
||||
"brightRed": "#DF6C75",
|
||||
"brightGreen": "#98C379",
|
||||
"brightYellow": "#E4C07A",
|
||||
"brightBlue": "#61AFEF",
|
||||
"brightPurple": "#C577DD",
|
||||
"brightCyan": "#56B5C1",
|
||||
"brightWhite": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
@@ -688,6 +601,7 @@ namespace TerminalAppLocalTests
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
SplitPaneArgs args{ SplitType::Duplicate };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
// eventArgs.Args(args);
|
||||
page->_HandleSplitPane(nullptr, eventArgs);
|
||||
auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
|
||||
@@ -868,7 +782,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the fourth tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleNextTab(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Log::Comment(L"Sleep to let events propagate");
|
||||
@@ -889,7 +804,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the second tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleNextTab(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Log::Comment(L"Sleep to let events propagate");
|
||||
@@ -913,7 +829,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next in-order tab, which is the third tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleNextTab(nullptr, eventArgs);
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
@@ -925,7 +842,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next in-order tab, which is the fourth tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleNextTab(nullptr, eventArgs);
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
@@ -984,10 +902,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Select the tabs from 0 to 3");
|
||||
RunOnUIThread([&page]() {
|
||||
page->_UpdatedSelectedTab(page->_tabs.GetAt(0));
|
||||
page->_UpdatedSelectedTab(page->_tabs.GetAt(1));
|
||||
page->_UpdatedSelectedTab(page->_tabs.GetAt(2));
|
||||
page->_UpdatedSelectedTab(page->_tabs.GetAt(3));
|
||||
page->_UpdatedSelectedTab(0);
|
||||
page->_UpdatedSelectedTab(1);
|
||||
page->_UpdatedSelectedTab(2);
|
||||
page->_UpdatedSelectedTab(3);
|
||||
});
|
||||
|
||||
VERIFY_ARE_EQUAL(4u, page->_mruTabs.Size());
|
||||
@@ -998,7 +916,7 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the third tab");
|
||||
RunOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
page->_SelectNextTab(true);
|
||||
// In the course of a single tick, the Command Palette will:
|
||||
// * open
|
||||
// * select the proper tab from the mru's list
|
||||
@@ -1025,321 +943,4 @@ namespace TerminalAppLocalTests
|
||||
// will also dismiss itself immediately when that's called. So we can't
|
||||
// really inspect the contents of the list in this test, unfortunately.
|
||||
}
|
||||
|
||||
void TabTests::TestWindowRenameSuccessful()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
page->RenameWindowRequested([&page](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) {
|
||||
// In the real terminal, this would bounce up to the monarch and
|
||||
// come back down. Instead, immediately call back and set the name.
|
||||
page->WindowName(args.ProposedName());
|
||||
});
|
||||
|
||||
bool windowNameChanged = false;
|
||||
page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable {
|
||||
if (args.PropertyName() == L"WindowNameForDisplay")
|
||||
{
|
||||
windowNameChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
page->_RequestWindowRename(winrt::hstring{ L"Foo" });
|
||||
});
|
||||
TestOnUIThread([&]() {
|
||||
VERIFY_ARE_EQUAL(L"Foo", page->_WindowName);
|
||||
VERIFY_IS_TRUE(windowNameChanged,
|
||||
L"The window name should have changed, and we should have raised a notification that WindowNameForDisplay changed");
|
||||
});
|
||||
}
|
||||
void TabTests::TestWindowRenameFailure()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
auto page = _commonSetup();
|
||||
page->RenameWindowRequested([&page](auto&&, auto&&) {
|
||||
// In the real terminal, this would bounce up to the monarch and
|
||||
// come back down. Instead, immediately call back to tell the terminal it failed.
|
||||
page->RenameFailed();
|
||||
});
|
||||
|
||||
bool windowNameChanged = false;
|
||||
|
||||
page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable {
|
||||
if (args.PropertyName() == L"WindowNameForDisplay")
|
||||
{
|
||||
windowNameChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
page->_RequestWindowRename(winrt::hstring{ L"Foo" });
|
||||
});
|
||||
TestOnUIThread([&]() {
|
||||
VERIFY_IS_FALSE(windowNameChanged,
|
||||
L"The window name should not have changed, we should have rejected the change.");
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::TestControlSettingsHasParent()
|
||||
{
|
||||
Log::Comment(L"Ensure that when we create a control, it always has a parent TerminalSettings");
|
||||
|
||||
auto page = _commonSetup();
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& originalSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::TestPreviewCommitScheme()
|
||||
{
|
||||
Log::Comment(L"Preview a color scheme. Make sure it's applied, then committed accordingly");
|
||||
|
||||
auto page = _commonSetup();
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& originalSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Emulate previewing the SetColorScheme action");
|
||||
SetColorSchemeArgs args{ L"Vintage" };
|
||||
page->_PreviewColorScheme(args);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& previewSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(previewSettings);
|
||||
|
||||
const auto& originalSettings = previewSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
Log::Comment(L"Color should be changed to the preview");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Emulate committing the SetColorScheme action");
|
||||
|
||||
SetColorSchemeArgs args{ L"Vintage" };
|
||||
page->_EndPreviewColorScheme();
|
||||
page->_HandleSetColorScheme(nullptr, ActionEventArgs{ args });
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& originalSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
const auto& grandparentSettings = originalSettings.GetParent();
|
||||
VERIFY_IS_NULL(grandparentSettings);
|
||||
|
||||
Log::Comment(L"Color should be changed");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::TestPreviewDismissScheme()
|
||||
{
|
||||
Log::Comment(L"Preview a color scheme. Make sure it's applied, then dismissed accordingly");
|
||||
|
||||
auto page = _commonSetup();
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& originalSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Emulate previewing the SetColorScheme action");
|
||||
SetColorSchemeArgs args{ L"Vintage" };
|
||||
page->_PreviewColorScheme(args);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& previewSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(previewSettings);
|
||||
|
||||
const auto& originalSettings = previewSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
Log::Comment(L"Color should be changed to the preview");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Emulate dismissing the SetColorScheme action");
|
||||
page->_EndPreviewColorScheme();
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& originalSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
const auto& grandparentSettings = originalSettings.GetParent();
|
||||
VERIFY_IS_NULL(grandparentSettings);
|
||||
|
||||
Log::Comment(L"Color should be the same as it originally was");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
|
||||
});
|
||||
}
|
||||
|
||||
void TabTests::TestPreviewSchemeWhilePreviewing()
|
||||
{
|
||||
Log::Comment(L"Preview a color scheme, then preview another scheme. ");
|
||||
|
||||
Log::Comment(L"Preview a color scheme. Make sure it's applied, then committed accordingly");
|
||||
|
||||
auto page = _commonSetup();
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& originalSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Emulate previewing the SetColorScheme action");
|
||||
SetColorSchemeArgs args{ L"Vintage" };
|
||||
page->_PreviewColorScheme(args);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& previewSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(previewSettings);
|
||||
|
||||
const auto& originalSettings = previewSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
Log::Comment(L"Color should be changed to the preview");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Now, preview another scheme");
|
||||
SetColorSchemeArgs args{ L"One Half Light" };
|
||||
page->_PreviewColorScheme(args);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& previewSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(previewSettings);
|
||||
|
||||
const auto& originalSettings = previewSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
Log::Comment(L"Color should be changed to the preview");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Emulate committing the SetColorScheme action");
|
||||
|
||||
SetColorSchemeArgs args{ L"One Half Light" };
|
||||
page->_EndPreviewColorScheme();
|
||||
page->_HandleSetColorScheme(nullptr, ActionEventArgs{ args });
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
const auto& activeControl{ page->_GetActiveControl() };
|
||||
VERIFY_IS_NOT_NULL(activeControl);
|
||||
|
||||
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
VERIFY_IS_NOT_NULL(controlSettings);
|
||||
|
||||
const auto& originalSettings = controlSettings.GetParent();
|
||||
VERIFY_IS_NOT_NULL(originalSettings);
|
||||
|
||||
const auto& grandparentSettings = originalSettings.GetParent();
|
||||
VERIFY_IS_NULL(grandparentSettings);
|
||||
|
||||
Log::Comment(L"Color should be changed");
|
||||
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
|
||||
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.18362.0" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug" MinVersion="14.0.27023.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug.UWPDesktop" MinVersion="14.0.27027.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
</Dependencies>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 20H1 -->
|
||||
<!-- Windows 10 1903 -->
|
||||
<!-- See https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands -->
|
||||
<!-- "maxversiontested" is CASE SENSITIVE. Do not change this.-->
|
||||
<maxversiontested Id="10.0.19041.0"/>
|
||||
<maxversiontested Id="10.0.18362.0"/>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<ProjectName>LocalTests_TerminalApp</ProjectName>
|
||||
<TargetName>TerminalApp.LocalTests</TargetName>
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<WindowsTargetPlatformMinVersion>10.0.18362.0</WindowsTargetPlatformMinVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
|
||||
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -57,7 +59,7 @@
|
||||
<!-- If you don't reference these projects here, the
|
||||
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsEditor\Microsoft.Terminal.Settings.Editor.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\TerminalControl.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\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
|
||||
@@ -97,6 +99,6 @@
|
||||
|
||||
<!-- We actually can just straight up reference MUX here, it's fine -->
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
|
||||
</Project>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user