Compare commits
174 Commits
release-1.
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4f3089bec | ||
|
|
a8ced8f4b4 | ||
|
|
df5f9733ce | ||
|
|
fb734d166c | ||
|
|
6ab833920a | ||
|
|
801b29635c | ||
|
|
c035e69874 | ||
|
|
2ed367fb49 | ||
|
|
093e8acaf8 | ||
|
|
acdcdcaccb | ||
|
|
d749df70ed | ||
|
|
a979e52f7c | ||
|
|
1519236f2b | ||
|
|
2f532bd410 | ||
|
|
43c469fc95 | ||
|
|
8346968881 | ||
|
|
8c6c7ece5d | ||
|
|
c5124956a9 | ||
|
|
f8e969e44b | ||
|
|
e8d06d7a74 | ||
|
|
04d403ca97 | ||
|
|
44f1ba6d4d | ||
|
|
99b09c08d5 | ||
|
|
fbdfc4d446 | ||
|
|
28da6c4ecb | ||
|
|
b76087f561 | ||
|
|
eee5beeaf3 | ||
|
|
8f8cf5996e | ||
|
|
322d511034 | ||
|
|
54f443b17f | ||
|
|
24765d708a | ||
|
|
2402c59f3d | ||
|
|
efc92de19f | ||
|
|
5e655ef2d8 | ||
|
|
acf84f188f | ||
|
|
243867aadc | ||
|
|
b0f1fa4d64 | ||
|
|
0bd30c2a7a | ||
|
|
af896cd4bc | ||
|
|
1a1b4ff21e | ||
|
|
691e02ef1c | ||
|
|
3029bb8a68 | ||
|
|
1e4c6978b7 | ||
|
|
6cd4e03a58 | ||
|
|
a47ed99272 | ||
|
|
95b031e27c | ||
|
|
8f9ccc55d1 | ||
|
|
3909cc103a | ||
|
|
8358f8d93f | ||
|
|
48d59e8304 | ||
|
|
83f2a3bb3d | ||
|
|
87fa526fb7 | ||
|
|
c6a31710d9 | ||
|
|
19bd0c94e7 | ||
|
|
629c06d0ad | ||
|
|
1202f89399 | ||
|
|
4a95341caf | ||
|
|
3cf7677d17 | ||
|
|
ac3fecb134 | ||
|
|
5aaf5b4d59 | ||
|
|
2961a104af | ||
|
|
7b1a660e59 | ||
|
|
cb03b97e67 | ||
|
|
f26c246c7c | ||
|
|
649c546960 | ||
|
|
a930aa390e | ||
|
|
d1bf0fcd1e | ||
|
|
66033dcb01 | ||
|
|
95f63a9100 | ||
|
|
6654f0d155 | ||
|
|
35e1168bfa | ||
|
|
ef4f2ca03e | ||
|
|
f87596f8f7 | ||
|
|
17c6f8e9ff | ||
|
|
81d773d2bf | ||
|
|
8ad4d1f19a | ||
|
|
99ffaa8a1a | ||
|
|
c9dea60bbe | ||
|
|
a158cc81ae | ||
|
|
049e37e514 | ||
|
|
eb0fb3e822 | ||
|
|
dfeb855d18 | ||
|
|
ba8bd006f4 | ||
|
|
177430272c | ||
|
|
69318d3ba1 | ||
|
|
ce99c2a349 | ||
|
|
e6aa902224 | ||
|
|
d12a71cdf9 | ||
|
|
bb0e1d3979 | ||
|
|
2c22b68e15 | ||
|
|
654c0cc286 | ||
|
|
c07553cb57 | ||
|
|
491cb21722 | ||
|
|
90699da23b | ||
|
|
eb349935a0 | ||
|
|
4c53c595e7 | ||
|
|
72cbe59078 | ||
|
|
ce0505073c | ||
|
|
847749f19e | ||
|
|
52d1533c4f | ||
|
|
ac3e4bfe56 | ||
|
|
12eb69f665 | ||
|
|
00d1dc99e4 | ||
|
|
557edc629d | ||
|
|
7d37ba22e7 | ||
|
|
525be22bd8 | ||
|
|
ca226d62e2 | ||
|
|
c1f844307c | ||
|
|
5ffb945b66 | ||
|
|
604eaf9bdd | ||
|
|
13e058d9f1 | ||
|
|
2f28d24fe0 | ||
|
|
49667c263f | ||
|
|
3822d5b662 | ||
|
|
7fac0c3571 | ||
|
|
7e11aeca0a | ||
|
|
38da2ff185 | ||
|
|
c3e0a8dce5 | ||
|
|
16d00a68fe | ||
|
|
4440256eba | ||
|
|
8f73145d9d | ||
|
|
42511265e5 | ||
|
|
ed19301ad3 | ||
|
|
3b247812ce | ||
|
|
03ebe514e9 | ||
|
|
8b2cdfd1f8 | ||
|
|
47f4b4197d | ||
|
|
6af49a5246 | ||
|
|
5fdad873d3 | ||
|
|
cad795470f | ||
|
|
a90289548f | ||
|
|
9047bbbafb | ||
|
|
3b7b200b59 | ||
|
|
3230b18020 | ||
|
|
a3c8beaba0 | ||
|
|
b009d06bc3 | ||
|
|
2c603ef953 | ||
|
|
5f8e3d1676 | ||
|
|
1962767aec | ||
|
|
47881a802f | ||
|
|
207f15498f | ||
|
|
4cce933f89 | ||
|
|
9cb8db8e9a | ||
|
|
230fad533e | ||
|
|
40e328984d | ||
|
|
ed4e829adc | ||
|
|
7c42ed4cc3 | ||
|
|
0811c572ae | ||
|
|
619710852c | ||
|
|
779354d368 | ||
|
|
45bee078f0 | ||
|
|
42f7403bf5 | ||
|
|
a5931fbead | ||
|
|
6b7149d9e6 | ||
|
|
3b9e6124e0 | ||
|
|
92b23700d3 | ||
|
|
9fb4fb2741 | ||
|
|
9d71fa817d | ||
|
|
40ebe5ab54 | ||
|
|
20152d9756 | ||
|
|
e207236713 | ||
|
|
597a3325ea | ||
|
|
636f436465 | ||
|
|
e7d32625be | ||
|
|
37cbcc3e2b | ||
|
|
b502e0e530 | ||
|
|
d29d72e1e0 | ||
|
|
054d7dbb1c | ||
|
|
b208a83666 | ||
|
|
8b855ca88c | ||
|
|
612e3a0a3e | ||
|
|
e798258ae0 | ||
|
|
857a893660 | ||
|
|
7241fa29c6 |
13
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
// 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
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
// 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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
45
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/bin/**": true,
|
||||
"**/obj/**": true,
|
||||
"**/packages/**": true,
|
||||
"**/generated files/**": true
|
||||
}
|
||||
}
|
||||
108
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"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}",
|
||||
"/t:$target",
|
||||
"/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",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Run Windows Terminal Dev",
|
||||
"command": "wtd.exe",
|
||||
"args": [
|
||||
],
|
||||
"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"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
116
OpenConsole.sln
@@ -161,7 +161,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalConnection", "src\c
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalCore", "src\cascadia\TerminalCore\lib\TerminalCore-lib.vcxproj", "{CA5CAD1A-ABCD-429C-B551-8562EC954746}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalControl", "src\cascadia\TerminalControl\TerminalControl.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalControlLib", "src\cascadia\TerminalControl\TerminalControlLib.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
|
||||
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {CA5CAD1A-ABCD-429C-B551-8562EC954746}
|
||||
@@ -169,10 +169,15 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalControl", "src\casc
|
||||
{48D21369-3D7B-4431-9967-24E81292CF63} = {48D21369-3D7B-4431-9967-24E81292CF63}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalControl", "src\cascadia\TerminalControl\dll\TerminalControl.vcxproj", "{CA5CAD1A-F542-4635-A069-7CAEFB930070}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminal", "src\cascadia\WindowsTerminal\WindowsTerminal.vcxproj", "{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32} = {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070} = {CA5CAD1A-F542-4635-A069-7CAEFB930070}
|
||||
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}
|
||||
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {CA5CAD1A-ABCD-429C-B551-8562EC954746}
|
||||
{27B5AAEB-A548-44CF-9777-F8BAA32AF7AE} = {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}
|
||||
@@ -185,7 +190,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia
|
||||
{CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076}
|
||||
{CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32} = {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}
|
||||
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070} = {CA5CAD1A-F542-4635-A069-7CAEFB930070}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminalShellExt", "src\cascadia\ShellExtension\WindowsTerminalShellExt.vcxproj", "{F2ED628A-DB22-446F-A081-4CC845B51A2B}"
|
||||
@@ -236,7 +241,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\casca
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076}
|
||||
{CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32} = {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070} = {CA5CAD1A-F542-4635-A069-7CAEFB930070}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_TerminalApp", "src\cascadia\LocalTests_TerminalApp\TerminalApp.LocalTests.vcxproj", "{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}"
|
||||
@@ -326,7 +331,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Settings
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Settings.Model.Lib", "src\cascadia\TerminalSettingsModel\Microsoft.Terminal.Settings.ModelLib.vcxproj", "{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070} = {CA5CAD1A-F542-4635-A069-7CAEFB930070}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Settings.Model", "src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj", "{CA5CAD1A-082C-4476-9F33-94B339494076}"
|
||||
@@ -338,7 +343,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_SettingsModel",
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076}
|
||||
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070} = {CA5CAD1A-F542-4635-A069-7CAEFB930070}
|
||||
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
@@ -362,6 +367,16 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_Remoting", "src\c
|
||||
{27B5AAEB-A548-44CF-9777-F8BAA32AF7AE} = {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wpf", "wpf", "{4DAF0299-495E-4CD1-A982-9BAC16A45932}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenConsoleProxy", "src\host\proxy\Host.Proxy.vcxproj", "{E437B604-3E98-4F40-A927-E173E818EA4B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Window", "Window", "{2D17E75D-2DDC-42C4-AD70-704D95A937AE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Settings", "Settings", "{77875138-BB08-49F9-8BB1-409C2150E0E1}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Control", "Control", "{9921CA0A-320C-4460-8623-3A3196E7F4CB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
@@ -1514,6 +1529,33 @@ Global
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|x64.Build.0 = Release|x64
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|x86.ActiveCfg = Release|Win32
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|x86.Build.0 = Release|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|Any CPU.ActiveCfg = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|ARM64.ActiveCfg = Release|ARM64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|DotNet_x64Test.ActiveCfg = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|DotNet_x86Test.ActiveCfg = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|x86.ActiveCfg = Release|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|ARM.ActiveCfg = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|x64.Build.0 = Debug|x64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|x86.Build.0 = Debug|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|ARM.ActiveCfg = Release|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|x64.ActiveCfg = Release|x64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|x64.Build.0 = Release|x64
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|x86.ActiveCfg = Release|Win32
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|x86.Build.0 = Release|Win32
|
||||
{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|Any CPU.ActiveCfg = Debug|x64
|
||||
{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
|
||||
{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM64.ActiveCfg = Release|ARM64
|
||||
@@ -2457,7 +2499,7 @@ Global
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.Build.0 = Release|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
@@ -2538,6 +2580,36 @@ Global
|
||||
{68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|x64.Build.0 = Release|x64
|
||||
{68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|x86.ActiveCfg = Release|Win32
|
||||
{68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|x86.Build.0 = Release|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|x64.ActiveCfg = AuditMode|x64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|ARM.ActiveCfg = Debug|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|x64.Build.0 = Debug|x64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Debug|x86.Build.0 = Debug|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|ARM.ActiveCfg = Release|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|x64.ActiveCfg = Release|x64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|x64.Build.0 = Release|x64
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|x86.ActiveCfg = Release|Win32
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -2583,11 +2655,12 @@ Global
|
||||
{0CF235BD-2DA0-407E-90EE-C467E8BBC714} = {1E4A062E-293B-4817-B20D-BF16B979E350}
|
||||
{48D21369-3D7B-4431-9967-24E81292CF62} = {05500DEF-2294-41E3-AF9A-24E580B82836}
|
||||
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {9921CA0A-320C-4460-8623-3A3196E7F4CB}
|
||||
{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {9921CA0A-320C-4460-8623-3A3196E7F4CB}
|
||||
{CA5CAD1A-F542-4635-A069-7CAEFB930070} = {9921CA0A-320C-4460-8623-3A3196E7F4CB}
|
||||
{CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B} = {2D17E75D-2DDC-42C4-AD70-704D95A937AE}
|
||||
{CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{F2ED628A-DB22-446F-A081-4CC845B51A2B} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{F2ED628A-DB22-446F-A081-4CC845B51A2B} = {2D17E75D-2DDC-42C4-AD70-704D95A937AE}
|
||||
{2C2BEEF4-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{16376381-CE22-42BE-B667-C6B35007008D} = {81C352DB-1818-45B7-A284-18E259F1CC87}
|
||||
@@ -2595,8 +2668,8 @@ Global
|
||||
{05500DEF-2294-41E3-AF9A-24E580B82836} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{1E4A062E-293B-4817-B20D-BF16B979E350} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{376FE273-6B84-4EB5-8B30-8DE9D21B022C} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{376FE273-6B84-4EB5-8B30-8DE9D21B022C} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{CA5CAD1A-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
@@ -2616,17 +2689,22 @@ Global
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87}
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-082C-4476-9F33-94B339494076} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32} = {77875138-BB08-49F9-8BB1-409C2150E0E1}
|
||||
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {77875138-BB08-49F9-8BB1-409C2150E0E1}
|
||||
{CA5CAD1A-082C-4476-9F33-94B339494076} = {77875138-BB08-49F9-8BB1-409C2150E0E1}
|
||||
{CA5CAD1A-9B68-456A-B13E-C8218070DC42} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{21B7EA5E-1EF8-49B6-AC07-11714AF0E37D} = {A10C4720-DCA4-4640-9749-67F4314F527C}
|
||||
{F75E29D0-D288-478B-8D83-2C190F321A3F} = {A10C4720-DCA4-4640-9749-67F4314F527C}
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{27B5AAEB-A548-44CF-9777-F8BAA32AF7AE} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26} = {2D17E75D-2DDC-42C4-AD70-704D95A937AE}
|
||||
{27B5AAEB-A548-44CF-9777-F8BAA32AF7AE} = {2D17E75D-2DDC-42C4-AD70-704D95A937AE}
|
||||
{68A10CD3-AA64-465B-AF5F-ED4E9700543C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{4DAF0299-495E-4CD1-A982-9BAC16A45932} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{E437B604-3E98-4F40-A927-E173E818EA4B} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{2D17E75D-2DDC-42C4-AD70-704D95A937AE} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{77875138-BB08-49F9-8BB1-409C2150E0E1} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{9921CA0A-320C-4460-8623-3A3196E7F4CB} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
||||
17
README.md
@@ -33,10 +33,23 @@ This is our preferred method.
|
||||
|
||||
#### Via GitHub
|
||||
|
||||
For users who are unable to install Terminal from the Microsoft Store, Terminal
|
||||
builds can be manually downloaded from this repository's [Releases
|
||||
For users who are unable to install Windows Terminal from the Microsoft Store,
|
||||
released 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
|
||||
|
||||
@@ -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", "TerminalControl.dll", "TerminalConnection.dll")
|
||||
$RequiredInProcServers = ("TerminalApp.dll", "Microsoft.Terminal.Control.dll", "Microsoft.Terminal.Remoting.dll", "Microsoft.Terminal.Settings.Editor.dll", "Microsoft.Terminal.Settings.Model.dll", "TerminalConnection.dll")
|
||||
|
||||
Write-Verbose "InProc Servers: $inProcServers"
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<!-- This file is read by XES, which we use in our Release builds. -->
|
||||
<PropertyGroup Label="Version">
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
|
||||
<XesBaseYearForStoreVersion>2021</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>6</VersionMinor>
|
||||
<VersionMinor>8</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -336,6 +336,8 @@ INITIALIZE_BINDABLE_ENUM_SETTING(LaunchMode, LaunchMode, LaunchMode, L"Globals_L
|
||||
|
||||
### Updating the UI
|
||||
|
||||
When adding a setting to the UI, make sure you follow the [UWP design guidance](https://docs.microsoft.com/windows/uwp/design/).
|
||||
|
||||
#### Enum Settings
|
||||
|
||||
Now, create a XAML control in the relevant XAML file. Use the following tips and tricks to style everything appropriately:
|
||||
|
||||
@@ -76,9 +76,11 @@
|
||||
"copy",
|
||||
"duplicateTab",
|
||||
"find",
|
||||
"findMatch",
|
||||
"moveFocus",
|
||||
"moveTab",
|
||||
"newTab",
|
||||
"newWindow",
|
||||
"nextTab",
|
||||
"openNewTabDropdown",
|
||||
"openSettings",
|
||||
@@ -105,6 +107,7 @@
|
||||
"toggleFocusMode",
|
||||
"toggleFullscreen",
|
||||
"togglePaneZoom",
|
||||
"toggleReadOnlyMode",
|
||||
"toggleShaderEffects",
|
||||
"wt",
|
||||
"unbound"
|
||||
@@ -137,6 +140,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FindMatchDirection": {
|
||||
"enum": [
|
||||
"next",
|
||||
"prev"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SplitState": {
|
||||
"enum": [
|
||||
"vertical",
|
||||
@@ -212,6 +222,11 @@
|
||||
"$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"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -558,6 +573,35 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FindMatchAction": {
|
||||
"description": "Arguments corresponding to a Find Match Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "findMatch" },
|
||||
"direction": {
|
||||
"$ref": "#/definitions/FindMatchDirection",
|
||||
"default": "prev",
|
||||
"description": "The direction to search in. \"prev\" will search upwards in the buffer, and \"next\" will search downwards."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [ "direction" ]
|
||||
},
|
||||
"NewWindowAction": {
|
||||
"description": "Arguments corresponding to a New Window Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{ "$ref": "#/definitions/NewTerminalArgs" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type":"string", "pattern": "newWindow" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -582,6 +626,8 @@
|
||||
{ "$ref": "#/definitions/ScrollUpAction" },
|
||||
{ "$ref": "#/definitions/ScrollDownAction" },
|
||||
{ "$ref": "#/definitions/MoveTabAction" },
|
||||
{ "$ref": "#/definitions/FindMatchAction" },
|
||||
{ "$ref": "#/definitions/NewWindowAction" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
@@ -629,11 +675,26 @@
|
||||
"description": "When set to true, tabs are always displayed. When set to false and \"showTabsInTitlebar\" is set to false, tabs only appear after opening a new tab.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"centerOnLaunch": {
|
||||
"default": false,
|
||||
"description": "When set to `true`, the terminal window will auto-center itself on the display it opens on. The terminal will use the \"initialPosition\" to determine which display to open on.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"inputServiceWarning": {
|
||||
"default": true,
|
||||
"description": "Warning if 'Touch Keyboard and Handwriting Panel Service' is disabled.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyOnSelect": {
|
||||
"default": false,
|
||||
"description": "When set to true, a selection is immediately copied to your clipboard upon creation. When set to false, the selection persists and awaits further action.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"focusFollowMouse": {
|
||||
"default": false,
|
||||
"description": "When set to true, the terminal will focus the pane on mouse hover.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyFormatting": {
|
||||
"default": true,
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
|
||||
@@ -806,6 +867,16 @@
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"windowingBehavior": {
|
||||
"default": "useNew",
|
||||
"description": "Controls how new terminal instances attach to existing windows. \"useNew\" will always create a new window. \"useExisting\" will create new tabs in the most recently used window on this virtual desktop, and \"useAnyExisting\" will create tabs in the most recent window on any desktop.",
|
||||
"enum": [
|
||||
"useNew",
|
||||
"useExisting",
|
||||
"useAnyExisting"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -927,9 +998,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 25-100.",
|
||||
"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.",
|
||||
"maximum": 100,
|
||||
"minimum": 25,
|
||||
"minimum": 1,
|
||||
"type": ["integer","null"],
|
||||
"default": 25
|
||||
},
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
author: Pankaj Bhojwani, pabhojwa@microsoft.com
|
||||
created on: 2020-11-20
|
||||
last updated: 2021-2-5
|
||||
issue id: #8345
|
||||
---
|
||||
|
||||
# Appearance configuration objects for profiles
|
||||
|
||||
## Abstract
|
||||
|
||||
This spec outlines how we can support 'configuration objects' in our profiles, which
|
||||
will allow us to render differently depending on the state of the control. For example, a
|
||||
control can be rendered differently if it's focused as compared to when it's unfocused.
|
||||
|
||||
## Inspiration
|
||||
|
||||
Reference: [#3062](https://github.com/microsoft/terminal/issues/3062)
|
||||
|
||||
Users want there to be a more visible indicator than the one we have currently for which
|
||||
pane is focused and which panes are unfocused. This change would grant us that feature.
|
||||
|
||||
## Solution Design
|
||||
|
||||
The implementation design for appearance config objects centers around the recent change where inheritance was added to the
|
||||
`TerminalSettings` class in the Terminal Settings Model - i.e. different `TerminalSettings` objects can inherit from each other.
|
||||
The reason for this change was that we did not want a settings reload to erase any overrides `TermControl` may have made
|
||||
to the settings during runtime. By instead passing a child of the `TerminalSettings` object to the control, we can change
|
||||
the parent of the child during a settings reload without the overrides being erased (since those overrides live in the child).
|
||||
|
||||
The idea behind unfocused appearance configurations is similar. We will pass in another `TerminalSettings` object to the control,
|
||||
which is simply a child that already has some overrides in it. When the control gains or loses focus, it simply switches between
|
||||
the two settings objects appropriately.
|
||||
|
||||
### Allowed parameters
|
||||
|
||||
For now, these states are meant to be entirely appearance-based. So, not all parameters which can be
|
||||
defined in a `Profile` can be defined in this new object (for example, we do not want parameters which
|
||||
would cause a resize in this object.) Here is the list of parameters we will allow:
|
||||
|
||||
- Anything regarding colors: `colorScheme`, `foreground`, `background`, `cursorColor` etc
|
||||
- Anything regarding background image: `path`, `opacity`, `alignment`, `stretchMode`
|
||||
- `cursorShape`
|
||||
|
||||
We may wish to allow further parameters in these objects in the future (like `bellStyle`?). The addition
|
||||
of further parameters can be discussed in the future and is out of scope for this spec.
|
||||
|
||||
### Inheritance
|
||||
|
||||
The inheritance model can be thought of as an 'all-or-nothing' approach in the sense that the `unfocusedAppearance` object
|
||||
is considered as a *single* setting instead of an object with many settings. We have chosen this model because it is cleaner
|
||||
and easier to understand than the alternative, where each setting within an `unfocusedAppearance` object has a parent from which
|
||||
it obtains its value.
|
||||
|
||||
Note that when `TerminalApp` initializes a control, it creates a `TerminalSettings` object for that profile and passes the
|
||||
control a child of that object (so that the control can store overrides in the child, as described earlier). If an unfocused
|
||||
config is defined in the profile (or in globals/profile defaults), then `TerminalApp` will create a *child of that child*,
|
||||
put the relevant overrides in it, and pass that into the control as well. Thus, the inheritance of any undefined parameters
|
||||
in the unfocused config will be as follows:
|
||||
|
||||
1. The unfocused config specified in the profile (or in globals/profile defaults)
|
||||
2. Overrides made by the terminal control
|
||||
3. The parent profile
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
Users will be able to add a new setting to their profiles that will look like this:
|
||||
|
||||
```
|
||||
"unfocusedAppearance":
|
||||
{
|
||||
"colorScheme": "Campbell",
|
||||
"cursorColor": "#888",
|
||||
"cursorShape": "emptyBox",
|
||||
"foreground": "#C0C0C0",
|
||||
"background": "#000000"
|
||||
}
|
||||
```
|
||||
|
||||
When certain appearance settings are changed via OSC sequences (such as the background color), only the focused/regular
|
||||
appearance will change and the unfocused one will remain unchanged. However, since the unfocused settings object inherits
|
||||
from the regular one, it will still apply the change (provided it does not define its own value for that setting).
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
Does not affect accessibility.
|
||||
|
||||
### Security
|
||||
|
||||
Does not affect security.
|
||||
|
||||
### Reliability
|
||||
|
||||
This is another location in the settings where parsing/loading the settings may fail. However, this is the case
|
||||
for any new setting we add so I would say that this is a reasonable cost for this feature.
|
||||
|
||||
### Compatibility
|
||||
|
||||
Should not affect compatibility.
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
Rapidly switching between many panes, causing several successive appearance changes in a short period of time, could
|
||||
potentially impact performance. However, regular/reasonable pane switching should not have a noticeable effect.
|
||||
|
||||
## Potential Issues
|
||||
|
||||
Inactive tabs will be 'rendered' in the background with the `UnfocusedRenderingParams` object, we need to make
|
||||
sure that switching to an inactive tab (and so causing the renderer to update with the 'normal' parameters)
|
||||
does not cause the window to flash/show a jarring indicator that the rendering values changed.
|
||||
|
||||
## Future considerations
|
||||
|
||||
We will need to decide how this will look in the settings UI.
|
||||
|
||||
We may wish to add more states in the future (like 'elevated'). When that happens, we will need to deal with how
|
||||
these appearance objects can scale/layer over each other. We had a lot of discussion about this and could not find
|
||||
a suitable solution to the problem of multiple states being valid at the same time (like unfocused and elevated).
|
||||
This, along with the fact that it is uncertain if there even will be more states we would want to add led us to
|
||||
the conclusion that we should only support the unfocused state for now, and come back to this issue later. If there
|
||||
are no more states other than unfocused and elevated, we could allow combining them (like having an 'unfocused elevated' state).
|
||||
If there are more states, we could do the implementation as an extension rather than inherently supporting it.
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-10-30
|
||||
last updated: 2020-02-05
|
||||
issue id: #4472
|
||||
---
|
||||
|
||||
# Windows Terminal Session Management
|
||||
|
||||
## Abstract
|
||||
This document is intended to serve as an addition to the [Process Model 2.0
|
||||
Spec]. That document provides a big-picture overview of changes to the entirety
|
||||
of the Windows Terminal process architecture, including both the split of
|
||||
window/content processes, as well as the introduction of monarch/peasant
|
||||
processes. The focus of that document was to identify solutions to a set of
|
||||
scenarios that were closely intertwined, and establish these solutions would
|
||||
work together, without preventing any one scenario from working. What that
|
||||
document did not do was prescribe specific solutions to the given scenarios.
|
||||
|
||||
This document offers a deeper dive on a subset of the issues in [#5000], to
|
||||
describe specifics for managing multiple windows with the Windows Terminal. This
|
||||
includes features such as:
|
||||
|
||||
* Run `wt` in the current window ([#4472])
|
||||
* Single Instance Mode ([#2227])
|
||||
|
||||
## Solution Design
|
||||
|
||||
### Monarch and Peasant Processes
|
||||
|
||||
This document assumes the reader is already familiar with the "Monarch and
|
||||
Peasant" architecture as detailed in the [Windows Terminal Process Model 2.0
|
||||
Spec]. As a quick summary:
|
||||
|
||||
* Every Windows Terminal window is a "Peasant" process.
|
||||
* One of the Windows Terminal window processes is also the "Monarch" process.
|
||||
The Monarch is picked randomly from the Terminal windows, and there is only
|
||||
ever one Monarch process at a time.
|
||||
* Peasants can communicate with the monarch when certain state changes (such as
|
||||
their window being activated), and the monarch can send commands to any of the
|
||||
peasants.
|
||||
|
||||
This architecture will be used to enable each of the following scenarios.
|
||||
|
||||
### Scenario: Open new tabs in most recently used window
|
||||
|
||||
A common feature of many browsers is that when a web URL is clicked somewhere,
|
||||
the web page is opened as a new tab in the most recently used window of the
|
||||
browser. This functionality is often referred to as "glomming", as the new tab
|
||||
"gloms" onto the existing window.
|
||||
|
||||
Currently, the terminal does not support such a feature - every `wt` invocation
|
||||
creates a new window. With the monarch/peasant architecture, it'll now be
|
||||
possible to enable such a scenario.
|
||||
|
||||
As each window is activated, it will call a method on the `Monarch` object
|
||||
(hosted by the monarch process) which will indicate that "I am peasant N, and
|
||||
I've been focused". The monarch will use those method calls to update its own
|
||||
internal stack of the most recently used windows.
|
||||
|
||||
Whenever a new `wt.exe` process is launched, that process will _first_ ask the
|
||||
monarch if it should run the commandline in an existing window, or create its
|
||||
own window.
|
||||
|
||||

|
||||
|
||||
If glomming is enabled, the monarch will dispatch the commandline to the
|
||||
appropriate window for them to handle instead. To the user, it'll seem as if the
|
||||
tab just opened in the most recent window.
|
||||
|
||||
Users should certainly be able to specify if they want new instances to glom
|
||||
onto the MRU window or not. You could imagine that currently, we default to the
|
||||
hypothetical value `"windowingBehavior": "useNew"`, meaning that each new wt gets
|
||||
its own new window.
|
||||
|
||||
If glomming is disabled, then the Monarch will call back to the peasant and tell
|
||||
it to run the provided commandline. The monarch will use the return value of
|
||||
`ExecuteCommandline` to indicate that the calling process should create a window
|
||||
and become a peasant process, and run the commandline itself.
|
||||
|
||||
#### Glomming within the same virtual desktop
|
||||
|
||||
When links are opened in the new Edge browser, they will only glom onto an
|
||||
existing window if that window is open in the current virtual desktop. This
|
||||
seems like a good idea of a feature for the Terminal to follow as well.
|
||||
|
||||
There must be some way for an application to determine which virtual desktop it
|
||||
is open on. We could use that information to have the monarch track the last
|
||||
active window _per-desktop_, and only glom when there's one on the current
|
||||
desktop.
|
||||
|
||||
We could make the `windowingBehavior` property accept a variety of
|
||||
configurations:
|
||||
|
||||
- `"useExisting"`: always glom to the most recent window, regardless of desktop.
|
||||
- `"useExistingOnSameDesktop"`: Only glom if there's an existing window on this
|
||||
virtual desktop, otherwise create a new window. This will be the new default
|
||||
value.
|
||||
- `"useNew"`: Never glom, always create a new window. This is technically the
|
||||
current behavior of the Terminal.
|
||||
|
||||
### Handling the current working directory
|
||||
|
||||
Consider the following scenario: the user runs `wt -d .` in the address bar of
|
||||
explorer, and the monarch determines that this new tab should be created in an
|
||||
existing window. For clarity during this example, we will label the existing
|
||||
window WT[1], and the second `wt.exe` process WT[2].
|
||||
|
||||
An example of this scenario is given in the following diagram:
|
||||
|
||||

|
||||
|
||||
In this scenario, we want the new tab to be spawned in the current working
|
||||
directory of WT[2], not WT[1]. So when WT[1] is about to run the commands that
|
||||
were passed to WT[2], WT[1] will need to:
|
||||
|
||||
* First, stash its own CWD
|
||||
* Change to the CWD of WT[2]
|
||||
* Run the commands from WT[2]
|
||||
* Then return to its original CWD.
|
||||
|
||||
So, as a part of the interface that a peasant uses to communicate the startup
|
||||
commandline to the monarch, we should also include the current working
|
||||
directory.
|
||||
|
||||
### Scenario: Run `wt` in the current window
|
||||
|
||||
One often requested scenario is the ability to run a `wt.exe` commandline in the
|
||||
current window, as opposed to always creating a new window. Presume we have the
|
||||
ability to communicate between different window processes. The logical extension
|
||||
of this scenario would be "run a `wt` commandline in _any_ given WT window".
|
||||
|
||||
Each window process will have its own unique ID assigned to it by the monarch.
|
||||
This ID will be a positive number. Windows can also have names assigned to them.
|
||||
These names are strings that the user specifies. A window will always have an
|
||||
ID, but not necessarily a name. Running a command in a given window with ID N
|
||||
should be as easy as something like:
|
||||
|
||||
```sh
|
||||
wt.exe --window N new-tab ; split-pane
|
||||
```
|
||||
|
||||
(or for shorthand, `wt -w N new-tab ; split-pane`).
|
||||
|
||||
More formally, we will add the following parameter to the top-level `wt`
|
||||
command:
|
||||
|
||||
#### `--window,-w <window-id>`
|
||||
Run these commands in the given Windows Terminal session. This enables opening
|
||||
new tabs, splits, etc. in already running Windows Terminal windows.
|
||||
* If `window-id` is `0`, run the given commands in _the current window_.
|
||||
* If `window-id` is a negative number, or the reserved name `new`, run the
|
||||
commands in a _new_ Terminal window.
|
||||
* If `window-id` is the ID or name of an existing window, then run the
|
||||
commandline in that window.
|
||||
* If `window-id` is _not_ the ID or name of an existing window, create a new
|
||||
window. That window will be assigned the ID or name provided in the
|
||||
commandline. The provided subcommands will be run in that new window.
|
||||
* If `window-id` is omitted, then obey the value of `windowingBehavior` when
|
||||
determining which window to run the command in.
|
||||
|
||||
_Whenever_ `wt.exe` is started, it must _always_ pass the provided commandline
|
||||
first to the monarch process for handling. This is important for glomming
|
||||
scenarios (as noted above). The monarch will parse the commandline, determine
|
||||
which window the commandline is destined for, then call `ExecuteCommandline` on
|
||||
that peasant, who will then run the command.
|
||||
|
||||
#### Running commands in the current window:`wt --window 0`
|
||||
|
||||
If `wt -w 0 <commands>` is run _outside_ a WT instance, it could attempt to glom
|
||||
onto _the most recent WT window_ instead. This seems more logical than something
|
||||
like `wt --window last` or some other special value indicating "run this in the
|
||||
MRU window".<sup>[[2]](#footnote-2)</sup>
|
||||
|
||||
That might be a simple, but **wrong**, implementation for "the current window".
|
||||
If the peasants always raise an event when their window is focused, and the
|
||||
monarch keeps track of the MRU order for peasants, then one could naively assume
|
||||
that the execution of `wt -w 0 <commands>` would always return the window the
|
||||
user was typing in, the current one. However, if someone were to do something
|
||||
like `sleep 10 ; wt -w 0 <commands>`, then the user could easily focus another
|
||||
WT window during the sleep, which would cause the MRU window to not be the same
|
||||
as the window executing the command.
|
||||
|
||||
To solve this issue, we'll other than
|
||||
attempting to use the `WT_SESSION` environment variable. If a `wt.exe` process
|
||||
is spawned and that's in its environment variables, it could try and ask the
|
||||
monarch for the peasant who's hosting the session corresponding to that GUID.
|
||||
This is more of a theoretical solution than anything else.
|
||||
|
||||
In the past we've been reluctant to rely too heavily on `WT_SESSION`. However,
|
||||
an environment variable does seem to be the only reliable way to be confident
|
||||
where the window was created from. We could introduce another environment
|
||||
variable instead - `WT_WINDOW_ID`. That would allow us to shortcut the session
|
||||
ID lookup. However, I worry about exposing the window ID as an environment
|
||||
variable. If we do that, users will inevitably use that instead of the `wt -0`
|
||||
alias, which should take care of the work for them. Additionally, `WT_WINDOW_ID`
|
||||
wouldn't update in the child processes as tabs are torn out of windows to create
|
||||
new windows.
|
||||
|
||||
Both solutions are prone to the user changing the value of the variable to some
|
||||
garbage value. If they do that, this lookup will most certainly not work as
|
||||
expected. Using the session ID (a GUID) instead of the window ID (an int) makes
|
||||
it less likely that they guess the ID of an existing instance.
|
||||
|
||||
#### Running commands in a new window:`wt --window -1` / `wt --window new`
|
||||
|
||||
If the user passes a negative number, or the reserved name `new` to the
|
||||
`--window` parameter, then we will always create a new window for that
|
||||
commandline, regardless of the value of `windowingBehavior`. This will allow
|
||||
users to do something like `wt -w -1 new-tab` to _always_ create a new window.
|
||||
|
||||
#### `--window` in subcommands
|
||||
|
||||
The `--window` parameter is a setting to `wt.exe` itself, not to one of its
|
||||
subcommands (like `new-tab` or `split-pane`). This means that all of the
|
||||
subcommands in a particular `wt` commandline will all be handled by the same
|
||||
session. For example, let us consider a user who wants to open a new tab in
|
||||
window 2, and split a new pane in window 3, all at once. The user _cannot_ do
|
||||
something like:
|
||||
|
||||
```cmd
|
||||
wt -w 2 new-tab ; -w 3 split-pane
|
||||
```
|
||||
|
||||
Instead, the user will need to separate the commands (by whatever their shell's
|
||||
own command delimiter is) and run two different `wt.exe` instances:
|
||||
|
||||
```cmd
|
||||
wt -w 2 new-tab & wt -w 3 split-pane
|
||||
```
|
||||
|
||||
This is done to make the parsing of the subcommands easier, and for the internal
|
||||
passing of arguments simpler. If the `--window` parameter were a part of each
|
||||
subcommand, then each individual subcommand's parser would need to be
|
||||
enlightened about that parameter, and then it would need to be possible for any
|
||||
single part of the commandline to call out to another process. It would be
|
||||
especially tricky then to coordinate the work being done across process here.
|
||||
The source process would need some sort of way to wait for the other process to
|
||||
notify the source that a particular subcommand completed, before allowing the
|
||||
source to dispatch the next part of the commandline.
|
||||
|
||||
Overall, this is seen as unnecessarily complex, and dispatching whole sets of
|
||||
commands as a simpler solution.
|
||||
|
||||
### Naming Windows
|
||||
|
||||
It's not user-friendly to rely on automatically generated, invisible numbers to
|
||||
identify windows. There's not a great way of identifying which window is which.
|
||||
The user would need to track the IDs in their head manually. Instead, we'll
|
||||
allow the user to provide a string name for the window. This name can be used to
|
||||
address a window in addition to the ID.
|
||||
|
||||
Names can be provided on the commandline, in the original commandline. For
|
||||
example, `wt -w foo nt` would name the new window "foo". Names can also be set
|
||||
with a new action, `NameWindow`<sup>[[3]](#footnote-3)</sup>. `name-window`
|
||||
could also be used as a subcommand. For example, `wt -w 4 name-window bar` would
|
||||
name window 4 "bar".
|
||||
|
||||
To keep identities mentally distinct, we will disallow names that are integers
|
||||
(positive or negative). This will prevent users from renaming a window to `2`,
|
||||
then having `wt -w 2` be ambiguous as to which window it refers to.
|
||||
|
||||
Names must also be unique. If a user attempts to set the name of the window to
|
||||
an already-used name, we'll need to ignore the name change. We could also
|
||||
display a "toast" or some other type of low-impact message to the user. That
|
||||
message would have some text like: "Unable to rename window. Another window with
|
||||
that name already exists".
|
||||
|
||||
The Terminal will reserve the name `new`. It will also reserve any names
|
||||
starting with the character `_`. The user will not be allowed to set the window
|
||||
name to any of these reserved names. Reserving `_*` allows us to add other
|
||||
keywords in the future, without introducing a breaking change.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
### `windowingBehavior` details
|
||||
|
||||
The following list gives greater breakdown of the values of `windowingBehavior`,
|
||||
and how they operate:
|
||||
|
||||
* `"windowingBehavior": "useExisting", "useExistingOnSameDesktop"`:
|
||||
**Browser-like glomming**
|
||||
- New instances open in the current window by default.
|
||||
- `newWindow` opens a new window.
|
||||
- Tabs can be torn out to create new windows.
|
||||
- `wt -w -1` opens a new window.
|
||||
* `"windowingBehavior": "useNew"`: No auto-glomming. This is **the current
|
||||
behavior** of the Terminal.
|
||||
- New instances open in new windows by default
|
||||
- `newWindow` opens a new window
|
||||
- Tabs can be torn out to create new windows.
|
||||
- `wt -w -1` opens a new window.
|
||||
|
||||
We'll be changing the default behavior from `useNew` to
|
||||
`useExistingOnSameDesktop`. This will be more consistent with other tabbed
|
||||
applications.
|
||||
|
||||
## Concerns
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Accessibility</strong></td>
|
||||
<td>
|
||||
|
||||
There is no expected accessibility impact from this feature. Each window will
|
||||
handle UIA access as it normally does.
|
||||
|
||||
In the future, we could consider exposing the window IDs and/or names via UIA.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Security</strong></td>
|
||||
<td>
|
||||
|
||||
Many security concerns have already be covered in greater detail in the parent
|
||||
spec, [Process Model 2.0 Spec].
|
||||
|
||||
When attempting to instantiate the Monarch, COM will only return the object from
|
||||
a server running at the same elevation level. We don't need to worry about
|
||||
unelevated peasants connecting to the elevated Monarch, or vice-versa.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Reliability</strong></td>
|
||||
<td>
|
||||
|
||||
We will need to be careful when working with objects hosted by another process.
|
||||
Any work we do with it MUST be in a try/catch, because at _any_ time, the other
|
||||
process could be killed. At any point, a window process could be killed. Both
|
||||
the monarch and peasant code will need to be redundant to such a scenario, and
|
||||
if the other process is killed, make sure to display an appropriate error and
|
||||
either recover or exit gracefully.
|
||||
|
||||
In any and all these situations, we will want to try and be as verbose as
|
||||
possible in the logging. This will make tracking which process had the error
|
||||
occur easier.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
|
||||
We will be changing the default behavior of the Terminal to auto-glom to the
|
||||
most-recently used window on the same desktop in the course of this work, which
|
||||
will be a breaking UX change. This is behavior that can be reverted with the
|
||||
`"windowingBehavior": "useNew"` setting.
|
||||
|
||||
We acknowledge that this is a pretty massive change to the default experience of
|
||||
the Terminal. We're planning on doing some polling of users to determine which
|
||||
behavior they want by default. Additionally, we'll be staging the rollout of
|
||||
this feature, using the Preview builds of the Terminal. The release notes that
|
||||
first include it will call extra attention to this feature. We'll ask that users
|
||||
provide their feedback in a dedicated thread, so we have time to collect
|
||||
opinions from users before rolling the change out to all users.
|
||||
|
||||
We may choose to only change the default to `useExistingOnSameDesktop` once tab
|
||||
tear out is available, so users who are particularly unhappy about this change
|
||||
can still tear out the tab (if they can't be bothered to change the setting).
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Performance, Power, and Efficiency</strong></td>
|
||||
<td>
|
||||
|
||||
There's no dramatic change expected here. There may be a minor delay in the
|
||||
spawning of new terminal instances, due to requiring cross-process hops for the
|
||||
communication between monarch and peasant processes.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Potential Issues
|
||||
|
||||
### Mixed Elevation Levels
|
||||
|
||||
As of December 2020, we're no longer pursuing a "mixed-elevation" scenario for
|
||||
the Terminal. This makes many of the cross-elevation scenarios simpler. Elevated
|
||||
and unelevated `wt` instances will always remain separate. The different
|
||||
elevation levels will maintain separate lists of window IDs. If the user is
|
||||
running both an elevated and unelevated window, then there will be two monarchs.
|
||||
One elevated, and the other unelevated.
|
||||
|
||||
There will also be some edge cases when handling the commandline that will need
|
||||
special care. Say the user wanted to open a new tab in the elevated window, from
|
||||
and unelevated `explorer.exe`. That would be a commandline like:
|
||||
|
||||
```sh
|
||||
wt -w 0 new-tab -d . --elevated
|
||||
```
|
||||
|
||||
Typically we first determine which window the commandline is intended for, then
|
||||
dispatch it to that window. In this case, the `-w 0` will cause us to pass the
|
||||
commandline to the current unelevated window. Then, that window will try to open
|
||||
an elevated tab, fail, and create a new `wt.exe` process. This second `wt.exe`
|
||||
process will lose the `-w 0` context. It won't inform the elevated monarch that
|
||||
this commandline should be run in the active session.
|
||||
|
||||
We will need to make sure that special care is taken when creating elevated
|
||||
instances that we maintain the `--window` parameter passed to the Terminal.
|
||||
|
||||
### `wt` Startup Commandline Options
|
||||
|
||||
There are a few commandline options which can be provided to `wt.exe` which
|
||||
don't make sense to pass to another session. These options include (but are not
|
||||
limited to):
|
||||
|
||||
* `--initialSize r,c`
|
||||
* `--initialPosition x,y`
|
||||
* `--fullscreen`, `--maximized`, etc.
|
||||
|
||||
When we're passing a commandline to another instance to handle, these arguments
|
||||
will be ignored. they only apply to the initial creation of a window.
|
||||
`--initialSize 32, 120` doesn't make sense if the window already has a size.
|
||||
|
||||
On startup of a new window, we currently assume that the first command is always
|
||||
`new-tab`. When passing commandlines to existing windows, we won't need to make
|
||||
that assumption anymore. There will already be existing tabs.
|
||||
|
||||
### Monarch MRU Window Tracking
|
||||
|
||||
As stated above, the monarch is responsible for tracking the MRU window stack.
|
||||
However, when the monarch is closed, this state will be lost. The new monarch
|
||||
will be elected, but it will be unable to ask the old monarch for the MRU
|
||||
order of the windows.
|
||||
|
||||
We had previously considered an _acceptable_ UX when this would occur. We would
|
||||
randomize the order (with the new monarch becoming the MRU window). If someone
|
||||
noticed this bug and complained, then we had a theoretical solution prepared.
|
||||
The peasants could inform not only the monarch, but _all other peasants_ when
|
||||
they become activated. This would mean all peasants are simultaneously tracking
|
||||
the MRU stack. This would mean that any given peasant would be prepared always
|
||||
to become the monarch.
|
||||
|
||||
A simpler solution though would be to not track the MRU stack in the Monarch at
|
||||
all. Instead, each peasant could just track internally when they were last
|
||||
activated. The Monarch wouldn't track any state itself. It would be distributed
|
||||
across all the peasants. The Monarch could then iterate over the list of
|
||||
peasants and find the one with the newest `LastActivated` timestamp.
|
||||
|
||||
Now, when a Monarch dies, the new Peasant doesn't have to come up with the stack
|
||||
itself. All the other Peasants keep their state. The new Monarch can query them
|
||||
and get the same answer the old Monarch would have.
|
||||
|
||||
We could further optimize this by having the Monarch also track the stack. Then,
|
||||
the monarch could query the MRU window quickly. The `LastActivated` timestamps
|
||||
would only be used by a new Monarch when it is elected, to reconstruct the MRU
|
||||
stack.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
This is a list of actionable tasks generated as described by this spec:
|
||||
|
||||
* [ ] Add support for `wt.exe` processes to be Monarchs and Peasants, and
|
||||
communicate that state between themselves. This task does not otherwise add
|
||||
any user-facing features, merely an architectural update.
|
||||
* [ ] Add support for the `windowingBehavior` setting as a boolean. Opening new
|
||||
WT windows will conditionally glom to existing windows.
|
||||
* [ ] Add support for per-desktop `windowingBehavior`, by adding the support for
|
||||
the enum values `"useExisting"`, `"useExistingOnSameDesktop"` and `"useNew"`.
|
||||
* [ ] Add support for `wt.exe` to pass commandlines intended for another window
|
||||
to the monarch, then to the intended window, with the `--window,-w
|
||||
window-id` commandline parameter.
|
||||
* [ ] Add support for targeting and naming windows via the `-w` parameter on the
|
||||
commandline
|
||||
* [ ] Add a `NameWindow` action, subcommand that allows the user to set the name
|
||||
for the window.
|
||||
* [ ] Add an action that will cause all windows to briefly display a overlay
|
||||
with the current window ID and name. This would be something like the
|
||||
"identify" feature of the Windows "Display" settings.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* What if the user wanted to pipe a command to a pane in an existing window?
|
||||
```sh
|
||||
man ping > wt -w 0 split-pane cat
|
||||
```
|
||||
Is there some way for WT to pass its stdin/out handles to the child process
|
||||
it's creating? This is _not_ related to the current spec at hand, just
|
||||
something the author considered while writing the spec. This likely belongs
|
||||
over in [#492], or in its own spec.
|
||||
- Or I suppose, with less confusion, someone could run `wt -w 0 split-pane --
|
||||
man ping > cat`. That's certainly more sensible, and wouldn't require any
|
||||
extra work.
|
||||
* "Single Instance Mode" is a scenario in which there is only ever one single WT
|
||||
window. A user might want this functionality to only ever allow a single
|
||||
terminal window to be open on their desktop. This is especially frequently
|
||||
requested in combination with "quake mode", as discussed in [#653]. When Single
|
||||
Instance Mode is active, and the user runs a new `wt.exe` commandline, it will
|
||||
always end up running in the existing window, if there is one.
|
||||
|
||||
An earlier version of this spec proposed a new value of `glomToLastWindow`.
|
||||
(`glomToLastWindow` was later renamed `windowingBehavior`). The `always` value
|
||||
would disable tab tear out<sup>[[1]](#footnote-1)</sup>. It would additionally
|
||||
disable the `newWindow` action, and prevent `wt -w new` from opening a new
|
||||
window.
|
||||
|
||||
In discussion, it was concluded that this setting didn't make sense. Why did the
|
||||
`glomToLastWindow` setting change the behavior of tear out? Single Instance Mode
|
||||
is most frequently requested in regards to quake mode. We're leaving the
|
||||
implementation of true single instance mode to that spec.
|
||||
* It was suggested in review that we could auto-generate names for windows, from
|
||||
some list of words. Prior art could be the URLS for gfycat.com or
|
||||
what3words.com, which use three random words. I believe `docker` also assigns
|
||||
names from a random selection of `adjective`+`name`. This is an interesting
|
||||
idea, and something that could be pursued in the future.
|
||||
- This would be a massive pain to localize though, hence why this is left as
|
||||
a future consideration.
|
||||
* We will _need_ to provide a commandline tool to list windows and their IDs &
|
||||
names. We're thinking a list of windows, their IDs, names, PIDs, and the title
|
||||
of the window.
|
||||
|
||||
Currently we're stuck with `wt.exe` which is a GUI application, and cannot
|
||||
print to the console. Our need is now fairly high for the ability to print
|
||||
info to the console. To remedy this, we'll need to ship another helper exe as
|
||||
a commandline tool for working with the terminal. The design for this is left
|
||||
for the future.
|
||||
|
||||
## Footnotes
|
||||
|
||||
<a name="footnote-1"><a>[1]: While tear-out is a separate track of work from
|
||||
session management in general, this setting could be implemented along with this
|
||||
set of features, and later used to control tear out as well.
|
||||
|
||||
<a name="footnote-2"><a>[2]: Since we're reserving the keyword `new` to mean "a
|
||||
new window", then we could also reserve `last` or `current` as an alias for "the
|
||||
current window".
|
||||
|
||||
<a name="footnote-3"><a>[3]: We currently have two actions for renaming _tabs_
|
||||
in the Terminal: `renameTab(name)`, and `openTabRenamer()`. We will likely
|
||||
similarly need `nameWindow(name)` and `openWindowNamer()`. `openWindowNamer`
|
||||
could display a dialog to allow the user to rename the current window at
|
||||
runtime.
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
* [Tab Tear-out in the community toolkit] - this document proved invaluable to
|
||||
the background of tearing a tab out of an application to create a new window.
|
||||
|
||||
<!-- Footnotes -->
|
||||
|
||||
[#5000]: https://github.com/microsoft/terminal/issues/5000
|
||||
[#1256]: https://github.com/microsoft/terminal/issues/1256
|
||||
[#4472]: https://github.com/microsoft/terminal/issues/4472
|
||||
[#2227]: https://github.com/microsoft/terminal/issues/2227
|
||||
[#653]: https://github.com/microsoft/terminal/issues/653
|
||||
[#1032]: https://github.com/microsoft/terminal/issues/1032
|
||||
[#632]: https://github.com/microsoft/terminal/issues/632
|
||||
[#492]: https://github.com/microsoft/terminal/issues/492
|
||||
[#4000]: https://github.com/microsoft/terminal/issues/4000
|
||||
[#7972]: https://github.com/microsoft/terminal/pull/7972
|
||||
[#961]: https://github.com/microsoft/terminal/issues/961
|
||||
[`30b8335`]: https://github.com/microsoft/terminal/commit/30b833547928d6dcbf88d49df0dbd5b3f6a7c879
|
||||
[Tab Tear-out in the community toolkit]: https://github.com/windows-toolkit/Sample-TabView-TearOff
|
||||
[Quake mode scenarios]: https://github.com/microsoft/terminal/issues/653#issuecomment-661370107
|
||||
[`ISwapChainPanelNative2::SetSwapChainHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/windows.ui.xaml.media.dxinterop/nf-windows-ui-xaml-media-dxinterop-iswapchainpanelnative2-setswapchainhandle
|
||||
[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
|
||||
1186
doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md
Normal file
BIN
doc/specs/#5000 - Process Model 2.0/auto-glom-wt-exe.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 54 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/figure-001.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/figure-002.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/figure-003.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/mixed-elevation.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/single-instance-mode-cwd.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/tear-out-tab.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/wt-session-0.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
95
doc/specs/#597 - Tab Sizing.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
author: Kayla Cinnamon @cinnamon-msft
|
||||
created on: 2020-01-03
|
||||
last updated: 2020-01-03
|
||||
issue id: 597
|
||||
---
|
||||
|
||||
# Tab Sizing
|
||||
|
||||
## Abstract
|
||||
|
||||
This spec outlines the tab sizing feature. This is an application-level feature that is not profile-specific (at least for now).
|
||||
|
||||
Global properties that encompass tab sizing:
|
||||
|
||||
* `tabWidthMode` (accepts pre-defined values for tab sizing behavior)
|
||||
* `tabWidthMin` (can never be smaller than the icon width)
|
||||
* `tabWidthMax` (can never be wider than the tab bar)
|
||||
|
||||
Acceptable values for `tabWidthMode`:
|
||||
|
||||
* [default] `equal` (all tabs are sized the same, regardless of tab title length)
|
||||
* `titleLength` (width of tab contains entire tab title)
|
||||
|
||||
## Inspiration
|
||||
|
||||
Other browsers and terminals have varying tab width behavior, so we should give people options.
|
||||
|
||||
## Solution Design
|
||||
|
||||
`tabWidthMode` will be a global setting that will accept the following strings:
|
||||
|
||||
* `equal`
|
||||
* All tabs are equal in width
|
||||
* If the tab bar has filled, tabs will shrink as additional tabs are added
|
||||
* Utilizes the `equal` setting from WinUI's TabView
|
||||
|
||||
* `titleLength`
|
||||
* Tab width varies depending on title length
|
||||
* Width of tab will fit the whole tab title
|
||||
* Utilizes the `sizeToContent` setting from WinUI's TabView
|
||||
|
||||
In addition to `tabWidthMode`, the following global properties will also be available:
|
||||
|
||||
* `tabWidthMin`
|
||||
* Accepts an integer
|
||||
* Value correlates to the minimum amount of pixels the tab width can be
|
||||
* If value is less than the width of the icon, the minimum width will be the width of the icon
|
||||
* If value is greater than the width of the tab bar, the maximum width will be the width of the tab bar
|
||||
* If not set, the tab will have the system-defined minimum width
|
||||
|
||||
* `tabWidthMax`
|
||||
* Accepts an integer
|
||||
* Value correlates to the maximum amount of pixels the tab width can be
|
||||
* If value is less than the width of the icon, the minimum width will be the width of the icon
|
||||
* If value is greater than the width of the tab bar, the maximum width will be the width of the tab bar
|
||||
* If not set, the tab will have the system-defined maximum width
|
||||
|
||||
If `tabWidthMode` is set to `titleLength`, the tab widths will fall between the `tabWidthMin` and `tabWidthMax` values if they are set, depending on the length of the tab title.
|
||||
|
||||
If `tabWidthMode` isn't set, the default experience will be `equal`. Justification for the default experience is the results from this [twitter poll](https://twitter.com/cinnamon_msft/status/1203093459055210496).
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
[This tweet](https://twitter.com/cinnamon_msft/status/1203094776117022720) displays how the `equal` and `titleLength` values behave for the `tabWidthMode` property.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
This feature could impact accessibility if the tab title isn't stored within the metadata of the tab. If the tab width is the width of the icon, then the title isn't visible. The tab title will have to be accessible by a screen reader.
|
||||
|
||||
### Security
|
||||
|
||||
This feature will not impact security.
|
||||
|
||||
### Reliability
|
||||
|
||||
This feature will not impact reliability. It provides users with additional customization options.
|
||||
|
||||
### Compatibility
|
||||
|
||||
This feature will not break existing compatibility.
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
## Potential Issues
|
||||
|
||||
This feature will not impact performance, power, nor efficiency.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* Provide tab sizing options per-profile
|
||||
* A `tabWidthMode` value that will evenly divide the entirety of the tab bar by the number of open tabs
|
||||
* i.e. One tab will take the full width of the tab bar, two tabs will each take up half the width of the tab bar, etc.
|
||||
@@ -2,17 +2,16 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the roadmap towards delivering Windows Terminal 2.0 by Spring 2021.
|
||||
|
||||
This document outlines the roadmap towards delivering Windows Terminal 2.0 by Winter 2021.
|
||||
|
||||
## Milestones
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
| Duration | Activity | Releases |
|
||||
| --- | --- | --- |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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
|
||||
@@ -26,12 +25,15 @@ 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/) |
|
||||
| 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 | |
|
||||
| 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-30 | [1.8] in Windows Terminal Preview<br>[1.7] in Windows Terminal | |
|
||||
| 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 | |
|
||||
|
||||
## Issue Triage & Prioritization
|
||||
|
||||
@@ -84,6 +86,9 @@ 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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 13H16V6H2C0.9 6 0 6.9 0 8V13Z" fill="#CCCCCC"/>
|
||||
<path d="M32 6H16V13H32V6Z" fill="#999999"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="foreground"><stop stop-color="#000000"/></linearGradient>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="foreground"><stop stop-color="#000000"/></linearGradient>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
42
samples/PixelShaders/Animate_breathe.hlsl
Normal file
@@ -0,0 +1,42 @@
|
||||
// A simple animated shader that fades the terminal background back and forth between two colours
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
// pi and tau (2 * pi) are useful constants when using trigonometric functions
|
||||
#define TAU 6.28318530718
|
||||
|
||||
// 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 sample = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// The number of seconds the breathing effect should span
|
||||
float duration = 5.0;
|
||||
|
||||
float3 color1 = float3(0.3, 0.0, 0.5); // indigo
|
||||
float3 color2 = float3(0.1, 0.1, 0.44); // midnight blue
|
||||
|
||||
// Set background colour based on the time
|
||||
float4 backgroundColor = float4(lerp(color1, color2, 0.5 * cos(TAU / duration * Time) + 0.5), 1.0);
|
||||
|
||||
// Draw the terminal graphics over the background
|
||||
return lerp(backgroundColor, sample, sample.w);
|
||||
}
|
||||
45
samples/PixelShaders/Animate_scan.hlsl
Normal file
@@ -0,0 +1,45 @@
|
||||
// A simple animated shader that draws an inverted line that scrolls down the screen
|
||||
|
||||
// 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 color = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// Here we spread the animation over 5 seconds. We use time modulo 5 because we want
|
||||
// the timer to count to five repeatedly. We then divide the result by five again
|
||||
// to get a value between 0.0 and 1.0, which maps to our texture coordinate.
|
||||
float linePosition = Time % 5 / 5;
|
||||
|
||||
// Since TEXCOORD ranges from 0.0 to 1.0, we need to divide 1.0 by the height of the
|
||||
// texture to find out the size of a single pixel
|
||||
float lineWidth = 1.0 / Resolution.y;
|
||||
|
||||
// If the current texture coordinate is in the range of our line on the Y axis:
|
||||
if (tex.y > linePosition - lineWidth && tex.y < linePosition)
|
||||
{
|
||||
// Invert the sampled color
|
||||
color.rgb = 1.0 - color.rgb;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
@@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// 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)
|
||||
|
||||
@@ -6,7 +6,7 @@ SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
@@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// 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)
|
||||
|
||||
@@ -20,7 +20,7 @@ SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
@@ -30,8 +30,9 @@ cbuffer PixelShaderSettings {
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// 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)
|
||||
@@ -49,16 +50,22 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
Save this file as `C:\temp\invert.hlsl`, then update a profile with the setting:
|
||||
|
||||
```
|
||||
"experimental.pixelShaderEffect": "C:\\temp\\invert.hlsl"
|
||||
"experimental.pixelShaderPath": "C:\\temp\\invert.hlsl"
|
||||
```
|
||||
|
||||
Once the settings file is saved, open a terminal with the changed profile. It should now invert the colors of the screen!
|
||||
|
||||
|
||||
Default Terminal | Inverted Terminal
|
||||
---------|---------
|
||||
 | 
|
||||
|
||||
|
||||
If your shader fails to compile, the Terminal will display a warning dialog and ignore it temporarily. After fixing your shader, touch the `settings.json` file again, or open a new tab, and the Terminal will try loading the shader again.
|
||||
|
||||
## HLSL
|
||||
|
||||
The language we use to write pixel shaders is called `HLSL`. It a `C`-like language, with some restrictions.You can't allocate memory, use pointers or recursion.
|
||||
The language we use to write pixel shaders is called `HLSL`. It's a `C`-like language, with some restrictions. You can't allocate memory, use pointers or recursion.
|
||||
What you get access to is computing power in the teraflop range on decently recent GPUs. This means writing real-time raytracers or other cool effects are in the realm of possibility.
|
||||
|
||||
[shadertoy](https://shadertoy.com/) is a great site that show case what's possible with pixel shaders (albeit in `GLSL`). For example this [menger sponge](https://www.shadertoy.com/view/4scXzn). Converting from `GLSL` to `HLSL` isn't overly hard once you gotten the hang of it.
|
||||
@@ -76,7 +83,7 @@ SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
@@ -86,8 +93,9 @@ cbuffer PixelShaderSettings {
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// 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)
|
||||
@@ -130,7 +138,83 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
|
||||
Once reloaded, it should show some retro raster bars in the background, with a drop shadow to make the text more readable.
|
||||
|
||||

|
||||
|
||||
## Retro Terminal Effect
|
||||
|
||||
As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory. Feel free to modify and experiment!
|
||||
As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory.
|
||||
|
||||

|
||||
|
||||
## Animated Effects
|
||||
|
||||
You can use the `Time` value in the shader input settings to drive animated effects. `Time` is the number of seconds since the shader first loaded. Here’s a simple example with a line of inverted pixels that scrolls down the terminal (`Animate_scan.hlsl`):
|
||||
|
||||
```hlsl
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
float4 color = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// Here we spread the animation over 5 seconds. We use time modulo 5 because we want
|
||||
// the timer to count to five repeatedly. We then divide the result by five again
|
||||
// to get a value between 0.0 and 1.0, which maps to our texture coordinate.
|
||||
float linePosition = Time % 5 / 5;
|
||||
|
||||
// Since TEXCOORD ranges from 0.0 to 1.0, we need to divide 1.0 by the height of the
|
||||
// texture to find out the size of a single pixel
|
||||
float lineWidth = 1.0 / Resolution.y;
|
||||
|
||||
// If the current texture coordinate is in the range of our line on the Y axis:
|
||||
if (tex.y > linePosition - lineWidth && tex.y < linePosition)
|
||||
{
|
||||
// Invert the sampled color
|
||||
color.rgb = 1.0 - color.rgb;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
```
|
||||
What if we want an animation that goes backwards and forwards? In this example (`Animate_breathe.hlsl`), we'll make the background fade between two colours. Our `Time` value only ever goes up, so we need a way to generate a value that sweeps back and forth from `0.0` to `1.0`. Trigonometric functions like cosine are perfect for this and are very frequently used in shaders.
|
||||
|
||||
`cos()` outputs a value between `-1.0` and `1.0`. We can adjust the wave with the following formula:
|
||||
|
||||
```
|
||||
a * cos(b * (x - c)) + d
|
||||
```
|
||||
|
||||
Where `a` adjusts the amplitude, `b` adjusts the wavelength/frequency, `c` adjusts the offset along the x axis, and `d` adjusts the offset along the y axis. You can use a graphing calculator (such as the Windows Calculator) to help visualize the output and experiment:
|
||||
|
||||

|
||||
|
||||
As shown above, by halving the output and then adding `0.5`, we can shift the range of the function to `0.0` - `1.0`. Because `cos()` takes input in radians, if we multiply `x` (`Time`) by tau (`2*pi`), we are effectively setting the wavelength to `1.0`.
|
||||
|
||||
In other words, our full animation will be one second long. We can modify this duration by dividing tau by the number of seconds we want the animation to run for. In this case, we’ll go for five seconds.
|
||||
|
||||
Finally we use linear interpolation to achieve our breathing effect by selecting a color between our two chosen colors based on the output from our cosine.
|
||||
|
||||
```hlsl
|
||||
// pi and tau (2 * pi) are useful constants when using trigonometric functions
|
||||
#define TAU 6.28318530718
|
||||
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
float4 sample = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// The number of seconds the breathing effect should span
|
||||
float duration = 5.0;
|
||||
|
||||
float3 color1 = float3(0.3, 0.0, 0.5); // indigo
|
||||
float3 color2 = float3(0.1, 0.1, 0.44); // midnight blue
|
||||
|
||||
// Set background colour based on the time
|
||||
float4 backgroundColor = float4(lerp(color1, color2, 0.5 * cos(TAU / duration * Time) + 0.5), 1.0);
|
||||
|
||||
// Draw the terminal graphics over the background
|
||||
return lerp(backgroundColor, sample, sample.w);
|
||||
}
|
||||
```
|
||||
|
||||
Feel free to modify and experiment!
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
@@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// 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)
|
||||
|
||||
BIN
samples/PixelShaders/Screenshots/GraphCosine.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
samples/PixelShaders/Screenshots/TerminalDefault.PNG
Normal file
|
After Width: | Height: | Size: 444 KiB |
BIN
samples/PixelShaders/Screenshots/TerminalInvert.PNG
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
samples/PixelShaders/Screenshots/TerminalRasterbars.PNG
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
samples/PixelShaders/Screenshots/TerminalRetro.PNG
Normal file
|
After Width: | Height: | Size: 495 KiB |
36
src/buffer/out/LineRendition.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- LineRendition.hpp
|
||||
|
||||
Abstract:
|
||||
- Enumerated type for the VT line rendition attribute. This determines the
|
||||
width and height scaling with which each line is rendered.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
enum class LineRendition
|
||||
{
|
||||
SingleWidth,
|
||||
DoubleWidth,
|
||||
DoubleHeightTop,
|
||||
DoubleHeightBottom
|
||||
};
|
||||
|
||||
constexpr SMALL_RECT ScreenToBufferLine(const SMALL_RECT& line, const LineRendition lineRendition)
|
||||
{
|
||||
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
|
||||
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
|
||||
return { line.Left >> scale, line.Top, line.Right >> scale, line.Bottom };
|
||||
}
|
||||
|
||||
constexpr SMALL_RECT BufferToScreenLine(const SMALL_RECT& line, const LineRendition lineRendition)
|
||||
{
|
||||
// Use shift left to quickly multiply the Left and Right by 2 for double width lines.
|
||||
const SHORT scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
|
||||
return { line.Left << scale, line.Top, (line.Right << scale) + scale, line.Bottom };
|
||||
}
|
||||
@@ -21,6 +21,7 @@ ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute f
|
||||
_rowWidth{ rowWidth },
|
||||
_charRow{ rowWidth, this },
|
||||
_attrRow{ rowWidth, fillAttribute },
|
||||
_lineRendition{ LineRendition::SingleWidth },
|
||||
_wrapForced{ false },
|
||||
_doubleBytePadded{ false },
|
||||
_pParent{ pParent }
|
||||
@@ -35,6 +36,7 @@ ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute f
|
||||
// - <none>
|
||||
bool ROW::Reset(const TextAttribute Attr)
|
||||
{
|
||||
_lineRendition = LineRendition::SingleWidth;
|
||||
_wrapForced = false;
|
||||
_doubleBytePadded = false;
|
||||
_charRow.Reset();
|
||||
|
||||
@@ -21,6 +21,7 @@ Revision History:
|
||||
#pragma once
|
||||
|
||||
#include "AttrRow.hpp"
|
||||
#include "LineRendition.hpp"
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellIterator.hpp"
|
||||
#include "CharRow.hpp"
|
||||
@@ -48,6 +49,9 @@ public:
|
||||
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
|
||||
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
|
||||
|
||||
LineRendition GetLineRendition() const noexcept { return _lineRendition; }
|
||||
void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; }
|
||||
|
||||
SHORT GetId() const noexcept { return _id; }
|
||||
void SetId(const SHORT id) noexcept { _id = id; }
|
||||
|
||||
@@ -70,6 +74,7 @@ public:
|
||||
private:
|
||||
CharRow _charRow;
|
||||
ATTR_ROW _attrRow;
|
||||
LineRendition _lineRendition;
|
||||
SHORT _id;
|
||||
unsigned short _rowWidth;
|
||||
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
<ClInclude Include="..\cursor.h" />
|
||||
<ClInclude Include="..\DbcsAttribute.hpp" />
|
||||
<ClInclude Include="..\ICharRow.hpp" />
|
||||
<ClInclude Include="..\LineRendition.hpp" />
|
||||
<ClInclude Include="..\OutputCell.hpp" />
|
||||
<ClInclude Include="..\OutputCellIterator.hpp" />
|
||||
<ClInclude Include="..\OutputCellRect.hpp" />
|
||||
|
||||
@@ -97,7 +97,12 @@ bool Search::FindNext()
|
||||
// - Takes the found word and selects it in the screen buffer
|
||||
void Search::Select() const
|
||||
{
|
||||
_uiaData.SelectNewRegion(_coordSelStart, _coordSelEnd);
|
||||
// Convert buffer selection offsets into the equivalent screen coordinates
|
||||
// required by SelectNewRegion, taking line renditions into account.
|
||||
const auto& textBuffer = _uiaData.GetTextBuffer();
|
||||
const auto selStart = textBuffer.BufferToScreenPosition(_coordSelStart);
|
||||
const auto selEnd = textBuffer.BufferToScreenPosition(_coordSelEnd);
|
||||
_uiaData.SelectNewRegion(selStart, selEnd);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -141,7 +146,10 @@ COORD Search::s_GetInitialAnchor(IUiaData& uiaData, const Direction direction)
|
||||
const COORD textBufferEndPosition = uiaData.GetTextBufferEndPosition();
|
||||
if (uiaData.IsSelectionActive())
|
||||
{
|
||||
auto anchor = uiaData.GetSelectionAnchor();
|
||||
// Convert the screen position of the selection anchor into an equivalent
|
||||
// buffer position to start searching, taking line rendition into account.
|
||||
auto anchor = textBuffer.ScreenToBufferPosition(uiaData.GetSelectionAnchor());
|
||||
|
||||
if (direction == Direction::Forward)
|
||||
{
|
||||
textBuffer.GetSize().IncrementInBoundsCircular(anchor);
|
||||
|
||||
@@ -290,13 +290,14 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
|
||||
// We only need to compensate for leading bytes
|
||||
if (dbcsAttribute.IsLeading())
|
||||
{
|
||||
short const sBufferWidth = GetSize().Width();
|
||||
const auto cursorPosition = GetCursor().GetPosition();
|
||||
const auto lineWidth = GetLineWidth(cursorPosition.Y);
|
||||
|
||||
// If we're about to lead on the last column in the row, we need to add a padding space
|
||||
if (GetCursor().GetPosition().X == sBufferWidth - 1)
|
||||
if (cursorPosition.X == lineWidth - 1)
|
||||
{
|
||||
// set that we're wrapping for double byte reasons
|
||||
auto& row = GetRowByOffset(GetCursor().GetPosition().Y);
|
||||
auto& row = GetRowByOffset(cursorPosition.Y);
|
||||
row.SetDoubleBytePadded(true);
|
||||
|
||||
// then move the cursor forward and onto the next row
|
||||
@@ -496,7 +497,7 @@ bool TextBuffer::IncrementCursor()
|
||||
// Cursor position is stored as logical array indices (starts at 0) for the window
|
||||
// Buffer Size is specified as the "length" of the array. It would say 80 for valid values of 0-79.
|
||||
// So subtract 1 from buffer size in each direction to find the index of the final column in the buffer
|
||||
const short iFinalColumnIndex = GetSize().RightInclusive();
|
||||
const short iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().Y) - 1;
|
||||
|
||||
// Move the cursor one position to the right
|
||||
GetCursor().IncrementXPosition(1);
|
||||
@@ -635,7 +636,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
|
||||
// Return Value:
|
||||
// - Coordinate position in screen coordinates of the character just before the cursor.
|
||||
// - NOTE: Will return 0,0 if already in the top left corner
|
||||
COORD TextBuffer::_GetPreviousFromCursor() const noexcept
|
||||
COORD TextBuffer::_GetPreviousFromCursor() const
|
||||
{
|
||||
COORD coordPosition = GetCursor().GetPosition();
|
||||
|
||||
@@ -649,11 +650,11 @@ COORD TextBuffer::_GetPreviousFromCursor() const noexcept
|
||||
// Otherwise, only if we're not on the top row (e.g. we don't move anywhere in the top left corner. there is no previous)
|
||||
if (coordPosition.Y > 0)
|
||||
{
|
||||
// move the cursor to the right edge
|
||||
coordPosition.X = GetSize().RightInclusive();
|
||||
|
||||
// and up one line
|
||||
// move the cursor up one line
|
||||
coordPosition.Y--;
|
||||
|
||||
// and to the right edge
|
||||
coordPosition.X = GetLineWidth(coordPosition.Y) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -801,6 +802,78 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no
|
||||
_currentAttributes = currentAttributes;
|
||||
}
|
||||
|
||||
void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
|
||||
{
|
||||
const auto cursorPosition = GetCursor().GetPosition();
|
||||
const auto rowIndex = cursorPosition.Y;
|
||||
auto& row = GetRowByOffset(rowIndex);
|
||||
if (row.GetLineRendition() != lineRendition)
|
||||
{
|
||||
row.SetLineRendition(lineRendition);
|
||||
// If the line rendition has changed, the row can no longer be wrapped.
|
||||
row.SetWrapForced(false);
|
||||
// And if it's no longer single width, the right half of the row should be erased.
|
||||
if (lineRendition != LineRendition::SingleWidth)
|
||||
{
|
||||
const auto fillChar = L' ';
|
||||
auto fillAttrs = GetCurrentAttributes();
|
||||
fillAttrs.SetStandardErase();
|
||||
const size_t fillOffset = GetLineWidth(rowIndex);
|
||||
const size_t fillLength = GetSize().Width() - fillOffset;
|
||||
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
|
||||
row.WriteCells(fillData, fillOffset, false);
|
||||
// We also need to make sure the cursor is clamped within the new width.
|
||||
GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
|
||||
}
|
||||
_NotifyPaint(Viewport::FromDimensions({ 0, rowIndex }, { GetSize().Width(), 1 }));
|
||||
}
|
||||
}
|
||||
|
||||
void TextBuffer::ResetLineRenditionRange(const size_t startRow, const size_t endRow)
|
||||
{
|
||||
for (auto row = startRow; row < endRow; row++)
|
||||
{
|
||||
GetRowByOffset(row).SetLineRendition(LineRendition::SingleWidth);
|
||||
}
|
||||
}
|
||||
|
||||
LineRendition TextBuffer::GetLineRendition(const size_t row) const
|
||||
{
|
||||
return GetRowByOffset(row).GetLineRendition();
|
||||
}
|
||||
|
||||
bool TextBuffer::IsDoubleWidthLine(const size_t row) const
|
||||
{
|
||||
return GetLineRendition(row) != LineRendition::SingleWidth;
|
||||
}
|
||||
|
||||
SHORT TextBuffer::GetLineWidth(const size_t row) const
|
||||
{
|
||||
// Use shift right to quickly divide the width by 2 for double width lines.
|
||||
const auto scale = IsDoubleWidthLine(row) ? 1 : 0;
|
||||
return GetSize().Width() >> scale;
|
||||
}
|
||||
|
||||
COORD TextBuffer::ClampPositionWithinLine(const COORD position) const
|
||||
{
|
||||
const SHORT rightmostColumn = GetLineWidth(position.Y) - 1;
|
||||
return { std::min(position.X, rightmostColumn), position.Y };
|
||||
}
|
||||
|
||||
COORD TextBuffer::ScreenToBufferPosition(const COORD position) const
|
||||
{
|
||||
// Use shift right to quickly divide the X pos by 2 for double width lines.
|
||||
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 auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
return { position.X << scale, position.Y };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Resets the text contents of this buffer with the default character
|
||||
// and the default current color attributes
|
||||
@@ -1425,9 +1498,11 @@ bool TextBuffer::MoveToPreviousGlyph(til::point& pos) const
|
||||
// - blockSelection: when enabled, only get the rectangular text region,
|
||||
// as opposed to the text extending to the left/right
|
||||
// buffer margins
|
||||
// - bufferCoordinates: when enabled, treat the coordinates as relative to
|
||||
// the buffer rather than the screen.
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection) const
|
||||
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const
|
||||
{
|
||||
std::vector<SMALL_RECT> textRects;
|
||||
|
||||
@@ -1461,6 +1536,13 @@ const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, b
|
||||
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
|
||||
}
|
||||
|
||||
// If we were passed screen coordinates, convert the given range into
|
||||
// equivalent buffer offsets, taking line rendition into account.
|
||||
if (!bufferCoordinates)
|
||||
{
|
||||
textRow = ScreenToBufferLine(textRow, GetLineRendition(row));
|
||||
}
|
||||
|
||||
_ExpandTextRow(textRow);
|
||||
textRects.emplace_back(textRow);
|
||||
}
|
||||
@@ -2044,7 +2126,6 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
|
||||
|
||||
const short cOldRowsTotal = cOldLastChar.Y + 1;
|
||||
const short cOldColsTotal = oldBuffer.GetSize().Width();
|
||||
|
||||
COORD cNewCursorPos = { 0 };
|
||||
bool fFoundCursorPos = false;
|
||||
@@ -2056,9 +2137,19 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
{
|
||||
// Fetch the row and its "right" which is the last printable character.
|
||||
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
|
||||
const short cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
|
||||
const CharRow& charRow = row.GetCharRow();
|
||||
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
|
||||
|
||||
// If we're starting a new row, try and preserve the line rendition
|
||||
// from the row in the original buffer.
|
||||
const auto newBufferPos = newBuffer.GetCursor().GetPosition();
|
||||
if (newBufferPos.X == 0)
|
||||
{
|
||||
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y);
|
||||
newRow.SetLineRendition(row.GetLineRendition());
|
||||
}
|
||||
|
||||
// There is a special case here. If the row has a "wrap"
|
||||
// flag on it, but the right isn't equal to the width (one
|
||||
// index past the final valid index in the row) then there
|
||||
|
||||
@@ -122,6 +122,16 @@ public:
|
||||
|
||||
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
|
||||
|
||||
void SetCurrentLineRendition(const LineRendition lineRendition);
|
||||
void ResetLineRenditionRange(const size_t startRow, const size_t endRow);
|
||||
LineRendition GetLineRendition(const size_t row) const;
|
||||
bool IsDoubleWidthLine(const size_t row) const;
|
||||
|
||||
SHORT GetLineWidth(const size_t row) const;
|
||||
COORD ClampPositionWithinLine(const COORD position) const;
|
||||
COORD ScreenToBufferPosition(const COORD position) const;
|
||||
COORD BufferToScreenPosition(const COORD position) const;
|
||||
|
||||
void Reset();
|
||||
|
||||
[[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept;
|
||||
@@ -141,7 +151,7 @@ public:
|
||||
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
|
||||
bool MoveToPreviousGlyph(til::point& pos) const;
|
||||
|
||||
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
|
||||
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const;
|
||||
|
||||
void AddHyperlinkToMap(std::wstring_view uri, uint16_t id);
|
||||
std::wstring GetHyperlinkUriFromId(uint16_t id) const;
|
||||
@@ -212,7 +222,7 @@ private:
|
||||
|
||||
void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept;
|
||||
|
||||
COORD _GetPreviousFromCursor() const noexcept;
|
||||
COORD _GetPreviousFromCursor() const;
|
||||
|
||||
void _SetWrapOnCurrentRow();
|
||||
void _AdjustWrapOnCurrentRow(const bool fSet);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<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>
|
||||
<!--
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
||||
IgnorableNamespaces="uap mp rescap">
|
||||
IgnorableNamespaces="uap mp rescap uap3">
|
||||
|
||||
<Identity
|
||||
Name="WindowsTerminalDev"
|
||||
@@ -69,21 +69,26 @@
|
||||
Enabled="false"
|
||||
DisplayName="ms-resource:AppNameDev" />
|
||||
</uap5:Extension>
|
||||
<uap3:Extension Category="windows.appExtensionHost">
|
||||
<uap3:AppExtensionHost>
|
||||
<uap3:Name>com.microsoft.windows.terminal.settings</uap3:Name>
|
||||
</uap3:AppExtensionHost>
|
||||
</uap3:Extension>
|
||||
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
|
||||
<com:Class Id="9f156763-7844-4dc4-b2b1-901f640f5155" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
<com:Class Id="52065414-e077-47ec-a3ac-1cc5455e1b54" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
</com:SurrogateServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
<desktop4:Extension Category="windows.fileExplorerContextMenus">
|
||||
<desktop4:FileExplorerContextMenus>
|
||||
<desktop5:ItemType Type="Directory">
|
||||
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
<desktop5:Verb Id="OpenTerminalDev" Clsid="52065414-e077-47ec-a3ac-1cc5455e1b54" />
|
||||
</desktop5:ItemType>
|
||||
<desktop5:ItemType Type="Directory\Background">
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
<desktop5:Verb Id="OpenTerminalDev" Clsid="52065414-e077-47ec-a3ac-1cc5455e1b54" />
|
||||
</desktop5:ItemType>
|
||||
</desktop4:FileExplorerContextMenus>
|
||||
</desktop4:Extension>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
|
||||
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap mp rescap">
|
||||
IgnorableNamespaces="uap mp rescap uap3">
|
||||
|
||||
<Identity
|
||||
Name="Microsoft.WindowsTerminalPreview"
|
||||
@@ -64,6 +64,11 @@
|
||||
<desktop:ExecutionAlias Alias="wt.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
<uap3:Extension Category="windows.appExtensionHost">
|
||||
<uap3:AppExtensionHost>
|
||||
<uap3:Name>com.microsoft.windows.terminal.settings</uap3:Name>
|
||||
</uap3:AppExtensionHost>
|
||||
</uap3:Extension>
|
||||
<uap5:Extension Category="windows.startupTask">
|
||||
<uap5:StartupTask
|
||||
TaskId="StartTerminalOnLoginTask"
|
||||
@@ -73,17 +78,17 @@
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
|
||||
<com:Class Id="9f156763-7844-4dc4-b2b1-901f640f5155" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
<com:Class Id="02db545a-3e20-46de-83a5-1329b1e88b6b" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
</com:SurrogateServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
<desktop4:Extension Category="windows.fileExplorerContextMenus">
|
||||
<desktop4:FileExplorerContextMenus>
|
||||
<desktop5:ItemType Type="Directory">
|
||||
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
<desktop5:Verb Id="OpenTerminalPre" Clsid="02db545a-3e20-46de-83a5-1329b1e88b6b" />
|
||||
</desktop5:ItemType>
|
||||
<desktop5:ItemType Type="Directory\Background">
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
<desktop5:Verb Id="OpenTerminalPre" Clsid="02db545a-3e20-46de-83a5-1329b1e88b6b" />
|
||||
</desktop5:ItemType>
|
||||
</desktop4:FileExplorerContextMenus>
|
||||
</desktop4:Extension>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
|
||||
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap mp rescap">
|
||||
IgnorableNamespaces="uap mp rescap uap3">
|
||||
|
||||
<Identity
|
||||
Name="Microsoft.WindowsTerminal"
|
||||
@@ -64,6 +64,11 @@
|
||||
<desktop:ExecutionAlias Alias="wt.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
<uap3:Extension Category="windows.appExtensionHost">
|
||||
<uap3:AppExtensionHost>
|
||||
<uap3:Name>com.microsoft.windows.terminal.settings</uap3:Name>
|
||||
</uap3:AppExtensionHost>
|
||||
</uap3:Extension>
|
||||
<uap5:Extension Category="windows.startupTask">
|
||||
<uap5:StartupTask
|
||||
TaskId="StartTerminalOnLoginTask"
|
||||
@@ -81,10 +86,10 @@
|
||||
<desktop4:Extension Category="windows.fileExplorerContextMenus">
|
||||
<desktop4:FileExplorerContextMenus>
|
||||
<desktop5:ItemType Type="Directory">
|
||||
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
<desktop5:Verb Id="OpenTerminalHere" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
<desktop5:ItemType Type="Directory\Background">
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
<desktop5:Verb Id="OpenTerminalHere" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
</desktop4:FileExplorerContextMenus>
|
||||
</desktop4:Extension>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
@@ -42,6 +42,8 @@ namespace SettingsModelLocalTests
|
||||
TEST_METHOD(TestAutogeneratedName);
|
||||
TEST_METHOD(TestLayerOnAutogeneratedName);
|
||||
|
||||
TEST_METHOD(TestGenerateCommandline);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -275,14 +277,6 @@ namespace SettingsModelLocalTests
|
||||
|
||||
void CommandTests::TestAutogeneratedName()
|
||||
{
|
||||
// Tests run in Helix can't report Skipped until GH#7286 is resolved.
|
||||
// Set ignore flag to make Helix run completely overlook it.
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Ignore", L"True")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
// This test to be corrected as a part of GH#7281
|
||||
|
||||
// This test ensures that we'll correctly create commands for actions
|
||||
// that don't have given names, pursuant to the spec in GH#6532.
|
||||
|
||||
@@ -369,4 +363,145 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestGenerateCommandline()
|
||||
{
|
||||
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
|
||||
|
||||
const std::string commands0String{ R"([
|
||||
{
|
||||
"name":"action0",
|
||||
"command": { "action": "newWindow" }
|
||||
},
|
||||
{
|
||||
"name":"action1",
|
||||
"command": { "action": "newTab", "profile": "foo" }
|
||||
},
|
||||
{
|
||||
"name":"action2",
|
||||
"command": { "action": "newWindow", "profile": "foo" }
|
||||
},
|
||||
{
|
||||
"name":"action3",
|
||||
"command": { "action": "newWindow", "commandline": "bar.exe" }
|
||||
},
|
||||
{
|
||||
"name":"action4",
|
||||
"command": { "action": "newWindow", "commandline": "pop.exe ya ha ha" }
|
||||
},
|
||||
{
|
||||
"name":"action5",
|
||||
"command": { "action": "newWindow", "commandline": "pop.exe \"ya ha ha\"" }
|
||||
},
|
||||
{
|
||||
"name":"action6",
|
||||
"command": { "action": "newWindow", "startingDirectory":"C:\\foo", "commandline": "bar.exe" }
|
||||
},
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(7u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
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);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
VERIFY_ARE_EQUAL(L"", cmdline);
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
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);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
VERIFY_ARE_EQUAL(L"--profile \"foo\"", cmdline);
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
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);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
VERIFY_ARE_EQUAL(L"--profile \"foo\"", cmdline);
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action3");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
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);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
VERIFY_ARE_EQUAL(L"-- \"bar.exe\"", cmdline);
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action4");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
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);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(L"-- \"pop.exe ya ha ha\"", terminalArgs.ToCommandline());
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action5");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
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);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(L"-- \"pop.exe \"ya ha ha\"\"", terminalArgs.ToCommandline());
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action6");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
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);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\foo\" -- \"bar.exe\"", terminalArgs.ToCommandline());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
|
||||
namespace SettingsModelLocalTests
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
||||
@@ -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::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
|
||||
namespace SettingsModelLocalTests
|
||||
{
|
||||
@@ -84,7 +84,7 @@ namespace SettingsModelLocalTests
|
||||
"initialPosition": ",",
|
||||
"launchMode": "default",
|
||||
"alwaysOnTop": false,
|
||||
|
||||
"inputServiceWarning": true,
|
||||
"copyOnSelect": false,
|
||||
"copyFormatting": "all",
|
||||
"wordDelimiters": " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502",
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<ClCompile Include="CommandTests.cpp" />
|
||||
<ClCompile Include="DeserializationTests.cpp" />
|
||||
<ClCompile Include="SerializationTests.cpp" />
|
||||
<ClCompile Include="TerminalSettingsTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -57,7 +58,7 @@
|
||||
|
||||
<!-- If you don't reference these projects here, the
|
||||
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
543
src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp
Normal file
@@ -0,0 +1,543 @@
|
||||
// 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 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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(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 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.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.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.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.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, 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
|
||||
}
|
||||
}
|
||||
@@ -24,18 +24,18 @@ public:
|
||||
// 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::KeyMapping& keymap,
|
||||
const winrt::Microsoft::Terminal::TerminalControl::KeyChord& kc)
|
||||
const winrt::Microsoft::Terminal::Control::KeyChord& kc)
|
||||
{
|
||||
std::wstring buffer{ L"" };
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Ctrl))
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Control::KeyModifiers::Ctrl))
|
||||
{
|
||||
buffer += L"Ctrl+";
|
||||
}
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Shift))
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Control::KeyModifiers::Shift))
|
||||
{
|
||||
buffer += L"Shift+";
|
||||
}
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Alt))
|
||||
if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Control::KeyModifiers::Alt))
|
||||
{
|
||||
buffer += L"Alt+";
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ Author(s):
|
||||
#include <WexTestClass.h>
|
||||
#include <json.h>
|
||||
#include "consoletaeftemplates.hpp"
|
||||
#include "winrtTaefTemplates.hpp"
|
||||
|
||||
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
|
||||
#include "winrt/Windows.UI.Xaml.Markup.h"
|
||||
|
||||
@@ -6,16 +6,24 @@
|
||||
|
||||
#include "../types/inc/utils.hpp"
|
||||
#include "../TerminalApp/TerminalPage.h"
|
||||
#include "../TerminalApp/AppLogic.h"
|
||||
#include "../TerminalApp/AppCommandlineArgs.h"
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace ::TerminalApp;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
namespace appImpl = TerminalApp::implementation;
|
||||
}
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
// TODO:microsoft/terminal#3838:
|
||||
@@ -64,6 +72,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TEST_METHOD(TestMultipleSplitPaneSizes);
|
||||
|
||||
TEST_METHOD(TestFindTargetWindow);
|
||||
TEST_METHOD(TestFindTargetWindowHelp);
|
||||
TEST_METHOD(TestFindTargetWindowVersion);
|
||||
|
||||
private:
|
||||
void _buildCommandlinesHelper(AppCommandlineArgs& appArgs,
|
||||
const size_t expectedSubcommands,
|
||||
@@ -620,7 +632,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"--tabColor", L"#009999" };
|
||||
const auto expectedColor = Microsoft::Console::Utils::ColorFromHexString("#009999");
|
||||
const auto expectedColor = ::Microsoft::Console::Utils::ColorFromHexString("#009999");
|
||||
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
@@ -1262,7 +1274,7 @@ namespace TerminalAppLocalTests
|
||||
void CommandlineTest::TestSimpleExecuteCommandlineAction()
|
||||
{
|
||||
ExecuteCommandlineArgs args{ L"new-tab" };
|
||||
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
auto actions = winrt::TerminalApp::implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
VERIFY_ARE_EQUAL(1u, actions.size());
|
||||
auto actionAndArgs = actions.at(0);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
@@ -1281,7 +1293,7 @@ namespace TerminalAppLocalTests
|
||||
void CommandlineTest::TestMultipleCommandExecuteCommandlineAction()
|
||||
{
|
||||
ExecuteCommandlineArgs args{ L"new-tab ; split-pane" };
|
||||
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
auto actions = winrt::TerminalApp::implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
VERIFY_ARE_EQUAL(2u, actions.size());
|
||||
{
|
||||
auto actionAndArgs = actions.at(0);
|
||||
@@ -1317,7 +1329,7 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
// -H and -V cannot be combined.
|
||||
ExecuteCommandlineArgs args{ L"split-pane -H -V" };
|
||||
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
auto actions = winrt::TerminalApp::implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
VERIFY_ARE_EQUAL(0u, actions.size());
|
||||
}
|
||||
|
||||
@@ -1578,4 +1590,209 @@ 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());
|
||||
|
||||
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(WindowingBehaviorUseAnyExisting, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
}
|
||||
{
|
||||
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());
|
||||
|
||||
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"\"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());
|
||||
|
||||
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 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());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
|
||||
result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseAnyExisting);
|
||||
VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent, result.WindowId());
|
||||
VERIFY_ARE_EQUAL(L"", result.WindowName());
|
||||
}
|
||||
{
|
||||
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());
|
||||
|
||||
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(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());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::TestFindTargetWindowHelp()
|
||||
{
|
||||
Log::Comment(L"--help should always create a new window");
|
||||
|
||||
// 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());
|
||||
|
||||
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());
|
||||
};
|
||||
|
||||
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" });
|
||||
}
|
||||
|
||||
void CommandlineTest::TestFindTargetWindowVersion()
|
||||
{
|
||||
Log::Comment(L"--version should always create a new window");
|
||||
|
||||
// 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());
|
||||
|
||||
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());
|
||||
};
|
||||
|
||||
testHelper(std::vector<winrt::hstring>{ L"wt.exe", L"--version" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
#include "pch.h"
|
||||
#include "../TerminalApp/CommandLinePaletteItem.h"
|
||||
#include "../TerminalApp/CommandPalette.h"
|
||||
#include "../CppWinrtTailored.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::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
@@ -29,187 +30,203 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void FilteredCommandTests::VerifyHighlighting()
|
||||
{
|
||||
const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"AAAAAABBBBBBCCC") };
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter equals to the string");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"AAAAAABBBBBBCCC";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter with first character matching");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"A";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 2u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter with other case");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"a";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 2u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter matching several characters");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"ab";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 4u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAA");
|
||||
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"B");
|
||||
VERIFY_IS_TRUE(segments.GetAt(2).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(3).TextSegment(), L"BBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(3).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with non matching filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"abcd";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
auto result = RunOnUIThread([]() {
|
||||
const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"AAAAAABBBBBBCCC") };
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter equals to the string");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"AAAAAABBBBBBCCC";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter with first character matching");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"A";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 2u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter with other case");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"a";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 2u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with filter matching several characters");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"ab";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 4u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A");
|
||||
VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAA");
|
||||
VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"B");
|
||||
VERIFY_IS_TRUE(segments.GetAt(2).IsHighlighted());
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(3).TextSegment(), L"BBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(3).IsHighlighted());
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing command name segmentation with non matching filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"abcd";
|
||||
auto segments = filteredCommand->_computeHighlightedName().Segments();
|
||||
VERIFY_ARE_EQUAL(segments.Size(), 1u);
|
||||
VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC");
|
||||
VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted());
|
||||
}
|
||||
});
|
||||
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void FilteredCommandTests::VerifyWeight()
|
||||
{
|
||||
const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"AAAAAABBBBBBCCC") };
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 0);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 0);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter equals to the string");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"AAAAAABBBBBBCCC";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 30); // 1 point for the first char and 2 points for the 14 consequent ones + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter with first character matching");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"A";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter with other case");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"a";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter matching several characters");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"ab";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 3); // 1 point for the first char match + 1 point for the beginning of the word + 1 point for the match of "b"
|
||||
}
|
||||
auto result = RunOnUIThread([]() {
|
||||
const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"AAAAAABBBBBBCCC") };
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 0);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 0);
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter equals to the string");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"AAAAAABBBBBBCCC";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 30); // 1 point for the first char and 2 points for the 14 consequent ones + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter with first character matching");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"A";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter with other case");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"a";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing weight of command with filter matching several characters");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"ab";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
auto weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(weight, 3); // 1 point for the first char match + 1 point for the beginning of the word + 1 point for the match of "b"
|
||||
}
|
||||
});
|
||||
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void FilteredCommandTests::VerifyCompare()
|
||||
{
|
||||
const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"AAAAAABBBBBBCCC") };
|
||||
const auto paletteItem2{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"BBBBBCCC") };
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
auto result = RunOnUIThread([]() {
|
||||
const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"AAAAAABBBBBBCCC") };
|
||||
const auto paletteItem2{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"BBBBBCCC") };
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with no filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
filteredCommand->_Weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with empty filter");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
filteredCommand->_Weight = filteredCommand->_computeWeight();
|
||||
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
filteredCommand2->_Filter = L"";
|
||||
filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName();
|
||||
filteredCommand2->_Weight = filteredCommand2->_computeWeight();
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
filteredCommand2->_Filter = L"";
|
||||
filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName();
|
||||
filteredCommand2->_Weight = filteredCommand2->_computeWeight();
|
||||
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with different weights");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"B";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
filteredCommand->_Weight = filteredCommand->_computeWeight();
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
{
|
||||
Log::Comment(L"Testing comparison of commands with different weights");
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
filteredCommand->_Filter = L"B";
|
||||
filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName();
|
||||
filteredCommand->_Weight = filteredCommand->_computeWeight();
|
||||
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
filteredCommand2->_Filter = L"B";
|
||||
filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName();
|
||||
filteredCommand2->_Weight = filteredCommand2->_computeWeight();
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
filteredCommand2->_Filter = L"B";
|
||||
filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName();
|
||||
filteredCommand2->_Weight = filteredCommand2->_computeWeight();
|
||||
|
||||
VERIFY_IS_TRUE(filteredCommand->Weight() < filteredCommand2->Weight()); // Second command gets more points due to the beginning of the word
|
||||
VERIFY_IS_FALSE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
VERIFY_IS_TRUE(filteredCommand->Weight() < filteredCommand2->Weight()); // Second command gets more points due to the beginning of the word
|
||||
VERIFY_IS_FALSE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
});
|
||||
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void FilteredCommandTests::VerifyCompareIgnoreCase()
|
||||
{
|
||||
const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"a") };
|
||||
const auto paletteItem2{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"B") };
|
||||
{
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
auto result = RunOnUIThread([]() {
|
||||
const auto paletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"a") };
|
||||
const auto paletteItem2{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(L"B") };
|
||||
{
|
||||
const auto filteredCommand = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem);
|
||||
const auto filteredCommand2 = winrt::make_self<winrt::TerminalApp::implementation::FilteredCommand>(paletteItem2);
|
||||
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight());
|
||||
VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2));
|
||||
}
|
||||
});
|
||||
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalApp/TerminalPage.h"
|
||||
#include "../TerminalApp/TerminalSettings.h"
|
||||
#include "../LocalTests_SettingsModel/TestUtils.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
@@ -13,7 +12,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::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
@@ -34,15 +33,6 @@ 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);
|
||||
@@ -83,487 +73,6 @@ 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`,
|
||||
|
||||
@@ -330,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 _DuplicateTabViewItem on tab 1
|
||||
// * Try calling _DuplicateFocusedTab on tab 1
|
||||
// * No new tab should be created (and more importantly, the app should not crash)
|
||||
//
|
||||
// Created to test GH#2455
|
||||
@@ -392,7 +392,7 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Duplicate the first tab");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateTabViewItem();
|
||||
page->_DuplicateFocusedTab();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
@@ -407,7 +407,7 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Duplicate the tab, and don't crash");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateTabViewItem();
|
||||
page->_DuplicateFocusedTab();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
@@ -868,13 +868,33 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(4u, page->_mruTabs.Size());
|
||||
|
||||
Log::Comment(L"give alphabetical names to all switch tab actions");
|
||||
RunOnUIThread([&page]() {
|
||||
TestOnUIThread([&page]() {
|
||||
page->_GetTerminalTabImpl(page->_tabs.GetAt(0))->Title(L"a");
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
page->_GetTerminalTabImpl(page->_tabs.GetAt(1))->Title(L"b");
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
page->_GetTerminalTabImpl(page->_tabs.GetAt(2))->Title(L"c");
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
page->_GetTerminalTabImpl(page->_tabs.GetAt(3))->Title(L"d");
|
||||
});
|
||||
|
||||
TestOnUIThread([&page]() {
|
||||
Log::Comment(L"Sanity check the titles of our tabs are what we set them to.");
|
||||
|
||||
VERIFY_ARE_EQUAL(L"a", page->_tabs.GetAt(0).Title());
|
||||
VERIFY_ARE_EQUAL(L"b", page->_tabs.GetAt(1).Title());
|
||||
VERIFY_ARE_EQUAL(L"c", page->_tabs.GetAt(2).Title());
|
||||
VERIFY_ARE_EQUAL(L"d", page->_tabs.GetAt(3).Title());
|
||||
|
||||
VERIFY_ARE_EQUAL(L"d", page->_mruTabs.GetAt(0).Title());
|
||||
VERIFY_ARE_EQUAL(L"c", page->_mruTabs.GetAt(1).Title());
|
||||
VERIFY_ARE_EQUAL(L"b", page->_mruTabs.GetAt(2).Title());
|
||||
VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title());
|
||||
});
|
||||
|
||||
Log::Comment(L"Change the tab switch order to MRU switching");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::MostRecentlyUsed);
|
||||
@@ -897,18 +917,30 @@ namespace TerminalAppLocalTests
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the third tab");
|
||||
RunOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true);
|
||||
// In the course of a single tick, the Command Palette will:
|
||||
// * open
|
||||
// * select the proper tab from the mru's list
|
||||
// * raise an event for _filteredActionsView().SelectionChanged to
|
||||
// immediately preview the new tab
|
||||
// * raise a _SwitchToTabRequestedHandlers event
|
||||
// * then dismiss itself, because we can't fake holing down an
|
||||
// anchor key in the tests
|
||||
});
|
||||
|
||||
const auto palette = winrt::get_self<implementation::CommandPalette>(page->CommandPalette());
|
||||
TestOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(L"c", page->_mruTabs.GetAt(0).Title());
|
||||
VERIFY_ARE_EQUAL(L"d", page->_mruTabs.GetAt(1).Title());
|
||||
VERIFY_ARE_EQUAL(L"b", page->_mruTabs.GetAt(2).Title());
|
||||
VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title());
|
||||
});
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, palette->_switcherStartIdx, L"Verify the index is 1 as we went right");
|
||||
VERIFY_ARE_EQUAL(implementation::CommandPaletteMode::TabSwitchMode, palette->_currentMode, L"Verify we are in the tab switcher mode");
|
||||
const auto palette = winrt::get_self<winrt::TerminalApp::implementation::CommandPalette>(page->CommandPalette());
|
||||
|
||||
Log::Comment(L"Verify command palette preserves MRU order of tabs");
|
||||
VERIFY_ARE_EQUAL(4u, palette->_filteredActions.Size());
|
||||
VERIFY_ARE_EQUAL(L"d", palette->_filteredActions.GetAt(0).Item().Name());
|
||||
VERIFY_ARE_EQUAL(L"c", palette->_filteredActions.GetAt(1).Item().Name());
|
||||
VERIFY_ARE_EQUAL(L"b", palette->_filteredActions.GetAt(2).Item().Name());
|
||||
VERIFY_ARE_EQUAL(L"a", palette->_filteredActions.GetAt(3).Item().Name());
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::implementation::CommandPaletteMode::TabSwitchMode, palette->_currentMode, L"Verify we are in the tab switcher mode");
|
||||
// At this point, the contents of the command palette's _mruTabs list is
|
||||
// still the _old_ ordering (d, c, b, a). The ordering is only updated
|
||||
// in TerminalPage::_SelectNextTab, but as we saw before, the palette
|
||||
// will also dismiss itself immediately when that's called. So we can't
|
||||
// really inspect the contents of the list in this test, unfortunately.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,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\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\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" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp">
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp uap3">
|
||||
<Identity Name="WindowsTerminal.TestHost" Publisher="CN=Windows Terminal Team" Version="1.0.0.0" />
|
||||
<mp:PhoneIdentity PhoneProductId="fba054a7-f1a1-4cb7-bb21-4949919af2f5" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
|
||||
<Properties>
|
||||
@@ -22,6 +22,13 @@
|
||||
</uap:DefaultTile>
|
||||
<uap:SplashScreen Image="taef.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.appExtensionHost">
|
||||
<uap3:AppExtensionHost>
|
||||
<uap3:Name>com.microsoft.windows.terminal.settings</uap3:Name>
|
||||
</uap3:AppExtensionHost>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</ProjectReference>
|
||||
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsEditor\Microsoft.Terminal.Settings.Editor.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\TerminalControl.vcxproj" />
|
||||
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\dll\TerminalApp.vcxproj">
|
||||
<Project>{ca5cad1a-44bd-4ac7-ac72-f16e576fdd12}</Project>
|
||||
|
||||
@@ -34,6 +34,7 @@ Author(s):
|
||||
#include <WexTestClass.h>
|
||||
#include <json.h>
|
||||
#include "consoletaeftemplates.hpp"
|
||||
#include "winrtTaefTemplates.hpp"
|
||||
|
||||
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
|
||||
#include "winrt/Windows.UI.Xaml.Markup.h"
|
||||
@@ -61,6 +62,8 @@ Author(s):
|
||||
#include <regex>
|
||||
#include <CLI11/CLI11.hpp>
|
||||
|
||||
#include <shobjidl_core.h>
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// It must be defined after CommandlineArgs.g.cpp, otherwise the compiler
|
||||
// will give you just the most impossible template errors to try and
|
||||
// decipher.
|
||||
void CommandlineArgs::Args(winrt::array_view<const winrt::hstring> const& value)
|
||||
void CommandlineArgs::Commandline(winrt::array_view<const winrt::hstring> const& value)
|
||||
{
|
||||
_args = { value.begin(), value.end() };
|
||||
}
|
||||
|
||||
winrt::com_array<winrt::hstring> CommandlineArgs::Args()
|
||||
winrt::com_array<winrt::hstring> CommandlineArgs::Commandline()
|
||||
{
|
||||
return winrt::com_array<winrt::hstring>{ _args.begin(), _args.end() };
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
|
||||
winrt::hstring CurrentDirectory() { return _cwd; };
|
||||
|
||||
void Args(winrt::array_view<const winrt::hstring> const& value);
|
||||
winrt::com_array<winrt::hstring> Args();
|
||||
void Commandline(winrt::array_view<const winrt::hstring> const& value);
|
||||
winrt::com_array<winrt::hstring> Commandline();
|
||||
|
||||
private:
|
||||
winrt::com_array<winrt::hstring> _args;
|
||||
|
||||
5
src/cascadia/Remoting/FindTargetWindowArgs.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
#include "FindTargetWindowArgs.g.cpp"
|
||||
36
src/cascadia/Remoting/FindTargetWindowArgs.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- FindTargetWindowArgs.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for determining which window a specific commandline is
|
||||
intended for. The Monarch will create one of these, then toss it over to
|
||||
TerminalApp. TerminalApp actually contains the logic for parsing a
|
||||
commandline, as well as settings like the windowing behavior. Once the
|
||||
TerminalApp determines the correct window, it'll fill in the
|
||||
ResultTargetWindow property. The monarch will then read that value out to
|
||||
invoke the commandline in the appropriate window.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FindTargetWindowArgs.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct FindTargetWindowArgs : public FindTargetWindowArgsT<FindTargetWindowArgs>
|
||||
{
|
||||
WINRT_PROPERTY(winrt::Microsoft::Terminal::Remoting::CommandlineArgs, Args, nullptr);
|
||||
WINRT_PROPERTY(int, ResultTargetWindow, -1);
|
||||
WINRT_PROPERTY(winrt::hstring, ResultTargetWindowName);
|
||||
|
||||
public:
|
||||
FindTargetWindowArgs(winrt::Microsoft::Terminal::Remoting::CommandlineArgs args) :
|
||||
_Args{ args } {};
|
||||
};
|
||||
}
|
||||
@@ -19,6 +19,15 @@
|
||||
<ClInclude Include="Monarch.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FindTargetWindowArgs.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ProposeCommandlineResult.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowActivatedArgs.h">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="MonarchFactory.h" />
|
||||
<ClInclude Include="Peasant.h">
|
||||
@@ -36,6 +45,15 @@
|
||||
<ClCompile Include="Monarch.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FindTargetWindowArgs.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProposeCommandlineResult.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowActivatedArgs.cpp">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -49,6 +67,7 @@
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="init.cpp" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= idl Files ======================== -->
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
#include "Monarch.h"
|
||||
#include "CommandlineArgs.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
#include "ProposeCommandlineResult.h"
|
||||
|
||||
#include "Monarch.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
@@ -18,6 +21,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
Monarch::Monarch() :
|
||||
_ourPID{ GetCurrentProcessId() }
|
||||
{
|
||||
try
|
||||
{
|
||||
_desktopManager = winrt::create_instance<IVirtualDesktopManager>(__uuidof(VirtualDesktopManager));
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// This is a private constructor to be used in unit tests, where we don't
|
||||
@@ -37,40 +45,57 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Add the given peasant to the list of peasants we're tracking. This Peasant may have already been assigned an ID. If it hasn't, then give it an ID.
|
||||
// - Add the given peasant to the list of peasants we're tracking. This
|
||||
// Peasant may have already been assigned an ID. If it hasn't, then give
|
||||
// it an ID.
|
||||
// Arguments:
|
||||
// - peasant: the new Peasant to track.
|
||||
// Return Value:
|
||||
// - the ID assigned to the peasant.
|
||||
uint64_t Monarch::AddPeasant(Remoting::IPeasant peasant)
|
||||
{
|
||||
// TODO:projects/5 This is terrible. There's gotta be a better way
|
||||
// of finding the first opening in a non-consecutive map of int->object
|
||||
const auto providedID = peasant.GetID();
|
||||
|
||||
if (providedID == 0)
|
||||
try
|
||||
{
|
||||
// Peasant doesn't currently have an ID. Assign it a new one.
|
||||
peasant.AssignID(_nextPeasantID++);
|
||||
// TODO:projects/5 This is terrible. There's gotta be a better way
|
||||
// of finding the first opening in a non-consecutive map of int->object
|
||||
const auto providedID = peasant.GetID();
|
||||
|
||||
if (providedID == 0)
|
||||
{
|
||||
// Peasant doesn't currently have an ID. Assign it a new one.
|
||||
peasant.AssignID(_nextPeasantID++);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Peasant already had an ID (from an older monarch). Leave that one
|
||||
// be. Make sure that the next peasant's ID is higher than it.
|
||||
_nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID;
|
||||
}
|
||||
|
||||
auto newPeasantsId = peasant.GetID();
|
||||
// Add an event listener to the peasant's WindowActivated event.
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_AddPeasant",
|
||||
TraceLoggingUInt64(providedID, "providedID", "the provided ID for the peasant"),
|
||||
TraceLoggingUInt64(newPeasantsId, "peasantID", "the ID of the new peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
return newPeasantsId;
|
||||
}
|
||||
else
|
||||
catch (...)
|
||||
{
|
||||
// Peasant already had an ID (from an older monarch). Leave that one
|
||||
// be. Make sure that the next peasant's ID is higher than it.
|
||||
_nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID;
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_AddPeasant_Failed",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// We can only get into this try/catch if the peasant died on us. So
|
||||
// the return value doesn't _really_ matter. They're not about to
|
||||
// get it.
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto newPeasantsId = peasant.GetID();
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
|
||||
// Add an event listener to the peasant's WindowActivated event.
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
|
||||
// TODO:projects/5 Wait on the peasant's PID, and remove them from the
|
||||
// map if they die. This won't work great in tests though, with fake
|
||||
// PIDs.
|
||||
|
||||
return newPeasantsId;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -79,40 +104,328 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// window".
|
||||
// Arguments:
|
||||
// - sender: the Peasant that raised this event. This might be out-of-proc!
|
||||
// - args: a bundle of the peasant ID, timestamp, and desktop ID, for the activated peasant
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::_peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
void Monarch::_peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// TODO:projects/5 Pass the desktop and timestamp of when the window was
|
||||
// activated in `args`.
|
||||
|
||||
if (auto peasant{ sender.try_as<Remoting::Peasant>() })
|
||||
{
|
||||
auto theirID = peasant.GetID();
|
||||
_setMostRecentPeasant(theirID);
|
||||
}
|
||||
HandleActivatePeasant(args);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Lookup a peasant by its ID.
|
||||
// - Lookup a peasant by its ID. If the peasant has died, this will also
|
||||
// remove the peasant from our list of peasants.
|
||||
// Arguments:
|
||||
// - peasantID: The ID Of the peasant to find
|
||||
// Return Value:
|
||||
// - the peasant if it exists in our map, otherwise null
|
||||
Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID)
|
||||
{
|
||||
auto peasantSearch = _peasants.find(peasantID);
|
||||
return peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
try
|
||||
{
|
||||
const auto peasantSearch = _peasants.find(peasantID);
|
||||
auto maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
// Ask the peasant for their PID. This will validate that they're
|
||||
// actually still alive.
|
||||
if (maybeThePeasant)
|
||||
{
|
||||
maybeThePeasant.GetPID();
|
||||
}
|
||||
return maybeThePeasant;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
// Remove the peasant from the list of peasants
|
||||
_peasants.erase(peasantID);
|
||||
|
||||
// Remove the peasant from the list of MRU windows. They're dead.
|
||||
// They can't be the MRU anymore.
|
||||
_clearOldMruEntries(peasantID);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Monarch::_setMostRecentPeasant(const uint64_t peasantID)
|
||||
// Method Description:
|
||||
// - Find the ID of the peasant with the given name. If no such peasant
|
||||
// exists, then we'll return 0. If we encounter any peasants who have died
|
||||
// during this process, then we'll remove them from the set of _peasants
|
||||
// Arguments:
|
||||
// - name: The window name to look for
|
||||
// Return Value:
|
||||
// - 0 if we didn't find the given peasant, otherwise a positive number for
|
||||
// the window's ID.
|
||||
uint64_t Monarch::_lookupPeasantIdForName(std::wstring_view name)
|
||||
{
|
||||
// TODO:projects/5 Use a heap/priority queue per-desktop to track which
|
||||
// peasant was the most recent per-desktop. When we want to get the most
|
||||
// recent of all desktops (WindowingBehavior::UseExisting), then use the
|
||||
// most recent of all desktops.
|
||||
_mostRecentPeasant = peasantID;
|
||||
if (name.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> peasantsToErase{};
|
||||
uint64_t result = 0;
|
||||
for (const auto& [id, p] : _peasants)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto otherName = p.WindowName();
|
||||
if (otherName == name)
|
||||
{
|
||||
result = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
// Normally, we'd just erase the peasant here. However, we can't
|
||||
// erase from the map while we're iterating over it like this.
|
||||
// Instead, pull a good ole Java and collect this id for removal
|
||||
// later.
|
||||
peasantsToErase.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the dead peasants we came across while iterating.
|
||||
for (const auto& id : peasantsToErase)
|
||||
{
|
||||
// Remove the peasant from the list of peasants
|
||||
_peasants.erase(id);
|
||||
// Remove the peasant from the list of MRU windows. They're dead.
|
||||
// They can't be the MRU anymore.
|
||||
_clearOldMruEntries(id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handler for the `Peasant::WindowActivated` event. We'll make a in-proc
|
||||
// copy of the WindowActivatedArgs from the peasant. That way, we won't
|
||||
// need to worry about the origin process dying when working with the
|
||||
// WindowActivatedArgs.
|
||||
// - If the peasant process dies while we're making this copy, then we'll
|
||||
// just log it and do nothing. We certainly don't want to track a dead
|
||||
// peasant.
|
||||
// - We'll pass that copy of the WindowActivatedArgs to
|
||||
// _doHandleActivatePeasant, which will actually insert the
|
||||
// WindowActivatedArgs into the list we're using to track the most recent
|
||||
// peasants.
|
||||
// Arguments:
|
||||
// - args: the WindowActivatedArgs describing when and where the peasant was activated.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::HandleActivatePeasant(const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// Start by making a local copy of these args. It's easier for us if our
|
||||
// tracking of these args is all in-proc. That way, the only thing that
|
||||
// could fail due to the peasant dying is _this first copy_.
|
||||
winrt::com_ptr<implementation::WindowActivatedArgs> localArgs{ nullptr };
|
||||
try
|
||||
{
|
||||
localArgs = winrt::make_self<implementation::WindowActivatedArgs>(args);
|
||||
// This method will actually do the hard work
|
||||
_doHandleActivatePeasant(localArgs);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_HandleActivatePeasant_Failed",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper for removing a peasant from the list of MRU peasants. We want to
|
||||
// do this both when the peasant dies, and also when the peasant is newly
|
||||
// activated (so that we don't leave an old entry for it in the list).
|
||||
// Arguments:
|
||||
// - peasantID: The ID of the peasant to remove from the MRU list
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::_clearOldMruEntries(const uint64_t peasantID)
|
||||
{
|
||||
auto result = std::find_if(_mruPeasants.begin(),
|
||||
_mruPeasants.end(),
|
||||
[peasantID](auto&& other) {
|
||||
return peasantID == other.PeasantID();
|
||||
});
|
||||
|
||||
if (result != std::end(_mruPeasants))
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_RemovedPeasantFromDesktop",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "The ID of the peasant"),
|
||||
TraceLoggingGuid(result->DesktopID(), "desktopGuid", "The GUID of the previous desktop the window was on"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
_mruPeasants.erase(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Actually handle inserting the WindowActivatedArgs into our list of MRU windows.
|
||||
// Arguments:
|
||||
// - localArgs: an in-proc WindowActivatedArgs that we should add to our list of MRU windows.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::_doHandleActivatePeasant(const winrt::com_ptr<implementation::WindowActivatedArgs>& localArgs)
|
||||
{
|
||||
const auto newLastActiveTime = localArgs->ActivatedTime().time_since_epoch().count();
|
||||
|
||||
// * Check all the current lists to look for this peasant.
|
||||
// remove it from any where it exists.
|
||||
_clearOldMruEntries(localArgs->PeasantID());
|
||||
|
||||
// * If the current desktop doesn't have a vector, add one.
|
||||
const auto desktopGuid{ localArgs->DesktopID() };
|
||||
|
||||
// * Add this args list. By using lower_bound with insert, we can get it
|
||||
// into exactly the right spot, without having to re-sort the whole
|
||||
// array.
|
||||
_mruPeasants.insert(std::lower_bound(_mruPeasants.begin(),
|
||||
_mruPeasants.end(),
|
||||
*localArgs,
|
||||
[](const auto& first, const auto& second) { return first.ActivatedTime() > second.ActivatedTime(); }),
|
||||
*localArgs);
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SetMostRecentPeasant",
|
||||
TraceLoggingUInt64(localArgs->PeasantID(), "peasantID", "the ID of the activated peasant"),
|
||||
TraceLoggingGuid(desktopGuid, "desktopGuid", "The GUID of the desktop the window is on"),
|
||||
TraceLoggingInt64(newLastActiveTime, "newLastActiveTime", "The provided localArgs->ActivatedTime()"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the ID of the MRU peasant window. If requested, will limit
|
||||
// the search to windows that are on the current desktop.
|
||||
// Arguments:
|
||||
// - limitToCurrentDesktop: if true, only return the MRU peasant that's
|
||||
// actually on the current desktop.
|
||||
// Return Value:
|
||||
// - the ID of the most recent peasant, otherwise 0 if we could not find one.
|
||||
uint64_t Monarch::_getMostRecentPeasantID(const bool limitToCurrentDesktop)
|
||||
{
|
||||
if (_mruPeasants.empty())
|
||||
{
|
||||
// We haven't yet been told the MRU peasant. Just use the first one.
|
||||
// This is just gonna be a random one, but really shouldn't happen
|
||||
// in practice. The WindowManager should set the MRU peasant
|
||||
// immediately as soon as it creates the monarch/peasant for the
|
||||
// first window.
|
||||
if (_peasants.size() > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _peasants.begin()->second.GetID();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// This shouldn't really happen. If we're the monarch, then the
|
||||
// first peasant should also _be us_. So we should be able to
|
||||
// get our own ID.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_getMostRecentPeasantID_NoPeasants",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Here, there's at least one MRU peasant.
|
||||
//
|
||||
// We're going to iterate over these peasants until we find one that both:
|
||||
// 1. Is alive
|
||||
// 2. Meets our selection criteria (do we care if it is on this desktop?)
|
||||
//
|
||||
// If the peasant is dead, then we'll remove it, and try the next one.
|
||||
// Once we find one that's alive, we'll either:
|
||||
// * check if we only want a peasant on the current desktop, and if so,
|
||||
// check if this peasant is on the current desktop.
|
||||
// - If it isn't on the current desktop, we'll loop again, on the
|
||||
// following peasant.
|
||||
// * If we don't care, then we'll just return that one.
|
||||
//
|
||||
// We're not just using an iterator because the contents of the list
|
||||
// might change while we're iterating here (if the peasant is dead we'll
|
||||
// remove it from the list).
|
||||
int positionInList = 0;
|
||||
while (_mruPeasants.cbegin() + positionInList < _mruPeasants.cend())
|
||||
{
|
||||
const auto mruWindowArgs{ *(_mruPeasants.begin() + positionInList) };
|
||||
const auto peasant{ _getPeasant(mruWindowArgs.PeasantID()) };
|
||||
if (!peasant)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_Collect_WasDead",
|
||||
TraceLoggingUInt64(mruWindowArgs.PeasantID(),
|
||||
"peasantID",
|
||||
"We thought this peasant was the MRU one, but it was actually already dead."),
|
||||
TraceLoggingGuid(mruWindowArgs.DesktopID(), "desktopGuid", "The GUID of the desktop the window is on"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
// We'll go through the loop again. We removed the current one
|
||||
// at positionInList, so the next one in positionInList will be
|
||||
// a new, different peasant.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (limitToCurrentDesktop && _desktopManager)
|
||||
{
|
||||
// Check if this peasant is actually on this desktop. We can't
|
||||
// simply get the GUID of the current desktop. We have to ask if
|
||||
// the HWND is on the current desktop.
|
||||
BOOL onCurrentDesktop{ false };
|
||||
|
||||
// SUCCEEDED_LOG will log if it failed, and return true if it SUCCEEDED
|
||||
if (SUCCEEDED_LOG(_desktopManager->IsWindowOnCurrentVirtualDesktop(reinterpret_cast<HWND>(mruWindowArgs.Hwnd()),
|
||||
&onCurrentDesktop)) &&
|
||||
onCurrentDesktop)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_Collect",
|
||||
TraceLoggingUInt64(mruWindowArgs.PeasantID(),
|
||||
"peasantID",
|
||||
"the ID of the MRU peasant for a desktop"),
|
||||
TraceLoggingGuid(mruWindowArgs.DesktopID(),
|
||||
"desktopGuid",
|
||||
"The GUID of the desktop the window is on"),
|
||||
TraceLoggingBoolean(limitToCurrentDesktop,
|
||||
"limitToCurrentDesktop",
|
||||
"True if we should only search for a window on the current desktop"),
|
||||
TraceLoggingBool(onCurrentDesktop,
|
||||
"onCurrentDesktop",
|
||||
"true if this window was in fact on the current desktop"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
return mruWindowArgs.PeasantID();
|
||||
}
|
||||
// If this window wasn't on the current desktop, another one
|
||||
// might be. We'll increment positionInList below, and try
|
||||
// again.
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_getMostRecentPeasantID_Found",
|
||||
TraceLoggingUInt64(mruWindowArgs.PeasantID(), "peasantID", "The ID of the MRU peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
return mruWindowArgs.PeasantID();
|
||||
}
|
||||
positionInList++;
|
||||
}
|
||||
|
||||
// Here, we've checked all the windows, and none of them was both alive
|
||||
// and the most recent (on this desktop). Just return 0 - the caller
|
||||
// will use this to create a new window.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_getMostRecentPeasantID_NotFound",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -122,16 +435,140 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
bool Monarch::ProposeCommandline(const Remoting::CommandlineArgs& /*args*/)
|
||||
// - true if the caller should create a new window for this commandline.
|
||||
// False otherwise - the monarch should have dispatched this commandline
|
||||
// to another window in this case.
|
||||
Remoting::ProposeCommandlineResult Monarch::ProposeCommandline(const Remoting::CommandlineArgs& args)
|
||||
{
|
||||
// TODO:projects/5
|
||||
// The branch dev/migrie/f/remote-commandlines has a more complete
|
||||
// version of this function, with a naive implementation. For now, we
|
||||
// always want to create a new window, so we'll just return true. This
|
||||
// will tell the caller that we didn't handle the commandline, and they
|
||||
// should open a new window to deal with it themselves.
|
||||
return true;
|
||||
}
|
||||
// Raise an event, to ask how to handle this commandline. We can't ask
|
||||
// the app ourselves - we exist isolated from that knowledge (and
|
||||
// dependency hell). The WindowManager will raise this up to the app
|
||||
// host, which will then ask the AppLogic, who will then parse the
|
||||
// commandline and determine the provided ID of the window.
|
||||
auto findWindowArgs{ winrt::make_self<Remoting::implementation::FindTargetWindowArgs>(args) };
|
||||
|
||||
// This is handled by some handler in-proc
|
||||
_FindTargetWindowRequestedHandlers(*this, *findWindowArgs);
|
||||
|
||||
// After the event was handled, ResultTargetWindow() will be filled with
|
||||
// the parsed result.
|
||||
const auto targetWindow = findWindowArgs->ResultTargetWindow();
|
||||
const auto targetWindowName = findWindowArgs->ResultTargetWindowName();
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline",
|
||||
TraceLoggingInt64(targetWindow, "targetWindow", "The window ID the args specified"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// If there's a valid ID returned, then let's try and find the peasant
|
||||
// that goes with it. Alternatively, if we were given a magic windowing
|
||||
// constant, we can use that to look up an appropriate peasant.
|
||||
if (targetWindow >= 0 ||
|
||||
targetWindow == WindowingBehaviorUseName ||
|
||||
targetWindow == WindowingBehaviorUseExisting ||
|
||||
targetWindow == WindowingBehaviorUseAnyExisting)
|
||||
{
|
||||
uint64_t windowID = 0;
|
||||
switch (targetWindow)
|
||||
{
|
||||
case WindowingBehaviorUseCurrent:
|
||||
case WindowingBehaviorUseExisting:
|
||||
// TODO:projects/5 for now, just use the MRU window. Technically,
|
||||
// UseExisting and UseCurrent are different.
|
||||
// UseCurrent implies that we should try to do the WT_SESSION
|
||||
// lookup to find the window that spawned this process (then
|
||||
// fall back to sameDesktop if we can't find a match). For now,
|
||||
// it's good enough to just try to find a match on this desktop.
|
||||
windowID = _getMostRecentPeasantID(true);
|
||||
break;
|
||||
case WindowingBehaviorUseAnyExisting:
|
||||
windowID = _getMostRecentPeasantID(false);
|
||||
break;
|
||||
case WindowingBehaviorUseName:
|
||||
windowID = _lookupPeasantIdForName(targetWindowName);
|
||||
break;
|
||||
default:
|
||||
windowID = ::base::saturated_cast<uint64_t>(targetWindow);
|
||||
break;
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline",
|
||||
TraceLoggingInt64(windowID,
|
||||
"windowID",
|
||||
"The actual peasant ID we evaluated the window ID as"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// If_getMostRecentPeasantID returns 0 above, then we couldn't find
|
||||
// a matching window for that style of windowing. _getPeasant will
|
||||
// return nullptr, and we'll fall through to the "create a new
|
||||
// window" branch below.
|
||||
|
||||
if (auto targetPeasant{ _getPeasant(windowID) })
|
||||
{
|
||||
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(false) };
|
||||
try
|
||||
{
|
||||
// This will raise the peasant's ExecuteCommandlineRequested
|
||||
// event, which will then ask the AppHost to handle the
|
||||
// commandline, which will then pass it to AppLogic for
|
||||
// handling.
|
||||
targetPeasant.ExecuteCommandline(args);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to propose the commandline to the peasant (it
|
||||
// died?) then just tell this process to become a new window
|
||||
// instead.
|
||||
result->WindowName(targetWindowName);
|
||||
result->ShouldCreateWindow(true);
|
||||
|
||||
// If this fails, it'll be logged in the following
|
||||
// TraceLoggingWrite statement, with succeeded=false
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_Existing",
|
||||
TraceLoggingUInt64(windowID,
|
||||
"peasantID",
|
||||
"the ID of the peasant the commandline waws intended for"),
|
||||
TraceLoggingBoolean(true, "foundMatch", "true if we found a peasant with that ID"),
|
||||
TraceLoggingBoolean(!result->ShouldCreateWindow(),
|
||||
"succeeded",
|
||||
"true if we successfully dispatched the commandline to the peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
return *result;
|
||||
}
|
||||
else if (windowID > 0)
|
||||
{
|
||||
// In this case, an ID was provided, but there's no
|
||||
// peasant with that ID. Instead, we should tell the caller that
|
||||
// they should make a new window, but _with that ID_.
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_Existing",
|
||||
TraceLoggingUInt64(windowID,
|
||||
"peasantID",
|
||||
"the ID of the peasant the commandline waws intended for"),
|
||||
TraceLoggingBoolean(false, "foundMatch", "true if we found a peasant with that ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true) };
|
||||
result->Id(windowID);
|
||||
result->WindowName(targetWindowName);
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we couldn't find an existing window. Make a new one.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_NewWindow",
|
||||
TraceLoggingInt64(targetWindow, "targetWindow", "The provided ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// In this case, no usable ID was provided. Return { true, nullopt }
|
||||
auto result = winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true);
|
||||
result->WindowName(targetWindowName);
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Monarch.g.h"
|
||||
#include "Peasant.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "WindowActivatedArgs.h"
|
||||
|
||||
// We sure different GUIDs here depending on whether we're running a Release,
|
||||
// Preview, or Dev build. This ensures that different installs don't
|
||||
@@ -30,12 +31,6 @@ constexpr GUID Monarch_clsid
|
||||
}
|
||||
};
|
||||
|
||||
enum class WindowingBehavior : uint64_t
|
||||
{
|
||||
UseNew = 0,
|
||||
UseExisting = 1,
|
||||
};
|
||||
|
||||
namespace RemotingUnitTests
|
||||
{
|
||||
class RemotingTests;
|
||||
@@ -52,7 +47,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
|
||||
uint64_t AddPeasant(winrt::Microsoft::Terminal::Remoting::IPeasant peasant);
|
||||
|
||||
bool ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
|
||||
private:
|
||||
Monarch(const uint64_t testPID);
|
||||
@@ -60,15 +58,21 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
|
||||
uint64_t _nextPeasantID{ 1 };
|
||||
uint64_t _thisPeasantID{ 0 };
|
||||
uint64_t _mostRecentPeasant{ 0 };
|
||||
WindowingBehavior _windowingBehavior{ WindowingBehavior::UseNew };
|
||||
|
||||
winrt::com_ptr<IVirtualDesktopManager> _desktopManager{ nullptr };
|
||||
|
||||
std::unordered_map<uint64_t, winrt::Microsoft::Terminal::Remoting::IPeasant> _peasants;
|
||||
|
||||
std::vector<Remoting::WindowActivatedArgs> _mruPeasants;
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _getPeasant(uint64_t peasantID);
|
||||
void _setMostRecentPeasant(const uint64_t peasantID);
|
||||
uint64_t _getMostRecentPeasantID(bool limitToCurrentDesktop);
|
||||
uint64_t _lookupPeasantIdForName(std::wstring_view name);
|
||||
|
||||
void _peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
void _doHandleActivatePeasant(const winrt::com_ptr<winrt::Microsoft::Terminal::Remoting::implementation::WindowActivatedArgs>& args);
|
||||
void _clearOldMruEntries(const uint64_t peasantID);
|
||||
|
||||
friend class RemotingUnitTests::RemotingTests;
|
||||
};
|
||||
|
||||
@@ -5,11 +5,27 @@ import "Peasant.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Remoting
|
||||
{
|
||||
|
||||
[default_interface] runtimeclass FindTargetWindowArgs {
|
||||
CommandlineArgs Args { get; };
|
||||
Int32 ResultTargetWindow;
|
||||
String ResultTargetWindowName;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ProposeCommandlineResult {
|
||||
Windows.Foundation.IReference<UInt64> Id { get; };
|
||||
String WindowName { get; };
|
||||
Boolean ShouldCreateWindow { get; }; // If you name this `CreateWindow`, the compiler will explode
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass Monarch {
|
||||
Monarch();
|
||||
|
||||
UInt64 GetPID();
|
||||
UInt64 AddPeasant(IPeasant peasant);
|
||||
Boolean ProposeCommandline(CommandlineArgs args);
|
||||
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args);
|
||||
void HandleActivatePeasant(WindowActivatedArgs args);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,6 +50,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
_initialArgs = args;
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_ExecuteCommandline",
|
||||
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
|
||||
TraceLoggingWideString(args.CurrentDirectory().c_str(), "directory", "the provided cwd"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// Raise an event with these args. The AppHost will listen for this
|
||||
// event to know when to take these args and dispatch them to a
|
||||
// currently-running window.
|
||||
@@ -63,4 +69,52 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
return _initialArgs;
|
||||
}
|
||||
|
||||
void Peasant::ActivateWindow(const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// TODO: projects/5 - somehow, pass an identifier for the current
|
||||
// desktop into this method. The Peasant shouldn't need to be able to
|
||||
// figure it out, but it will need to report it to the monarch.
|
||||
|
||||
// Store these new args as our last activated state. If a new monarch
|
||||
// comes looking, we can use this info to tell them when we were last
|
||||
// activated.
|
||||
_lastActivatedArgs = args;
|
||||
|
||||
bool successfullyNotified = false;
|
||||
// Raise our WindowActivated event, to let the monarch know we've been
|
||||
// activated.
|
||||
try
|
||||
{
|
||||
// Try/catch this, because the other side of this event is handled
|
||||
// by the monarch. The monarch might have died. If they have, this
|
||||
// will throw an exception. Just eat it, the election thread will
|
||||
// handle hooking up the new one.
|
||||
_WindowActivatedHandlers(*this, args);
|
||||
successfullyNotified = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_ActivateWindow",
|
||||
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
|
||||
TraceLoggingBoolean(successfullyNotified, "successfullyNotified", "true if we successfully notified the monarch"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieve the WindowActivatedArgs describing the last activation of this
|
||||
// peasant. New monarchs can use this state to determine when we were last
|
||||
// activated.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a WindowActivatedArgs with info about when and where we were last activated.
|
||||
Remoting::WindowActivatedArgs Peasant::GetLastActivatedArgs()
|
||||
{
|
||||
return _lastActivatedArgs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
uint64_t GetPID();
|
||||
|
||||
bool ExecuteCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
void ActivateWindow(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
WINRT_PROPERTY(winrt::hstring, WindowName);
|
||||
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs);
|
||||
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs);
|
||||
|
||||
private:
|
||||
@@ -33,6 +38,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
uint64_t _id{ 0 };
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs _initialArgs{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs _lastActivatedArgs{ nullptr };
|
||||
|
||||
friend class RemotingUnitTests::RemotingTests;
|
||||
};
|
||||
|
||||
@@ -9,10 +9,20 @@ namespace Microsoft.Terminal.Remoting
|
||||
CommandlineArgs();
|
||||
CommandlineArgs(String[] args, String cwd);
|
||||
|
||||
String[] Args { get; set; };
|
||||
String[] Commandline { get; set; };
|
||||
String CurrentDirectory();
|
||||
};
|
||||
|
||||
runtimeclass WindowActivatedArgs
|
||||
{
|
||||
WindowActivatedArgs(UInt64 peasantID, Guid desktopID, Windows.Foundation.DateTime activatedTime);
|
||||
WindowActivatedArgs(UInt64 peasantID, UInt64 hwnd, Guid desktopID, Windows.Foundation.DateTime activatedTime);
|
||||
UInt64 PeasantID { get; };
|
||||
UInt64 Hwnd { get; };
|
||||
Guid DesktopID { get; };
|
||||
Windows.Foundation.DateTime ActivatedTime { get; };
|
||||
};
|
||||
|
||||
interface IPeasant
|
||||
{
|
||||
CommandlineArgs InitialArgs { get; };
|
||||
@@ -21,7 +31,11 @@ namespace Microsoft.Terminal.Remoting
|
||||
UInt64 GetID();
|
||||
UInt64 GetPID();
|
||||
Boolean ExecuteCommandline(CommandlineArgs args);
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowActivated;
|
||||
void ActivateWindow(WindowActivatedArgs args);
|
||||
WindowActivatedArgs GetLastActivatedArgs();
|
||||
String WindowName { get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
|
||||
};
|
||||
|
||||
|
||||
5
src/cascadia/Remoting/ProposeCommandlineResult.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "ProposeCommandlineResult.h"
|
||||
#include "ProposeCommandlineResult.g.cpp"
|
||||
37
src/cascadia/Remoting/ProposeCommandlineResult.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- ProposeCommandlineResult.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for encapsulating the result of a
|
||||
Monarch::ProposeCommandline call. The monarch will be telling the new process
|
||||
whether it should create a new window or not. If the value of
|
||||
ShouldCreateWindow is false, that implies that some other window process was
|
||||
given the commandline for handling, and the caller should just exit.
|
||||
- If ShouldCreateWindow is true, the Id property may or may not contain an ID
|
||||
that the new window should use as it's ID.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ProposeCommandlineResult.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct ProposeCommandlineResult : public ProposeCommandlineResultT<ProposeCommandlineResult>
|
||||
{
|
||||
public:
|
||||
WINRT_PROPERTY(Windows::Foundation::IReference<uint64_t>, Id);
|
||||
WINRT_PROPERTY(winrt::hstring, WindowName);
|
||||
WINRT_PROPERTY(bool, ShouldCreateWindow, true);
|
||||
|
||||
public:
|
||||
ProposeCommandlineResult(bool shouldCreateWindow) :
|
||||
_ShouldCreateWindow{ shouldCreateWindow } {};
|
||||
};
|
||||
}
|
||||
5
src/cascadia/Remoting/WindowActivatedArgs.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "WindowActivatedArgs.h"
|
||||
#include "WindowActivatedArgs.g.cpp"
|
||||
61
src/cascadia/Remoting/WindowActivatedArgs.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- WindowActivatedArgs.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for encapsulating all the information about when and
|
||||
where a window was activated. This will be used by the Monarch to determine
|
||||
who the most recent peasant is.
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "WindowActivatedArgs.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct CompareWindowActivatedArgs
|
||||
{
|
||||
bool operator()(const Remoting::WindowActivatedArgs& lhs, const Remoting::WindowActivatedArgs& rhs) const
|
||||
{
|
||||
return lhs.ActivatedTime() > rhs.ActivatedTime();
|
||||
}
|
||||
};
|
||||
struct WindowActivatedArgs : public WindowActivatedArgsT<WindowActivatedArgs>
|
||||
{
|
||||
WINRT_PROPERTY(uint64_t, PeasantID, 0);
|
||||
WINRT_PROPERTY(winrt::guid, DesktopID);
|
||||
WINRT_PROPERTY(winrt::Windows::Foundation::DateTime, ActivatedTime, {});
|
||||
WINRT_PROPERTY(uint64_t, Hwnd, 0);
|
||||
|
||||
public:
|
||||
WindowActivatedArgs(uint64_t peasantID,
|
||||
uint64_t hwnd,
|
||||
winrt::guid desktopID,
|
||||
winrt::Windows::Foundation::DateTime timestamp) :
|
||||
_PeasantID{ peasantID },
|
||||
_Hwnd{ hwnd },
|
||||
_DesktopID{ desktopID },
|
||||
_ActivatedTime{ timestamp } {};
|
||||
|
||||
WindowActivatedArgs(uint64_t peasantID,
|
||||
winrt::guid desktopID,
|
||||
winrt::Windows::Foundation::DateTime timestamp) :
|
||||
WindowActivatedArgs(peasantID, 0, desktopID, timestamp){};
|
||||
|
||||
WindowActivatedArgs(const Remoting::WindowActivatedArgs& other) :
|
||||
_PeasantID{ other.PeasantID() },
|
||||
_Hwnd{ other.Hwnd() },
|
||||
_DesktopID{ other.DesktopID() },
|
||||
_ActivatedTime{ other.ActivatedTime() } {};
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(WindowActivatedArgs);
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "WindowManager.h"
|
||||
#include "MonarchFactory.h"
|
||||
#include "CommandlineArgs.h"
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
|
||||
#include "WindowManager.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
@@ -18,10 +20,29 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
WindowManager::WindowManager()
|
||||
{
|
||||
_monarchWaitInterrupt.create();
|
||||
|
||||
// Register with COM as a server for the Monarch class
|
||||
_registerAsMonarch();
|
||||
// Instantiate an instance of the Monarch. This may or may not be in-proc!
|
||||
_createMonarch();
|
||||
bool foundMonarch = false;
|
||||
while (!foundMonarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
// _createMonarchAndCallbacks will initialize _isKing
|
||||
foundMonarch = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to find the monarch,
|
||||
// stay in this jail until we do.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInCtor",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WindowManager::~WindowManager()
|
||||
@@ -32,23 +53,128 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// monarch!
|
||||
CoRevokeClassObject(_registrationHostClass);
|
||||
_registrationHostClass = 0;
|
||||
_monarchWaitInterrupt.SetEvent();
|
||||
|
||||
// A thread is joinable once it's been started. Basically this just
|
||||
// makes sure that the thread isn't just default-constructed.
|
||||
if (_electionThread.joinable())
|
||||
{
|
||||
_electionThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
|
||||
{
|
||||
const bool isKing = _areWeTheKing();
|
||||
// If we're the king, we _definitely_ want to process the arguments, we were
|
||||
// launched with them!
|
||||
//
|
||||
// Otherwise, the King will tell us if we should make a new window
|
||||
_shouldCreateWindow = isKing ||
|
||||
_monarch.ProposeCommandline(args);
|
||||
_shouldCreateWindow = _isKing;
|
||||
std::optional<uint64_t> givenID;
|
||||
winrt::hstring givenName{};
|
||||
if (!_isKing)
|
||||
{
|
||||
// The monarch may respond back "you should be a new
|
||||
// window, with ID,name of (id, name)". Really the responses are:
|
||||
// * You should not create a new window
|
||||
// * Create a new window (but without a given ID or name). The
|
||||
// Monarch will assign your ID/name later
|
||||
// * Create a new window, and you'll have this ID or name
|
||||
// - This is the case where the user provides `wt -w 1`, and
|
||||
// there's no existing window 1
|
||||
|
||||
const auto result = _monarch.ProposeCommandline(args);
|
||||
_shouldCreateWindow = result.ShouldCreateWindow();
|
||||
if (result.Id())
|
||||
{
|
||||
givenID = result.Id().Value();
|
||||
}
|
||||
givenName = result.WindowName();
|
||||
// TraceLogging doesn't have a good solution for logging an
|
||||
// optional. So we have to repeat the calls here:
|
||||
if (givenID)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(givenID.value(), "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingPointer(nullptr, "Id", "No ID provided"),
|
||||
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're the monarch, we don't need to propose anything. We're just
|
||||
// going to do it.
|
||||
//
|
||||
// However, we _do_ need to ask what our name should be. It's
|
||||
// possible someone started the _first_ wt with something like `wt
|
||||
// -w king` as the commandline - we want to make sure we set our
|
||||
// name to "king".
|
||||
//
|
||||
// The FindTargetWindow event is the WindowManager's way of saying
|
||||
// "I do not know how to figure out how to turn this list of args
|
||||
// into a window ID/name. Whoever's listening to this event does, so
|
||||
// I'll ask them". It's a convoluted way of hooking the
|
||||
// WindowManager up to AppLogic without actually telling it anything
|
||||
// about TerminalApp (or even WindowsTerminal)
|
||||
auto findWindowArgs{ winrt::make_self<Remoting::implementation::FindTargetWindowArgs>(args) };
|
||||
_raiseFindTargetWindowRequested(nullptr, *findWindowArgs);
|
||||
|
||||
const auto responseId = findWindowArgs->ResultTargetWindow();
|
||||
if (responseId > 0)
|
||||
{
|
||||
givenID = ::base::saturated_cast<uint64_t>(responseId);
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline_AsMonarch",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(givenID.value(), "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
else if (responseId == WindowingBehaviorUseName)
|
||||
{
|
||||
givenName = findWindowArgs->ResultTargetWindowName();
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline_AsMonarch",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(0, "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline_AsMonarch",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(0, "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingWideString(L"", "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
|
||||
if (_shouldCreateWindow)
|
||||
{
|
||||
// If we should create a new window, then instantiate our Peasant
|
||||
// instance, and tell that peasant to handle that commandline.
|
||||
_createOurPeasant();
|
||||
_createOurPeasant({ givenID }, givenName);
|
||||
|
||||
// Spawn a thread to wait on the monarch, and handle the election
|
||||
if (!_isKing)
|
||||
{
|
||||
_createPeasantThread();
|
||||
}
|
||||
|
||||
_peasant.ExecuteCommandline(args);
|
||||
}
|
||||
@@ -83,27 +209,273 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
|
||||
// NOTE: This can throw! Callers include:
|
||||
// - the constructor, who performs this in a loop until it successfully
|
||||
// find a a monarch
|
||||
// - the performElection method, which is called in the waitOnMonarch
|
||||
// thread. All the calls in that thread are wrapped in try/catch's
|
||||
// already.
|
||||
// - _createOurPeasant, who might do this in a loop to establish us with the
|
||||
// monarch.
|
||||
void WindowManager::_createMonarchAndCallbacks()
|
||||
{
|
||||
_createMonarch();
|
||||
// Save the result of checking if we're the king. We want to avoid
|
||||
// unnecessary calls back and forth if we can.
|
||||
_isKing = _areWeTheKing();
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ConnectedToMonarch",
|
||||
TraceLoggingUInt64(_monarch.GetPID(), "monarchPID", "The PID of the new Monarch"),
|
||||
TraceLoggingBoolean(_isKing, "isKing", "true if we are the new monarch"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
if (_peasant)
|
||||
{
|
||||
// Inform the monarch of the time we were last activated
|
||||
_monarch.HandleActivatePeasant(_peasant.GetLastActivatedArgs());
|
||||
}
|
||||
|
||||
if (!_isKing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Here, we're the king!
|
||||
//
|
||||
// This is where you should do any additional setup that might need to be
|
||||
// done when we become the king. THis will be called both for the first
|
||||
// window, and when the current monarch dies.
|
||||
|
||||
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
|
||||
}
|
||||
|
||||
bool WindowManager::_areWeTheKing()
|
||||
{
|
||||
const auto kingPID{ _monarch.GetPID() };
|
||||
const auto ourPID{ GetCurrentProcessId() };
|
||||
const auto kingPID{ _monarch.GetPID() };
|
||||
return (ourPID == kingPID);
|
||||
}
|
||||
|
||||
Remoting::IPeasant WindowManager::_createOurPeasant()
|
||||
Remoting::IPeasant WindowManager::_createOurPeasant(std::optional<uint64_t> givenID,
|
||||
const winrt::hstring& givenName)
|
||||
{
|
||||
auto p = winrt::make_self<Remoting::implementation::Peasant>();
|
||||
_peasant = *p;
|
||||
_monarch.AddPeasant(_peasant);
|
||||
if (givenID)
|
||||
{
|
||||
p->AssignID(givenID.value());
|
||||
}
|
||||
|
||||
// TODO:projects/5 Spawn a thread to wait on the monarch, and handle the election
|
||||
// If the name wasn't specified, this will be an empty string.
|
||||
p->WindowName(givenName);
|
||||
_peasant = *p;
|
||||
|
||||
// Try to add us to the monarch. If that fails, try to find a monarch
|
||||
// again, until we find one (we will eventually find us)
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
_monarch.AddPeasant(_peasant);
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Wrap this in it's own try/catch, because this can throw.
|
||||
_createMonarchAndCallbacks();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_CreateOurPeasant",
|
||||
TraceLoggingUInt64(_peasant.GetID(), "peasantID", "The ID of our new peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
return _peasant;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempt to connect to the monarch process. This might be us!
|
||||
// - For the new monarch, add us to their list of peasants.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff we're the new monarch process.
|
||||
// NOTE: This can throw!
|
||||
bool WindowManager::_performElection()
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
|
||||
// Tell the new monarch who we are. We might be that monarch!
|
||||
_monarch.AddPeasant(_peasant);
|
||||
|
||||
// This method is only called when a _new_ monarch is elected. So
|
||||
// don't do anything here that needs to be done for all monarch
|
||||
// windows. This should only be for work that's done when a window
|
||||
// _becomes_ a monarch, after the death of the previous monarch.
|
||||
return _isKing;
|
||||
}
|
||||
|
||||
void WindowManager::_createPeasantThread()
|
||||
{
|
||||
// If we catch an exception trying to get at the monarch ever, we can
|
||||
// set the _monarchWaitInterrupt, and use that to trigger a new
|
||||
// election. Though, we wouldn't be able to retry the function that
|
||||
// caused the exception in the first place...
|
||||
|
||||
_electionThread = std::thread([this] {
|
||||
_waitOnMonarchThread();
|
||||
});
|
||||
}
|
||||
|
||||
void WindowManager::_waitOnMonarchThread()
|
||||
{
|
||||
// This is the array of HANDLEs that we're going to wait on in
|
||||
// WaitForMultipleObjects below.
|
||||
// * waits[0] will be the handle to the monarch process. It gets
|
||||
// signalled when the process exits / dies.
|
||||
// * waits[1] is the handle to our _monarchWaitInterrupt event. Another
|
||||
// thread can use that to manually break this loop. We'll do that when
|
||||
// we're getting torn down.
|
||||
HANDLE waits[2];
|
||||
waits[1] = _monarchWaitInterrupt.get();
|
||||
const auto peasantID = _peasant.GetID(); // safe: _peasant is in-proc.
|
||||
|
||||
bool exitThreadRequested = false;
|
||||
while (!exitThreadRequested)
|
||||
{
|
||||
// At any point in all this, the current monarch might die. If it
|
||||
// does, we'll go straight to a new election, in the "jail"
|
||||
// try/catch below. Worst case, eventually, we'll become the new
|
||||
// monarch.
|
||||
try
|
||||
{
|
||||
// This might fail to even ask the monarch for it's PID.
|
||||
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
|
||||
FALSE,
|
||||
static_cast<DWORD>(_monarch.GetPID())) };
|
||||
|
||||
// If we fail to open the monarch, then they don't exist
|
||||
// anymore! Go straight to an election.
|
||||
if (hMonarch.get() == nullptr)
|
||||
{
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_FailedToOpenMonarch",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
exitThreadRequested = _performElection();
|
||||
continue;
|
||||
}
|
||||
|
||||
waits[0] = hMonarch.get();
|
||||
auto waitResult = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
|
||||
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0 + 0: // waits[0] was signaled, the handle to the monarch process
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchDied",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
// Connect to the new monarch, which might be us!
|
||||
// If we become the monarch, then we'll return true and exit this thread.
|
||||
exitThreadRequested = _performElection();
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 1: // waits[1] was signaled, our manual interrupt
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchWaitInterrupted",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
exitThreadRequested = true;
|
||||
break;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
// This should be impossible.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchWaitTimeout",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
exitThreadRequested = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
// Returning any other value is invalid. Just die.
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_WaitFailed",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Theoretically, if window[1] dies when we're trying to get
|
||||
// it's PID we'll get here. If we just try to do the election
|
||||
// once here, it's possible we might elect window[2], but have
|
||||
// it die before we add ourselves as a peasant. That
|
||||
// _performElection call will throw, and we wouldn't catch it
|
||||
// here, and we'd die.
|
||||
|
||||
// Instead, we're going to have a resilient election process.
|
||||
// We're going to keep trying an election, until one _doesn't_
|
||||
// throw an exception. That might mean burning through all the
|
||||
// other dying monarchs until we find us as the monarch. But if
|
||||
// this process is alive, then there's _someone_ in the line of
|
||||
// succession.
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInWaitThread",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
bool foundNewMonarch = false;
|
||||
while (!foundNewMonarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
exitThreadRequested = _performElection();
|
||||
// It doesn't matter if we're the monarch, or someone
|
||||
// else is, but if we complete the election, then we've
|
||||
// registered with a new one. We can escape this jail
|
||||
// and re-enter society.
|
||||
foundNewMonarch = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to acknowledge the results of the election,
|
||||
// stay in this jail until we do.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInNestedWaitThread",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Remoting::Peasant WindowManager::CurrentWindow()
|
||||
{
|
||||
return _peasant;
|
||||
}
|
||||
|
||||
void WindowManager::_raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args)
|
||||
{
|
||||
_FindTargetWindowRequestedHandlers(sender, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- WindowManager.h
|
||||
|
||||
Abstract:
|
||||
- The Window Manager takes care of coordinating the monarch and peasant for this
|
||||
process.
|
||||
- It's responsible for registering as a potential future monarch. It's also
|
||||
responsible for creating the Peasant for this process when it's determined
|
||||
this process should become a window process.
|
||||
- If we aren't the monarch, it's responsible for watching the current monarch
|
||||
process, and finding the new one if the current monarch dies.
|
||||
- When the monarch needs to ask the TerminalApp about how to parse a
|
||||
commandline, it'll ask by raising an event that we'll bubble up to the
|
||||
AppHost.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -20,16 +38,30 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow();
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
|
||||
private:
|
||||
bool _shouldCreateWindow{ false };
|
||||
bool _isKing{ false };
|
||||
DWORD _registrationHostClass{ 0 };
|
||||
winrt::Microsoft::Terminal::Remoting::Monarch _monarch{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
|
||||
|
||||
wil::unique_event _monarchWaitInterrupt;
|
||||
std::thread _electionThread;
|
||||
|
||||
void _registerAsMonarch();
|
||||
void _createMonarch();
|
||||
void _createMonarchAndCallbacks();
|
||||
bool _areWeTheKing();
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant();
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant(std::optional<uint64_t> givenID,
|
||||
const winrt::hstring& givenName);
|
||||
|
||||
bool _performElection();
|
||||
void _createPeasantThread();
|
||||
void _waitOnMonarchThread();
|
||||
void _raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "Peasant.idl";
|
||||
import "Monarch.idl";
|
||||
|
||||
|
||||
namespace Microsoft.Terminal.Remoting
|
||||
@@ -9,5 +10,6 @@ namespace Microsoft.Terminal.Remoting
|
||||
void ProposeCommandline(CommandlineArgs args);
|
||||
Boolean ShouldCreateWindow { get; };
|
||||
IPeasant CurrentWindow();
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="Microsoft.Terminal.Remoting.def" />
|
||||
<None Include="$(ProjectName).def" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
|
||||
37
src/cascadia/Remoting/init.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include <LibraryResources.h>
|
||||
#include <WilErrorReporting.h>
|
||||
|
||||
// Note: Generate GUID using TlgGuid.exe tool
|
||||
#pragma warning(suppress : 26477) // One of the macros uses 0/NULL. We don't have control to make it nullptr.
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hRemotingProvider,
|
||||
"Microsoft.Windows.Terminal.Remoting",
|
||||
// {d6f04aad-629f-539a-77c1-73f5c3e4aa7b}
|
||||
(0xd6f04aad, 0x629f, 0x539a, 0x77, 0xc1, 0x73, 0xf5, 0xc3, 0xe4, 0xaa, 0x7b),
|
||||
TraceLoggingOptionMicrosoftTelemetry());
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
|
||||
{
|
||||
switch (reason)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
DisableThreadLibraryCalls(hInstDll);
|
||||
TraceLoggingRegister(g_hRemotingProvider);
|
||||
Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hRemotingProvider);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (g_hRemotingProvider)
|
||||
{
|
||||
TraceLoggingUnregister(g_hRemotingProvider);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"Microsoft.Terminal.Remoting/Resources");
|
||||
@@ -7,11 +7,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// Block minwindef.h min/max macros to prevent <algorithm> conflict
|
||||
#define NOMINMAX
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMCX
|
||||
#define NOHELP
|
||||
#define NOCOMM
|
||||
|
||||
#include <unknwn.h>
|
||||
#include <ShObjIdl.h>
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#define BLOCK_TIL
|
||||
#include <LibraryIncludes.h>
|
||||
@@ -25,8 +31,6 @@
|
||||
|
||||
#include <wil/cppwinrt.h>
|
||||
|
||||
#include <unknwn.h>
|
||||
|
||||
#include <hstring.h>
|
||||
|
||||
#include <winrt/Windows.ApplicationModel.h>
|
||||
@@ -38,7 +42,7 @@
|
||||
// Including TraceLogging essentials for the binary
|
||||
#include <TraceLoggingProvider.h>
|
||||
#include <winmeta.h>
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hRemotingProvider);
|
||||
#include <telemetry/ProjectTelemetry.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "OpenTerminalHere.h"
|
||||
#include "../WinRTUtils/inc/WtExeUtils.h"
|
||||
#include <ShlObj.h>
|
||||
|
||||
// TODO GH#6112: Localize these strings
|
||||
@@ -10,103 +11,10 @@ static constexpr std::wstring_view VerbDisplayName{ L"Open in Windows Terminal"
|
||||
static constexpr std::wstring_view VerbDevBuildDisplayName{ L"Open in Windows Terminal (Dev Build)" };
|
||||
static constexpr std::wstring_view VerbName{ L"WindowsTerminalOpenHere" };
|
||||
|
||||
static constexpr std::wstring_view WtExe{ L"wt.exe" };
|
||||
static constexpr std::wstring_view WtdExe{ L"wtd.exe" };
|
||||
static constexpr std::wstring_view WindowsTerminalExe{ L"WindowsTerminal.exe" };
|
||||
|
||||
static constexpr std::wstring_view LocalAppDataAppsPath{ L"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\" };
|
||||
|
||||
// This code is aggressively copied from
|
||||
// https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/
|
||||
// Win7Samples/winui/shell/appshellintegration/ExplorerCommandVerb/ExplorerCommandVerb.cpp
|
||||
|
||||
// Function Description:
|
||||
// - This is a helper to determine if we're running as a part of the Dev Build
|
||||
// Package or the release package. We'll need to return different text, icons,
|
||||
// and use different commandlines depending on which one the user requested.
|
||||
// - Uses a C++11 "magic static" to make sure this is only computed once.
|
||||
// - If we can't determine if it's the dev build or not, we'll default to true
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if we believe this extension is being run in the dev build package.
|
||||
static bool IsDevBuild()
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
static bool isDevBuild = []() -> bool {
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
const auto id = package.Id();
|
||||
const std::wstring name{ id.FullName() };
|
||||
// Does our PFN start with WindowsTerminalDev?
|
||||
return name.rfind(L"WindowsTerminalDev", 0) == 0;
|
||||
}
|
||||
CATCH_LOG();
|
||||
return true;
|
||||
}();
|
||||
|
||||
return isDevBuild;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function for getting the path to the appropriate executable to use
|
||||
// for this instance of the shell extension. If we're running the dev build,
|
||||
// it should be a `wtd.exe`, but if we're preview or release, we want to make
|
||||
// sure to get the correct `wt.exe` that corresponds to _us_.
|
||||
// - If we're unpackaged, this needs to get us `WindowsTerminal.exe`, because
|
||||
// the `wt*exe` alias won't have been installed for this install.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the full path to the exe, one of `wt.exe`, `wtd.exe`, or `WindowsTerminal.exe`.
|
||||
static std::wstring _getExePath()
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
static const std::wstring exePath = []() -> std::wstring {
|
||||
// First, check a packaged location for the exe. If we've got a package
|
||||
// family name, that means we're one of the packaged Dev build, packaged
|
||||
// Release build, or packaged Preview build.
|
||||
//
|
||||
// If we're the preview or release build, there's no way of knowing if the
|
||||
// `wt.exe` on the %PATH% is us or not. Fortunately, _our_ execution alias
|
||||
// is located in "%LOCALAPPDATA%\Microsoft\WindowsApps\<our package family
|
||||
// name>", _always_, so we can use that to look up the exe easier.
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
const auto id = package.Id();
|
||||
const std::wstring pfn{ id.FamilyName() };
|
||||
if (!pfn.empty())
|
||||
{
|
||||
const std::filesystem::path windowsAppsPath{ wil::ExpandEnvironmentStringsW<std::wstring>(LocalAppDataAppsPath.data()) };
|
||||
const std::filesystem::path wtPath = windowsAppsPath / pfn / (IsDevBuild() ? WtdExe : WtExe);
|
||||
return wtPath;
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
// If we're here, then we couldn't resolve our exe from the package. This
|
||||
// means we're running unpackaged. We should just use the
|
||||
// WindowsTerminal.exe that's sitting in the directory next to us.
|
||||
try
|
||||
{
|
||||
HMODULE hModule = GetModuleHandle(nullptr);
|
||||
THROW_LAST_ERROR_IF(hModule == nullptr);
|
||||
std::wstring dllPathString;
|
||||
THROW_IF_FAILED(wil::GetModuleFileNameW(hModule, dllPathString));
|
||||
const std::filesystem::path dllPath{ dllPathString };
|
||||
const std::filesystem::path rootDir = dllPath.parent_path();
|
||||
std::filesystem::path wtPath = rootDir / WindowsTerminalExe;
|
||||
return wtPath;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
return L"wt.exe";
|
||||
}();
|
||||
return exePath;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method is called when the user activates the context menu item. We'll
|
||||
// launch the Terminal using the current working directory.
|
||||
@@ -148,7 +56,7 @@ HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray,
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEX);
|
||||
|
||||
// Append a "\." to the given path, so that this will work in "C:\"
|
||||
std::wstring cmdline = fmt::format(L"\"{}\" -d \"{}\\.\"", _getExePath(), pszName.get());
|
||||
std::wstring cmdline = fmt::format(L"\"{}\" -d \"{}\\.\"", GetWtExePath(), pszName.get());
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(
|
||||
nullptr,
|
||||
cmdline.data(),
|
||||
|
||||
@@ -27,8 +27,15 @@ Author(s):
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
struct __declspec(uuid("9f156763-7844-4dc4-b2b1-901f640f5155"))
|
||||
OpenTerminalHere : public RuntimeClass<RuntimeClassFlags<ClassicCom | InhibitFtmBase>, IExplorerCommand>
|
||||
struct
|
||||
#if defined(WT_BRANDING_RELEASE)
|
||||
__declspec(uuid("9f156763-7844-4dc4-b2b1-901f640f5155"))
|
||||
#elif defined(WT_BRANDING_PREVIEW)
|
||||
__declspec(uuid("02db545a-3e20-46de-83a5-1329b1e88b6b"))
|
||||
#else // DEV
|
||||
__declspec(uuid("52065414-e077-47ec-a3ac-1cc5455e1b54"))
|
||||
#endif
|
||||
OpenTerminalHere : public RuntimeClass<RuntimeClassFlags<ClassicCom | InhibitFtmBase>, IExplorerCommand>
|
||||
{
|
||||
#pragma region IExplorerCommand
|
||||
STDMETHODIMP Invoke(IShellItemArray* psiItemArray,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "PlaceholderType.h"
|
||||
#include "PlaceholderType.g.cpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::ShellExtension::implementation
|
||||
{
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- PlaceholderType.h
|
||||
|
||||
Abstract:
|
||||
- This class is just here to make our .wapproj play nicely with this project. If
|
||||
we don't define any winrt types, then we won't generate a .winmd, and the
|
||||
.wapproj will become _very_ mad at this project. So we'll use this placeholder
|
||||
class just to trick cppwinrt into generating a winmd for us. If we ever _do_
|
||||
add a real winrt type to this project, this can be removed.
|
||||
|
||||
Author(s):
|
||||
- Mike Griese - May 2020
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include <conattrs.hpp>
|
||||
#include "PlaceholderType.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::ShellExtension::implementation
|
||||
{
|
||||
struct PlaceholderType : PlaceholderTypeT<PlaceholderType>
|
||||
{
|
||||
PlaceholderType() = default;
|
||||
GETSET_PROPERTY(int32_t, Placeholder, 42);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::ShellExtension::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(PlaceholderType);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// This class is just here to make our .wapproj play nicely with this project.
|
||||
// If we don't define any winrt types, then we won't generate a .winmd, and the
|
||||
// .wapproj will become _very_ mad at this project. So we'll use this
|
||||
// placeholder class just to trick cppwinrt into generating a winmd for us. If
|
||||
// we ever _do_ add a real winrt type to this project, this can be removed.
|
||||
|
||||
namespace Microsoft.Terminal.ShellExtension
|
||||
{
|
||||
[default_interface] runtimeclass PlaceholderType {
|
||||
PlaceholderType();
|
||||
Int32 Placeholder
|
||||
{
|
||||
get;
|
||||
set;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
EXPORTS
|
||||
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
|
||||
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
|
||||
DllCanUnloadNow PRIVATE
|
||||
DllGetActivationFactory PRIVATE
|
||||
DllGetClassObject PRIVATE
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<!-- build a dll, not exe (Application) -->
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<!-- sets a bunch of Windows Universal properties -->
|
||||
<OpenConsoleUniversalApp>true</OpenConsoleUniversalApp>
|
||||
<!-- suppress a bunch of Windows Universal properties from cppwinrt.props -->
|
||||
<OpenConsoleUniversalApp>false</OpenConsoleUniversalApp>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
@@ -21,27 +21,17 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="OpenTerminalHere.h" />
|
||||
<ClInclude Include="PlaceholderType.h">
|
||||
<DependentUpon>PlaceholderType.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PlaceholderType.cpp">
|
||||
<DependentUpon>PlaceholderType.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OpenTerminalHere.cpp" />
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="PlaceholderType.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="WindowsTerminalShellExt.def" />
|
||||
<None Include="$(ProjectName).def" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
@@ -58,5 +48,41 @@
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)\build\rules\Branding.targets" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<!-- Override GetPackagingOutputs to roll up our DLL.
|
||||
This is a heavily stripped version of the one in Microsoft.*.AppxPackage.targets.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true</_ContinueOnError>
|
||||
<_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false</_ContinueOnError>
|
||||
</PropertyGroup>
|
||||
<Target Name="GetPackagingOutputs" Returns="@(PackagingOutputs)">
|
||||
<CallTarget Targets="BuiltProjectOutputGroup">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_BuiltProjectOutputGroupOutput" />
|
||||
</CallTarget>
|
||||
<ItemGroup>
|
||||
<_PackagingOutputsUnexpanded Include="%(_BuiltProjectOutputGroupOutput.FinalOutputPath)">
|
||||
<TargetPath>%(_BuiltProjectOutputGroupOutput.TargetPath)</TargetPath>
|
||||
<OutputGroup>BuiltProjectOutputGroup</OutputGroup>
|
||||
<ProjectName>$(ProjectName)</ProjectName>
|
||||
</_PackagingOutputsUnexpanded>
|
||||
</ItemGroup>
|
||||
<CallTarget Targets="DebugSymbolsProjectOutputGroup">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_DebugSymbolsProjectOutputGroupOutput" />
|
||||
</CallTarget>
|
||||
<ItemGroup>
|
||||
<_PackagingOutputsUnexpanded Include="%(_DebugSymbolsProjectOutputGroupOutput.FinalOutputPath)">
|
||||
<OutputGroup>DebugSymbolsProjectOutputGroup</OutputGroup>
|
||||
<ProjectName>$(ProjectName)</ProjectName>
|
||||
</_PackagingOutputsUnexpanded>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackagingOutputs Include="@(_PackagingOutputsUnexpanded)">
|
||||
<TargetPath>%(Filename)%(Extension)</TargetPath>
|
||||
</PackagingOutputs>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -4,22 +4,21 @@
|
||||
#include "pch.h"
|
||||
#include "OpenTerminalHere.h"
|
||||
|
||||
// For reference, see:
|
||||
// * https://docs.microsoft.com/en-us/cpp/cppcx/wrl/how-to-create-a-classic-com-component-using-wrl?view=vs-2019
|
||||
// * https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/move-to-winrt-from-wrl#porting-a-wrl-module-microsoftwrlmodule
|
||||
//
|
||||
// We don't need to implement DllGetActivationFactory or DllCanUnloadNow
|
||||
// manually, since the generated module.g.cpp will handle it for us, and will
|
||||
// handle our WRL types appropriately.
|
||||
//
|
||||
// We DO need to implement DllGetClassObject, because that's what explorer.exe
|
||||
// will call to attempt to create a class factory for our shell extension. The
|
||||
// CoCreatableClass macro in OpenTerminalHere.h will create the factory for us,
|
||||
// so that the GetClassObject call will work like magic.
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv)
|
||||
STDAPI DllCanUnloadNow()
|
||||
{
|
||||
return Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
|
||||
return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory)
|
||||
{
|
||||
return Module<InProc>::GetModule().GetActivationFactory(activatableClassId, factory);
|
||||
}
|
||||
|
||||
STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ void** ppv)
|
||||
{
|
||||
return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
|
||||
}
|
||||
|
||||
STDAPI_(BOOL)
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
Name(command.Name());
|
||||
KeyChordText(command.KeyChordText());
|
||||
Icon(command.Icon());
|
||||
Icon(command.IconPath());
|
||||
|
||||
_commandChangedRevoker = command.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& sender, auto& e) {
|
||||
auto item{ weakThis.get() };
|
||||
@@ -40,9 +40,9 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
item->KeyChordText(senderCommand.KeyChordText());
|
||||
}
|
||||
else if (changedProperty == L"Icon")
|
||||
else if (changedProperty == L"IconPath")
|
||||
{
|
||||
item->Icon(senderCommand.Icon());
|
||||
item->Icon(senderCommand.IconPath());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace winrt::TerminalApp::implementation
|
||||
ActionPaletteItem() = default;
|
||||
ActionPaletteItem(Microsoft::Terminal::Settings::Model::Command const& command);
|
||||
|
||||
GETSET_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, nullptr);
|
||||
WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, nullptr);
|
||||
|
||||
private:
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _commandChangedRevoker;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "App.h"
|
||||
|
||||
#include "TerminalPage.h"
|
||||
#include "../WinRTUtils/inc/WtExeUtils.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
@@ -15,7 +17,7 @@ using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
||||
using namespace ::TerminalApp;
|
||||
|
||||
@@ -37,7 +39,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPage::_HandleDuplicateTab(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
_DuplicateTabViewItem();
|
||||
_DuplicateFocusedTab();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
@@ -154,6 +156,17 @@ namespace winrt::TerminalApp::implementation
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleTogglePaneReadOnly(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
activeTab->TogglePaneReadOnly();
|
||||
}
|
||||
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
@@ -182,6 +195,18 @@ namespace winrt::TerminalApp::implementation
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleFindMatch(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<FindMatchArgs>())
|
||||
{
|
||||
if (const auto& control{ _GetActiveControl() })
|
||||
{
|
||||
control.SearchMatch(realArgs.Direction() == FindMatchDirection::Next);
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
void TerminalPage::_HandleOpenSettings(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
@@ -351,8 +376,8 @@ namespace winrt::TerminalApp::implementation
|
||||
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
|
||||
{
|
||||
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
controlSettings->ApplyColorScheme(scheme);
|
||||
activeControl.UpdateSettings(*controlSettings);
|
||||
controlSettings.ApplyColorScheme(scheme);
|
||||
activeControl.UpdateSettings();
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
@@ -465,18 +490,20 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove tabs after the current one
|
||||
while (_tabs.Size() > index + 1)
|
||||
// Since _RemoveTab is asynchronous, create a snapshot of the tabs we want to remove
|
||||
std::vector<winrt::TerminalApp::TabBase> tabsToRemove;
|
||||
if (index > 0)
|
||||
{
|
||||
_RemoveTabViewItemByIndex(_tabs.Size() - 1);
|
||||
std::copy(begin(_tabs), begin(_tabs) + index, std::back_inserter(tabsToRemove));
|
||||
}
|
||||
|
||||
// Remove all of them leading up to the selected tab
|
||||
while (_tabs.Size() > 1)
|
||||
if (index + 1 < _tabs.Size())
|
||||
{
|
||||
_RemoveTabViewItemByIndex(0);
|
||||
std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove));
|
||||
}
|
||||
|
||||
_RemoveTabs(tabsToRemove);
|
||||
|
||||
actionArgs.Handled(true);
|
||||
}
|
||||
}
|
||||
@@ -502,11 +529,10 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove tabs after the current one
|
||||
while (_tabs.Size() > index + 1)
|
||||
{
|
||||
_RemoveTabViewItemByIndex(_tabs.Size() - 1);
|
||||
}
|
||||
// Since _RemoveTab is asynchronous, create a snapshot of the tabs we want to remove
|
||||
std::vector<winrt::TerminalApp::TabBase> tabsToRemove;
|
||||
std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove));
|
||||
_RemoveTabs(tabsToRemove);
|
||||
|
||||
// TODO:GH#7182 For whatever reason, if you run this action
|
||||
// when the tab that's currently focused is _before_ the `index`
|
||||
@@ -557,4 +583,88 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper to launch a new WT instance. It can either launch the instance
|
||||
// elevated or unelevated.
|
||||
// - To launch elevated, it will as the shell to elevate the process for us.
|
||||
// This might cause a UAC prompt. The elevation is performed on a
|
||||
// background thread, as to not block the UI thread.
|
||||
// Arguments:
|
||||
// - elevate: If true, launch the new Terminal elevated using `runas`
|
||||
// - newTerminalArgs: A NewTerminalArgs describing the terminal instance
|
||||
// that should be spawned. The Profile should be filled in with the GUID
|
||||
// of the profile we want to launch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
// Important: Don't take the param by reference, since we'll be doing work
|
||||
// on another thread.
|
||||
fire_and_forget TerminalPage::_OpenNewWindow(const bool elevate,
|
||||
const NewTerminalArgs newTerminalArgs)
|
||||
{
|
||||
// Hop to the BG thread
|
||||
co_await winrt::resume_background();
|
||||
|
||||
// This will get us the correct exe for dev/preview/release. If you
|
||||
// don't stick this in a local, it'll get mangled by ShellExecute. I
|
||||
// have no idea why.
|
||||
const auto exePath{ GetWtExePath() };
|
||||
|
||||
// Build the commandline to pass to wt for this set of NewTerminalArgs
|
||||
// `-w -1` will ensure a new window is created.
|
||||
winrt::hstring cmdline{
|
||||
fmt::format(L"-w -1 new-tab {}",
|
||||
newTerminalArgs ? newTerminalArgs.ToCommandline().c_str() :
|
||||
L"")
|
||||
};
|
||||
|
||||
// Build the args to ShellExecuteEx. We need to use ShellExecuteEx so we
|
||||
// can pass the SEE_MASK_NOASYNC flag. That flag allows us to safely
|
||||
// call this on the background thread, and have ShellExecute _not_ call
|
||||
// back to us on the main thread. Without this, if you close the
|
||||
// Terminal quickly after the UAC prompt, the elevated WT will never
|
||||
// actually spawn.
|
||||
SHELLEXECUTEINFOW seInfo{ 0 };
|
||||
seInfo.cbSize = sizeof(seInfo);
|
||||
seInfo.fMask = SEE_MASK_NOASYNC;
|
||||
// `runas` will cause the shell to launch this child process elevated.
|
||||
// `open` will just run the executable normally.
|
||||
seInfo.lpVerb = elevate ? L"runas" : L"open";
|
||||
seInfo.lpFile = exePath.c_str();
|
||||
seInfo.lpParameters = cmdline.c_str();
|
||||
seInfo.nShow = SW_SHOWNORMAL;
|
||||
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& actionArgs)
|
||||
{
|
||||
NewTerminalArgs newTerminalArgs{ nullptr };
|
||||
// If the caller provided NewTerminalArgs, then try to use those
|
||||
if (actionArgs)
|
||||
{
|
||||
if (const auto& realArgs = actionArgs.ActionArgs().try_as<NewWindowArgs>())
|
||||
{
|
||||
newTerminalArgs = realArgs.TerminalArgs();
|
||||
}
|
||||
}
|
||||
// Otherwise, if no NewTerminalArgs were provided, then just use a
|
||||
// default-constructed one. The default-constructed one implies that
|
||||
// nothing about the launch should be modified (just use the default
|
||||
// profile).
|
||||
if (!newTerminalArgs)
|
||||
{
|
||||
newTerminalArgs = NewTerminalArgs();
|
||||
}
|
||||
|
||||
const auto profileGuid{ _settings.GetProfileForArgs(newTerminalArgs) };
|
||||
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
|
||||
|
||||
// Manually fill in the evaluated profile.
|
||||
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profileGuid));
|
||||
_OpenNewWindow(false, newTerminalArgs);
|
||||
actionArgs.Handled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -185,6 +185,10 @@ void AppCommandlineArgs::_buildParser()
|
||||
maximized->excludes(fullscreen);
|
||||
focus->excludes(fullscreen);
|
||||
|
||||
_app.add_option("-w,--window",
|
||||
_windowTarget,
|
||||
RS_A(L"CmdWindowTargetArgDesc"));
|
||||
|
||||
// Subcommands
|
||||
_buildNewTabParser();
|
||||
_buildSplitPaneParser();
|
||||
@@ -413,6 +417,11 @@ void AppCommandlineArgs::_addNewTerminalArgs(AppCommandlineArgs::NewTerminalSubc
|
||||
_startingTabColor,
|
||||
RS_A(L"CmdTabColorArgDesc"));
|
||||
|
||||
subcommand.suppressApplicationTitleOption = subcommand.subcommand->add_flag(
|
||||
"--suppressApplicationTitle,!--useApplicationTitle",
|
||||
_suppressApplicationTitle,
|
||||
RS_A(L"CmdSuppressApplicationTitleDesc"));
|
||||
|
||||
// Using positionals_at_end allows us to support "wt new-tab -d wsl -d Ubuntu"
|
||||
// without CLI11 thinking that we've specified -d twice.
|
||||
// There's an alternate construction where we make all subcommands "prefix commands",
|
||||
@@ -480,6 +489,11 @@ NewTerminalArgs AppCommandlineArgs::_getNewTerminalArgs(AppCommandlineArgs::NewT
|
||||
args.TabColor(static_cast<winrt::Windows::UI::Color>(tabColor));
|
||||
}
|
||||
|
||||
if (*subcommand.suppressApplicationTitleOption)
|
||||
{
|
||||
args.SuppressApplicationTitle(_suppressApplicationTitle);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -518,6 +532,7 @@ void AppCommandlineArgs::_resetStateToDefault()
|
||||
_startingTitle.clear();
|
||||
_startingTabColor.clear();
|
||||
_commandline.clear();
|
||||
_suppressApplicationTitle = false;
|
||||
|
||||
_splitVertical = false;
|
||||
_splitHorizontal = false;
|
||||
@@ -531,6 +546,7 @@ void AppCommandlineArgs::_resetStateToDefault()
|
||||
// DON'T clear _launchMode here! This will get called once for every
|
||||
// subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out
|
||||
// the "global" fullscreen flag (-F).
|
||||
// Same with _windowTarget.
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
@@ -848,4 +864,11 @@ void AppCommandlineArgs::FullResetState()
|
||||
_startupActions.clear();
|
||||
_exitMessage = "";
|
||||
_shouldExitEarly = false;
|
||||
|
||||
_windowTarget = {};
|
||||
}
|
||||
|
||||
std::string_view AppCommandlineArgs::GetTargetWindow() const noexcept
|
||||
{
|
||||
return _windowTarget;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ public:
|
||||
void DisableHelpInExitMessage();
|
||||
void FullResetState();
|
||||
|
||||
std::string_view GetTargetWindow() const noexcept;
|
||||
|
||||
private:
|
||||
static const std::wregex _commandDelimiterRegex;
|
||||
|
||||
@@ -59,6 +61,7 @@ private:
|
||||
CLI::Option* startingDirectoryOption;
|
||||
CLI::Option* titleOption;
|
||||
CLI::Option* tabColorOption;
|
||||
CLI::Option* suppressApplicationTitleOption;
|
||||
};
|
||||
|
||||
struct NewPaneSubcommand : public NewTerminalSubcommand
|
||||
@@ -83,6 +86,7 @@ private:
|
||||
std::string _startingDirectory;
|
||||
std::string _startingTitle;
|
||||
std::string _startingTabColor;
|
||||
bool _suppressApplicationTitle{ false };
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
|
||||
|
||||
@@ -103,6 +107,8 @@ private:
|
||||
std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
|
||||
std::string _exitMessage;
|
||||
bool _shouldExitEarly{ false };
|
||||
|
||||
std::string _windowTarget{};
|
||||
// Are you adding more args or attributes here? If they are not reset in _resetStateToDefault, make sure to reset them in FullResetState
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
|
||||