mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-07 23:01:09 +00:00
Compare commits
111 Commits
dev/miniks
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fe7df4918 | ||
|
|
875680a3f1 | ||
|
|
8de952ca06 | ||
|
|
869bcb6ec4 | ||
|
|
1fe836eeca | ||
|
|
c97f336f54 | ||
|
|
ad80cffedf | ||
|
|
1bcc85a60b | ||
|
|
0b8b0b9d94 | ||
|
|
b4dae1238e | ||
|
|
7deaf6b5aa | ||
|
|
266402a2d6 | ||
|
|
3d7b455bb7 | ||
|
|
a6dedbb25a | ||
|
|
37d417c07d | ||
|
|
91503e0c96 | ||
|
|
9516372a8a | ||
|
|
7b9c8c7055 | ||
|
|
860affd608 | ||
|
|
d0602ef907 | ||
|
|
3dc0672faa | ||
|
|
f1d3136a24 | ||
|
|
f5ab042939 | ||
|
|
57a80aa531 | ||
|
|
5a1b7b664b | ||
|
|
38058a7a86 | ||
|
|
c530d2a0d3 | ||
|
|
f919a46caf | ||
|
|
ae71dce2ca | ||
|
|
93b31f6e3f | ||
|
|
068e3e7bc2 | ||
|
|
f276d83c7a | ||
|
|
ffd8f53529 | ||
|
|
275417c792 | ||
|
|
23f742061f | ||
|
|
64ac0d25e0 | ||
|
|
cd87db6713 | ||
|
|
0e33d8634d | ||
|
|
ca8d101740 | ||
|
|
e596943ed2 | ||
|
|
a80382b1c4 | ||
|
|
a5297fac3e | ||
|
|
b752da96de | ||
|
|
57ee5a9d0d | ||
|
|
2b6e96a745 | ||
|
|
d954ad68f2 | ||
|
|
61d635367b | ||
|
|
75f4240adc | ||
|
|
dd8813e0be | ||
|
|
bf48ce5b51 | ||
|
|
71f7d0311e | ||
|
|
f34a5e8f8a | ||
|
|
469fdd0faa | ||
|
|
8e387d8bc0 | ||
|
|
9a45e1e271 | ||
|
|
8c37708bc4 | ||
|
|
b4653156fb | ||
|
|
a34a957cf7 | ||
|
|
e79a421f3a | ||
|
|
4834604ddc | ||
|
|
852f0aac34 | ||
|
|
cc35c83e6a | ||
|
|
d8f7aac4ca | ||
|
|
be77b2a2e9 | ||
|
|
27342db1aa | ||
|
|
3953b71d95 | ||
|
|
2cba4c628e | ||
|
|
a3d68d2b21 | ||
|
|
a32956d620 | ||
|
|
267deaaf70 | ||
|
|
c6879d75af | ||
|
|
44c4a8c925 | ||
|
|
e58a648bd4 | ||
|
|
2d707f102b | ||
|
|
142a9e1f9d | ||
|
|
a3eb427d8b | ||
|
|
7f43b40da9 | ||
|
|
4608fd0b94 | ||
|
|
4393fefb71 | ||
|
|
161fe60171 | ||
|
|
0e672fac08 | ||
|
|
74cd9db383 | ||
|
|
a97048a798 | ||
|
|
2f60cf0e91 | ||
|
|
e5182fb3e8 | ||
|
|
1de07aa3ab | ||
|
|
9f53107f00 | ||
|
|
d7ea526c3c | ||
|
|
61e5917fe8 | ||
|
|
31c9d19a72 | ||
|
|
0efdc8f004 | ||
|
|
64b446abb0 | ||
|
|
de5e72f3a4 | ||
|
|
8a5407c13a | ||
|
|
4def49c45e | ||
|
|
b8e33560f9 | ||
|
|
671110c88a | ||
|
|
4420950337 | ||
|
|
7d6738cde7 | ||
|
|
693cdc1c95 | ||
|
|
360c655acc | ||
|
|
215df3212f | ||
|
|
d0c8221c6e | ||
|
|
ce39b63f46 | ||
|
|
2dec894515 | ||
|
|
39d3c65420 | ||
|
|
deccf7e12b | ||
|
|
8392d6b647 | ||
|
|
c54f59b3c5 | ||
|
|
9df9bd00d7 | ||
|
|
941a44a464 |
@@ -1,8 +1,9 @@
|
||||
# Code of Conduct
|
||||
# Microsoft Open Source Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code].
|
||||
For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments.
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
|
||||
[conduct-code]: https://opensource.microsoft.com/codeofconduct/
|
||||
[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
|
||||
[conduct-email]: mailto:opencode@microsoft.com
|
||||
Resources:
|
||||
|
||||
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
|
||||
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
|
||||
|
||||
@@ -294,6 +294,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{D3EF
|
||||
build\scripts\Test-WindowsTerminalPackage.ps1 = build\scripts\Test-WindowsTerminalPackage.ps1
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dx.Unit.Tests", "src\renderer\dx\ut_dx\Dx.Unit.Tests.vcxproj", "{95B136F9-B238-490C-A7C5-5843C1FECAC4}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty.Tests.Feature", "src\winconpty\ft_pty\winconpty.FeatureTests.vcxproj", "{024052DE-83FB-4653-AEA4-90790D29D5BD}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAzBridge", "src\cascadia\TerminalAzBridge\TerminalAzBridge.vcxproj", "{067F0A06-FCB7-472C-96E9-B03B54E8E18D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
@@ -1419,6 +1425,66 @@ Global
|
||||
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x64.Build.0 = Release|x64
|
||||
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.ActiveCfg = Release|Win32
|
||||
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.Build.0 = Release|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.Build.0 = Debug|x64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.Build.0 = Debug|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.ActiveCfg = Release|x64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.Build.0 = Release|x64
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.ActiveCfg = Release|Win32
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.Build.0 = Release|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x64.Build.0 = Debug|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x86.Build.0 = Debug|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x64.ActiveCfg = Release|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x64.Build.0 = Release|x64
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x86.ActiveCfg = Release|Win32
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x86.Build.0 = Release|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x64.Build.0 = Debug|x64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x86.Build.0 = Debug|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x64.ActiveCfg = Release|x64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x64.Build.0 = Release|x64
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x86.ActiveCfg = Release|Win32
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -1493,6 +1559,9 @@ Global
|
||||
{53DD5520-E64C-4C06-B472-7CE62CA539C9} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
|
||||
{6B5A44ED-918D-4747-BFB1-2472A1FCA173} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
|
||||
{D3EF7B96-CD5E-47C9-B9A9-136259563033} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
|
||||
{95B136F9-B238-490C-A7C5-5843C1FECAC4} = {05500DEF-2294-41E3-AF9A-24E580B82836}
|
||||
{024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
||||
13
README.md
13
README.md
@@ -17,7 +17,15 @@ Related repositories include:
|
||||
|
||||
> 👉 Note: Windows Terminal requires Windows 10 1903 (build 18362) or later
|
||||
|
||||
### Manually installing builds from this repository
|
||||
### Microsoft Store [Recommended]
|
||||
|
||||
Install the [Windows Terminal from the Microsoft Store][store-install-link]. This allows you to always be on the latest version when we release new builds with automatic upgrades.
|
||||
|
||||
This is our preferred method.
|
||||
|
||||
### Other install methods
|
||||
|
||||
#### 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 page](https://github.com/microsoft/terminal/releases).
|
||||
|
||||
@@ -26,7 +34,7 @@ For users who are unable to install Terminal from the Microsoft Store, Terminal
|
||||
> * Be sure to install the [Desktop Bridge VC++ v14 Redistributable Package](https://www.microsoft.com/en-us/download/details.aspx?id=53175) otherwise Terminal may not install and/or run and may crash at startup
|
||||
> * Terminal will not auto-update when new builds are released so you will need to regularly install the latest Terminal release to receive all the latest fixes and improvements!
|
||||
|
||||
### Install via Chocolatey (unofficial)
|
||||
#### Via Chocolatey (unofficial)
|
||||
|
||||
[Chocolatey](https://chocolatey.org) users can download and install the latest Terminal release by installing the `microsoft-windows-terminal` package:
|
||||
|
||||
@@ -219,3 +227,4 @@ For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [open
|
||||
[conduct-code]: https://opensource.microsoft.com/codeofconduct/
|
||||
[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
|
||||
[conduct-email]: mailto:opencode@microsoft.com
|
||||
[store-install-link]: https://aka.ms/windowsterminal
|
||||
|
||||
@@ -6,6 +6,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
submodules: false
|
||||
clean: true
|
||||
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
"/src/winconpty/",
|
||||
"/.nuget/",
|
||||
"/.github/",
|
||||
"/samples/"
|
||||
"/samples/",
|
||||
"/res/terminal/",
|
||||
"/doc/specs/",
|
||||
"/doc/cascadia/",
|
||||
"/doc/user-docs/"
|
||||
],
|
||||
"SuffixFilters": [
|
||||
".dbb",
|
||||
|
||||
@@ -1,13 +1,38 @@
|
||||
|
||||
# How to build Openconsole
|
||||
# How to build OpenConsole
|
||||
|
||||
Openconsole can be built with Visual Studio or from the command line. There are build scripts for both cmd and PowerShell in /tools.
|
||||
This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) for some of its dependencies. To make sure submodules are restored or updated, be sure to run the following prior to building:
|
||||
|
||||
```shell
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
OpenConsole.sln may be built from within Visual Studio or from the command-line using a set of convenience scripts & tools in the **/tools** directory:
|
||||
|
||||
When using Visual Studio, be sure to set up the path for code formatting. This can be done in Visual Studio by going to Tools > Options > Text Editor > C++ > Formatting and checking "Use custom clang-format.exe file" and choosing the clang-format.exe in the repository at /dep/llvm/clang-format.exe by clicking "browse" right under the check box.
|
||||
|
||||
## Building with cmd
|
||||
### Building in PowerShell
|
||||
|
||||
The cmd scripts are set up to emulate a portion of the OS razzle build environment. razzle.cmd is the first script that should be run. bcz.cmd will build clean and bz.cmd should build incrementally.
|
||||
```powershell
|
||||
Import-Module .\tools\OpenConsole.psm1
|
||||
Set-MsBuildDevEnvironment
|
||||
Invoke-OpenConsoleBuild
|
||||
```
|
||||
|
||||
There are a few additional exported functions (look at their documentation for further details):
|
||||
|
||||
- `Invoke-OpenConsoleBuild` - builds the solution. Can be passed msbuild arguments.
|
||||
- `Invoke-OpenConsoleTests` - runs the various tests. Will run the unit tests by default.
|
||||
- `Start-OpenConsole` - starts Openconsole.exe from the output directory. x64 is run by default.
|
||||
- `Debug-OpenConsole` - starts Openconsole.exe and attaches it to the default debugger. x64 is run by default.
|
||||
- `Invoke-CodeFormat` - uses clang-format to format all c++ files to match our coding style.
|
||||
|
||||
### Building in Cmd
|
||||
|
||||
```shell
|
||||
.\tools\razzle.cmd
|
||||
bcz
|
||||
```
|
||||
|
||||
There are also scripts for running the tests:
|
||||
- `runut.cmd` - run the unit tests
|
||||
@@ -15,15 +40,13 @@ There are also scripts for running the tests:
|
||||
- `runuia.cmd` - run the UIA tests
|
||||
- `runformat` - uses clang-format to format all c++ files to match our coding style.
|
||||
|
||||
## Build with Powershell
|
||||
## Running & Debugging
|
||||
|
||||
Openconsole.psm1 should be loaded with `Import-Module`. From there `Set-MsbuildDevEnvironment` will set up environment variables required to build. There are a few exported functions (look at their documentation for further details):
|
||||
To debug the Windows Terminal in VS, right click on `CascadiaPackage` (in the Solution Explorer) and go to properties. In the Debug menu, change "Application process" and "Background task process" to "Native Only".
|
||||
|
||||
- `Invoke-OpenConsolebuild` - builds the solution. Can be passed msbuild arguments.
|
||||
- `Invoke-OpenConsoleTests` - runs the various tests. Will run the unit tests by default.
|
||||
- `Start-OpenConsole` - starts Openconsole.exe from the output directory. x64 is run by default.
|
||||
- `Debug-OpenConsole` - starts Openconsole.exe and attaches it to the default debugger. x64 is run by default.
|
||||
- `Invoke-CodeFormat` - uses clang-format to format all c++ files to match our coding style.
|
||||
You should then be able to build & debug the Terminal project by hitting <kbd>F5</kbd>.
|
||||
|
||||
> 👉 You will _not_ be able to launch the Terminal directly by running the WindowsTerminal.exe. For more details on why, see [#926](https://github.com/microsoft/terminal/issues/926), [#4043](https://github.com/microsoft/terminal/issues/4043)
|
||||
|
||||
## Configuration Types
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ Properties listed below are specific to each unique profile.
|
||||
| `guid` | _Required_ | String | | Unique identifier of the profile. Written in registry format: `"{00000000-0000-0000-0000-000000000000}"`. |
|
||||
| `name` | _Required_ | String | | Name of the profile. Displays in the dropdown menu. <br>Additionally, this value will be used as the "title" to pass to the shell on startup. Some shells (like `bash`) may choose to ignore this initial value, while others (`cmd`, `powershell`) may use this value over the lifetime of the application. This "title" behavior can be overridden by using `tabTitle`. |
|
||||
| `acrylicOpacity` | Optional | Number | `0.5` | When `useAcrylic` is set to `true`, it sets the transparency of the window for the profile. Accepts floating point values from 0-1. |
|
||||
| `antialiasingMode` | Optional | String | `"grayscale"` | Controls how text is antialiased in the renderer. Possible values are "grayscale", "cleartype" and "aliased". Note that changing this setting will require starting a new terminal instance. |
|
||||
| `background` | Optional | String | | Sets the background color of the profile. Overrides `background` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
|
||||
| `backgroundImage` | Optional | String | | Sets the file location of the Image to draw over the window background. |
|
||||
| `backgroundImageAlignment` | Optional | String | `center` | Sets how the background image aligns to the boundaries of the window. Possible values: `"center"`, `"left"`, `"top"`, `"right"`, `"bottom"`, `"topLeft"`, `"topRight"`, `"bottomLeft"`, `"bottomRight"` |
|
||||
@@ -109,31 +110,31 @@ For commands with arguments:
|
||||
|
||||
| Command | Command Description | Action (*=required) | Action Arguments | Argument Descriptions |
|
||||
| ------- | ------------------- | ------ | ---------------- | ----------------- |
|
||||
| closePane | Close the active pane. | | | |
|
||||
| closeTab | Close the current tab. | | | |
|
||||
| closeWindow | Close the current window and all tabs within it. | | | |
|
||||
| copy | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
|
||||
| decreaseFontSize | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
|
||||
| duplicateTab | Make a copy and open the current tab. | | | |
|
||||
| find | Open the search dialog box. | | | |
|
||||
| increaseFontSize | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
|
||||
| moveFocus | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
|
||||
| newTab | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
|
||||
| nextTab | Open the tab to the right of the current one. | | | |
|
||||
| openNewTabDropdown | Open the dropdown menu. | | | |
|
||||
| openSettings | Open the settings file. | | | |
|
||||
| paste | Insert the content that was copied onto the clipboard. | | | |
|
||||
| prevTab | Open the tab to the left of the current one. | | | |
|
||||
| resetFontSize | Reset the text size to the default value. | | | |
|
||||
| resizePane | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
|
||||
| scrollDown | Move the screen down. | | | |
|
||||
| scrollUp | Move the screen up. | | | |
|
||||
| scrollUpPage | Move the screen up a whole page. | | | |
|
||||
| scrollDownPage | Move the screen down a whole page. | | | |
|
||||
| splitPane | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
|
||||
| switchToTab | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
|
||||
| toggleFullscreen | Switch between fullscreen and default window sizes. | | | |
|
||||
| unbound | Unbind the associated keys from any command. | | | |
|
||||
| `closePane` | Close the active pane. | | | |
|
||||
| `closeTab` | Close the current tab. | | | |
|
||||
| `closeWindow` | Close the current window and all tabs within it. | | | |
|
||||
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
|
||||
| `decreaseFontSize` | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
|
||||
| `duplicateTab` | Make a copy and open the current tab. | | | |
|
||||
| `find` | Open the search dialog box. | | | |
|
||||
| `increaseFontSize` | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
|
||||
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
|
||||
| `newTab` | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
|
||||
| `nextTab` | Open the tab to the right of the current one. | | | |
|
||||
| `openNewTabDropdown` | Open the dropdown menu. | | | |
|
||||
| `openSettings` | Open the settings file. | | | |
|
||||
| `paste` | Insert the content that was copied onto the clipboard. | | | |
|
||||
| `prevTab` | Open the tab to the left of the current one. | | | |
|
||||
| `resetFontSize` | Reset the text size to the default value. | | | |
|
||||
| `resizePane` | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
|
||||
| `scrollDown` | Move the screen down. | | | |
|
||||
| `scrollUp` | Move the screen up. | | | |
|
||||
| `scrollUpPage` | Move the screen up a whole page. | | | |
|
||||
| `scrollDownPage` | Move the screen down a whole page. | | | |
|
||||
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
|
||||
| `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
|
||||
| `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | |
|
||||
| `unbound` | Unbind the associated keys from any command. | | | |
|
||||
|
||||
### Accepted Modifiers and Keys
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Microsoft's Windows Terminal Settings Profile Schema'",
|
||||
"definitions": {
|
||||
"KeyChordSegment": {
|
||||
"pattern": "^(?<modifier>(ctrl|alt|shift)\\+?((ctrl|alt|shift)(?<!\\2)\\+?)?((ctrl|alt|shift)(?<!\\2|\\4))?\\+?)?(?<key>[^+\\s]+?)?(?<=[^+\\s])$",
|
||||
"type": "string"
|
||||
},
|
||||
"Color": {
|
||||
"default": "#",
|
||||
"pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
|
||||
@@ -244,12 +248,18 @@
|
||||
},
|
||||
"keys": {
|
||||
"description": "Defines the key combinations used to call the command.",
|
||||
"items": {
|
||||
"pattern": "^(?<modifier>(ctrl|alt|shift)\\+?((ctrl|alt|shift)(?<!\\2)\\+?)?((ctrl|alt|shift)(?<!\\2|\\4))?\\+?)?(?<key>[^+\\s]+?)?(?<=[^+\\s])$",
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeyChordSegment"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/KeyChordSegment"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -378,6 +388,16 @@
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"antialiasingMode": {
|
||||
"default": "grayscale",
|
||||
"description": "Controls how text is antialiased in the renderer. Possible values are \"grayscale\", \"cleartype\" and \"aliased\". Note that changing this setting will require starting a new terminal instance.",
|
||||
"enum": [
|
||||
"grayscale",
|
||||
"cleartype",
|
||||
"aliased"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"background": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": "#0c0c0c",
|
||||
|
||||
@@ -34,7 +34,7 @@ Ultimately, we're aiming for Terminal v1.0 to be feature-complete by Dec 2019, a
|
||||
| 2020-01-28 | Beta 1 | Pri 0/1/2 Bug fixes & polish |
|
||||
| 2020-02-25 | Beta 2 | Pri 0/1 Bug fixes & polish |
|
||||
| 2020-03-24 | RC | Pri 0 bug fixes |
|
||||
| 2020-04-01 ? | v1.0 | Terminal v1.0 Release |
|
||||
| 2020-05 | v1.0 | Terminal v1.0 Release |
|
||||
|
||||
## GitHub Milestones
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Assuming that you've installed Anaconda into `%USERPROFILE%\Anaconda3`:
|
||||
|
||||
```json
|
||||
{
|
||||
"commandline" : "cmd.exe /K %USERPROFILE%\\Anaconda3\\Scripts\\activate.bat %USERPROFILE%\\Anaconda3",
|
||||
"commandline" : "cmd.exe /k \"%USERPROFILE%\\Anaconda3\\Scripts\\activate.bat %USERPROFILE%\\Anaconda3\"",
|
||||
"icon" : "%USERPROFILE%/Anaconda3/Menu/anaconda-navigator.ico",
|
||||
"name" : "Anaconda3",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
@@ -28,7 +28,7 @@ Assuming that you've installed cmder into `%CMDER_ROOT%`:
|
||||
|
||||
```json
|
||||
{
|
||||
"commandline" : "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
|
||||
"commandline" : "cmd.exe /k \"%CMDER_ROOT%\\vendor\\init.bat\"",
|
||||
"name" : "cmder",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pass, and gives some examples of how to use the `wt` commandline.
|
||||
### Options
|
||||
|
||||
#### `--help,-h,-?,/?,`
|
||||
|
||||
Display the help message.
|
||||
|
||||
## Subcommands
|
||||
@@ -35,21 +36,18 @@ opens a new window. Subsequent `new-tab` commands will all open new tabs in the
|
||||
same window.
|
||||
|
||||
**Parameters**:
|
||||
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
|
||||
|
||||
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
|
||||
|
||||
#### `split-pane`
|
||||
|
||||
`split-pane [--target,-t target-pane] [-H]|[-V] [terminal_parameters]`
|
||||
`split-pane [-H]|[-V] [terminal_parameters]`
|
||||
|
||||
Creates a new pane in the currently focused tab by splitting the given pane
|
||||
vertically or horizontally.
|
||||
|
||||
**Parameters**:
|
||||
* `--target,-t target-pane`: Creates a new split in the given `target-pane`.
|
||||
Each pane has a unique index (per-tab) which can be used to identify them.
|
||||
These indicies are assigned in the order the panes were created. If omitted,
|
||||
defaults to the index of the currently focused pane.
|
||||
|
||||
* `-H`, `-V`: Used to indicate which direction to split the pane. `-V` is
|
||||
"vertically" (think `[|]`), and `-H` is "horizontally" (think `[-]`). If
|
||||
omitted, defaults to "auto", which splits the current pane in whatever the
|
||||
@@ -72,7 +70,6 @@ Moves focus to a given tab.
|
||||
* `-p,--previous`: Move focus to the previous tab. Will display an error if
|
||||
combined with either of `--next` or `--target`.
|
||||
|
||||
|
||||
#### `[terminal_parameters]`
|
||||
|
||||
Some of the preceding commands are used to create a new terminal instance.
|
||||
@@ -92,12 +89,11 @@ following:
|
||||
selected profile. If the user wants to use a `;` in this commandline, it
|
||||
should be escaped as `\;`.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Open Windows Terminal in the current directory
|
||||
|
||||
```
|
||||
```powershell
|
||||
wt -d .
|
||||
```
|
||||
|
||||
@@ -114,11 +110,12 @@ the `split-pane` command to create new panes.
|
||||
|
||||
Consider the following commandline:
|
||||
|
||||
```
|
||||
```powershell
|
||||
wt ; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe
|
||||
```
|
||||
|
||||
This creates a new Windows Terminal window with one tab, and 3 panes:
|
||||
|
||||
* `wt`: Creates the new tab with the default profile
|
||||
* `split-pane -p "Windows PowerShell"`: This will create a new pane, split from
|
||||
the parent with the default profile. This pane will open with the "Windows
|
||||
|
||||
@@ -54,8 +54,8 @@ object under a root property `"globals"`.
|
||||
This is an array of key chords and shortcuts to invoke various commands.
|
||||
Each command can have more than one key binding.
|
||||
|
||||
NOTE: Key bindings is a subfield of the global settings and
|
||||
key bindings apply to all profiles in the same manner.
|
||||
> 👉 **Note**: Key bindings is a subfield of the global settings and
|
||||
> key bindings apply to all profiles in the same manner.
|
||||
|
||||
For example, here's a sample of the default keybindings:
|
||||
|
||||
@@ -69,9 +69,26 @@ For example, here's a sample of the default keybindings:
|
||||
// etc.
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can also use a single key chord string as the value of `"keys"`.
|
||||
It will be treated as a chord of length one.
|
||||
This will allow you to simplify the above snippet as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"keybindings":
|
||||
[
|
||||
{ "command": "closePane", "keys": "ctrl+shift+w" },
|
||||
{ "command": "copy", "keys": "ctrl+shift+c" },
|
||||
{ "command": "newTab", "keys": "ctrl+shift+t" },
|
||||
// etc.
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Unbinding keys
|
||||
|
||||
If you ever come across a key binding that you're unhappy with, it's possible to
|
||||
@@ -136,7 +153,7 @@ the property `"hidden": true` to the profile's json. This can also be used to
|
||||
remove the default `cmd` and PowerShell profiles, if the user does not wish to
|
||||
see them.
|
||||
|
||||
## Color Schemes
|
||||
## Color Schemes
|
||||
|
||||
Each scheme defines the color values to be used for various terminal escape sequences.
|
||||
Each schema is identified by the name field. Examples include
|
||||
@@ -159,6 +176,7 @@ The schema name can then be referenced in one or more profiles.
|
||||
## Settings layering
|
||||
|
||||
The runtime settings are actually constructed from _three_ sources:
|
||||
|
||||
* The default settings, which are hardcoded into the application, and available
|
||||
in `defaults.json`. This includes the default keybindings, color schemes, and
|
||||
profiles for both Windows PowerShell and Command Prompt (`cmd.exe`).
|
||||
@@ -269,7 +287,7 @@ properties for all your profiles, like so:
|
||||
{
|
||||
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
"name": "Windows PowerShell",
|
||||
"commandline": "powershell.exe",
|
||||
"commandline": "powershell.exe"
|
||||
},
|
||||
{
|
||||
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
|
||||
@@ -281,12 +299,13 @@ properties for all your profiles, like so:
|
||||
"name" : "cmder",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
Note that the `profiles` property has changed in this example from a _list_ of
|
||||
profiles, to an _object_ with two properties:
|
||||
|
||||
* a `list` that contains the list of all the profiles
|
||||
* the new `defaults` object, which contains all the settings that should apply to
|
||||
every profile.
|
||||
@@ -309,7 +328,7 @@ could achieve that with the following:
|
||||
{
|
||||
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
"name": "Windows PowerShell",
|
||||
"commandline": "powershell.exe",
|
||||
"commandline": "powershell.exe"
|
||||
},
|
||||
{
|
||||
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
|
||||
@@ -322,19 +341,18 @@ could achieve that with the following:
|
||||
"name" : "cmder",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
In the above settings, the `"fontFace"` in the `cmd.exe` profile overrides the
|
||||
`"fontFace"` from the `defaults`.
|
||||
|
||||
|
||||
## Configuration Examples:
|
||||
## Configuration Examples
|
||||
|
||||
### Add a custom background to the WSL Debian terminal profile
|
||||
|
||||
1. Download the Debian JPG logo https://www.debian.org/logos/openlogo-100.jpg
|
||||
1. Download the [Debian JPG logo](https://www.debian.org/logos/openlogo-100.jpg)
|
||||
2. Put the image in the
|
||||
`$env:LocalAppData\Packages\Microsoft.WindowsTerminal_<randomString>\LocalState\`
|
||||
directory (same directory as your `profiles.json` file).
|
||||
@@ -342,17 +360,20 @@ In the above settings, the `"fontFace"` in the `cmd.exe` profile overrides the
|
||||
__NOTE__: You can put the image anywhere you like, the above suggestion happens to be convenient.
|
||||
3. Open your WT json properties file.
|
||||
4. Under the Debian Linux profile, add the following fields:
|
||||
|
||||
```json
|
||||
"backgroundImage": "ms-appdata:///Local/openlogo-100.jpg",
|
||||
"backgroundImageOpacity": 1,
|
||||
"backgroundImageStretchMode" : "none",
|
||||
"backgroundImageAlignment" : "topRight",
|
||||
```
|
||||
|
||||
5. Make sure that `useAcrylic` is `false`.
|
||||
6. Save the file.
|
||||
7. Jump over to WT and verify your changes.
|
||||
|
||||
Notes:
|
||||
|
||||
1. You will need to experiment with different color settings
|
||||
and schemes to make your terminal text visible on top of your image
|
||||
2. If you store the image in the UWP directory (the same directory as your profiles.json file),
|
||||
@@ -414,7 +435,6 @@ no text selection. Additionally, if you set `paste` to `"ctrl+v"`, commandline
|
||||
applications won't be able to read a ctrl+v from the input. For these reasons,
|
||||
we suggest `"ctrl+shift+c"` and `"ctrl+shift+v"`
|
||||
|
||||
|
||||
### Setting the `startingDirectory` of WSL Profiles to `~`
|
||||
|
||||
By default, the `startingDirectory` of a profile is `%USERPROFILE%`
|
||||
|
||||
@@ -75,15 +75,16 @@ To open the settings file from Windows Terminal:
|
||||
|
||||
For an introduction to the various settings, see [Using Json Settings](UsingJsonSettings.md). The list of valid settings can be found in the [profiles.json documentation](../cascadia/SettingsSchema.md) section.
|
||||
|
||||
## Tips and Tricks:
|
||||
## Tips and Tricks
|
||||
|
||||
1. In PowerShell you can discover if the Windows Terminal is being used by checking for the existence of the environment variable `WT_SESSION`.
|
||||
|
||||
Under pwsh you can also use
|
||||
`(Get-Process -Id $pid).Parent.ProcessName -eq 'WindowsTerminal'`
|
||||
|
||||
(ref https://twitter.com/r_keith_hill/status/1142871145852440576)
|
||||
(ref [https://twitter.com/r_keith_hill/status/1142871145852440576](https://twitter.com/r_keith_hill/status/1142871145852440576))
|
||||
|
||||
2. Terminal zoom can be changed by holding <kbd>Ctrl</kbd> and scrolling with mouse.
|
||||
3. If `useAcrylic` is enabled in profiles.json, background opacity can be changed by holding <kbd>Ctrl</kbd>+<kbd>Shift</kbd> and scrolling with mouse. Note that acrylic transparency is limited by the OS only to focused windows.
|
||||
4. Please add more Tips and Tricks
|
||||
3. Background opacity can be changed by holding <kbd>Ctrl</kbd>+<kbd>Shift</kbd> and scrolling with mouse. Note that acrylic transparency is limited by the OS only to focused windows.
|
||||
4. Open Windows Terminal in current directory by typing `wt -d .` in the address bar.
|
||||
5. Please add more Tips and Tricks.
|
||||
|
||||
@@ -269,6 +269,33 @@ std::wstring CharRow::GetText() const
|
||||
return wstr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get delimiter class for a position in the char row
|
||||
// - used for double click selection and uia word navigation
|
||||
// Arguments:
|
||||
// - column: column to get text data for
|
||||
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const DelimiterClass CharRow::DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
|
||||
|
||||
const auto glyph = *GlyphAt(column).begin();
|
||||
if (glyph <= UNICODE_SPACE)
|
||||
{
|
||||
return DelimiterClass::ControlChar;
|
||||
}
|
||||
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
|
||||
{
|
||||
return DelimiterClass::DelimiterChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DelimiterClass::RegularChar;
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeStorage& CharRow::GetUnicodeStorage() noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
|
||||
@@ -27,6 +27,13 @@ Revision History:
|
||||
|
||||
class ROW;
|
||||
|
||||
enum class DelimiterClass
|
||||
{
|
||||
ControlChar,
|
||||
DelimiterChar,
|
||||
RegularChar
|
||||
};
|
||||
|
||||
// the characters of one row of screen buffer
|
||||
// we keep the following values so that we don't write
|
||||
// more pixels to the screen than we have to:
|
||||
@@ -64,6 +71,8 @@ public:
|
||||
void ClearGlyph(const size_t column);
|
||||
std::wstring GetText() const;
|
||||
|
||||
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
// working with glyphs
|
||||
const reference GlyphAt(const size_t column) const;
|
||||
reference GlyphAt(const size_t column);
|
||||
|
||||
@@ -155,11 +155,6 @@ void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground)
|
||||
}
|
||||
}
|
||||
|
||||
bool TextAttribute::_IsReverseVideo() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
||||
}
|
||||
|
||||
bool TextAttribute::IsLeadingByte() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
|
||||
|
||||
@@ -163,12 +163,41 @@ public:
|
||||
return _foreground.IsRgb() || _background.IsRgb();
|
||||
}
|
||||
|
||||
// This returns whether this attribute, if printed directly next to another attribute, for the space
|
||||
// character, would look identical to the other one.
|
||||
constexpr bool HasIdenticalVisualRepresentationForBlankSpace(const TextAttribute& other, const bool inverted = false) const noexcept
|
||||
{
|
||||
// sneaky-sneaky: I'm using xor here
|
||||
// inverted is whether there's a global invert; Reverse is a local one.
|
||||
// global ^ local == true : the background attribute is actually the visible foreground, so we care about the foregrounds being identical
|
||||
// global ^ local == false: the foreground attribute is the visible foreground, so we care about the backgrounds being identical
|
||||
const auto checkForeground = (inverted != _IsReverseVideo());
|
||||
return !IsAnyGridLineEnabled() && // grid lines have a visual representation
|
||||
// crossed out, doubly and singly underlined have a visual representation
|
||||
WI_AreAllFlagsClear(_extendedAttrs, ExtendedAttributes::CrossedOut | ExtendedAttributes::DoublyUnderlined | ExtendedAttributes::Underlined) &&
|
||||
// all other attributes do not have a visual representation
|
||||
(_wAttrLegacy & META_ATTRS) == (other._wAttrLegacy & META_ATTRS) &&
|
||||
((checkForeground && _foreground == other._foreground) ||
|
||||
(!checkForeground && _background == other._background)) &&
|
||||
_extendedAttrs == other._extendedAttrs;
|
||||
}
|
||||
|
||||
constexpr bool IsAnyGridLineEnabled() const noexcept
|
||||
{
|
||||
return WI_IsAnyFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE);
|
||||
}
|
||||
|
||||
private:
|
||||
COLORREF _GetRgbForeground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept;
|
||||
COLORREF _GetRgbBackground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept;
|
||||
bool _IsReverseVideo() const noexcept;
|
||||
|
||||
constexpr bool _IsReverseVideo() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
||||
}
|
||||
|
||||
void _SetBoldness(const bool isBold) noexcept;
|
||||
|
||||
WORD _wAttrLegacy;
|
||||
|
||||
@@ -573,27 +573,21 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Retrieves the position of the last non-space character on the final line of the text buffer.
|
||||
// - By default, we search the entire buffer to find the last non-space character
|
||||
//Arguments:
|
||||
// - <none>
|
||||
//Return Value:
|
||||
// - Coordinate position in screen coordinates (offset coordinates, not array index coordinates).
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter() const
|
||||
{
|
||||
return GetLastNonSpaceCharacter(GetSize());
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Retrieves the position of the last non-space character in the given viewport
|
||||
// - This is basically an optimized version of GetLastNonSpaceCharacter(), and can be called when
|
||||
// - we know the last character is within the given viewport (so we don't need to check the entire buffer)
|
||||
// - Retrieves the position of the last non-space character in the given
|
||||
// viewport
|
||||
// - By default, we search the entire buffer to find the last non-space
|
||||
// character.
|
||||
// - If we know the last character is within the given viewport (so we don't
|
||||
// need to check the entire buffer), we can provide a value in viewOptional
|
||||
// that we'll use to search for the last character in.
|
||||
//Arguments:
|
||||
// - The viewport
|
||||
//Return value:
|
||||
// - Coordinate position (relative to the text buffer)
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Console::Types::Viewport> viewOptional) const
|
||||
{
|
||||
const auto viewport = viewOptional.has_value() ? viewOptional.value() : GetSize();
|
||||
|
||||
COORD coordEndOfText = { 0 };
|
||||
// Search the given viewport by starting at the bottom.
|
||||
coordEndOfText.Y = viewport.BottomInclusive();
|
||||
@@ -951,6 +945,19 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep
|
||||
return _renderTarget;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get delimiter class for buffer cell position
|
||||
// - used for double click selection and uia word navigation
|
||||
// Arguments:
|
||||
// - pos: the buffer cell under observation
|
||||
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the COORD for the beginning of the word you are on
|
||||
// Arguments:
|
||||
@@ -1001,16 +1008,11 @@ const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const
|
||||
COORD result = target;
|
||||
const auto bufferSize = GetSize();
|
||||
bool stayAtOrigin = false;
|
||||
auto bufferIterator = GetTextDataAt(result);
|
||||
|
||||
// ignore left boundary. Continue until readable text found
|
||||
while (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
{
|
||||
if (bufferSize.DecrementInBounds(result))
|
||||
{
|
||||
--bufferIterator;
|
||||
}
|
||||
else
|
||||
if (!bufferSize.DecrementInBounds(result))
|
||||
{
|
||||
// first char in buffer is a DelimiterChar or ControlChar
|
||||
// we can't move any further back
|
||||
@@ -1020,13 +1022,9 @@ const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const
|
||||
}
|
||||
|
||||
// make sure we expand to the left boundary or the beginning of the word
|
||||
while (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
{
|
||||
if (bufferSize.DecrementInBounds(result))
|
||||
{
|
||||
--bufferIterator;
|
||||
}
|
||||
else
|
||||
if (!bufferSize.DecrementInBounds(result))
|
||||
{
|
||||
// first char in buffer is a RegularChar
|
||||
// we can't move any further back
|
||||
@@ -1035,7 +1033,7 @@ const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const
|
||||
}
|
||||
|
||||
// move off of delimiter and onto word start
|
||||
if (!stayAtOrigin && _GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
if (!stayAtOrigin && _GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
{
|
||||
bufferSize.IncrementInBounds(result);
|
||||
}
|
||||
@@ -1054,17 +1052,16 @@ const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std:
|
||||
{
|
||||
COORD result = target;
|
||||
const auto bufferSize = GetSize();
|
||||
auto bufferIterator = GetTextDataAt(result);
|
||||
const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters);
|
||||
|
||||
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
|
||||
|
||||
// expand left until we hit the left boundary or a different delimiter class
|
||||
while (result.X > bufferSize.Left() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter))
|
||||
while (result.X > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
|
||||
{
|
||||
bufferSize.DecrementInBounds(result);
|
||||
--bufferIterator;
|
||||
}
|
||||
|
||||
if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter)
|
||||
if (_GetDelimiterClassAt(result, wordDelimiters) != initialDelimiter)
|
||||
{
|
||||
// move off of delimiter
|
||||
bufferSize.IncrementInBounds(result);
|
||||
@@ -1116,31 +1113,20 @@ const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const st
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
COORD result = target;
|
||||
auto bufferIterator = GetTextDataAt(result);
|
||||
|
||||
// ignore right boundary. Continue through readable text found
|
||||
while (_GetDelimiterClass(*bufferIterator, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
{
|
||||
if (bufferSize.IncrementInBounds(result, true))
|
||||
if (!bufferSize.IncrementInBounds(result, true))
|
||||
{
|
||||
++bufferIterator;
|
||||
}
|
||||
else
|
||||
{
|
||||
// last char in buffer is a RegularChar
|
||||
// we can't move any further forward
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we expand to the beginning of the NEXT word
|
||||
while (_GetDelimiterClass(*bufferIterator, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
{
|
||||
if (bufferSize.IncrementInBounds(result, true))
|
||||
{
|
||||
++bufferIterator;
|
||||
}
|
||||
else
|
||||
if (!bufferSize.IncrementInBounds(result, true))
|
||||
{
|
||||
// we are at the EndInclusive COORD
|
||||
// this signifies that we must include the last char in the buffer
|
||||
@@ -1162,25 +1148,23 @@ const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const st
|
||||
const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
COORD result = target;
|
||||
auto bufferIterator = GetTextDataAt(result);
|
||||
|
||||
// can't expand right
|
||||
if (target.X == bufferSize.RightInclusive())
|
||||
{
|
||||
return result;
|
||||
return target;
|
||||
}
|
||||
|
||||
const auto initialDelimiter = _GetDelimiterClass(*bufferIterator, wordDelimiters);
|
||||
COORD result = target;
|
||||
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
|
||||
|
||||
// expand right until we hit the right boundary or a different delimiter class
|
||||
while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClass(*bufferIterator, wordDelimiters) == initialDelimiter))
|
||||
while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
|
||||
{
|
||||
bufferSize.IncrementInBounds(result);
|
||||
++bufferIterator;
|
||||
}
|
||||
|
||||
if (_GetDelimiterClass(*bufferIterator, wordDelimiters) != initialDelimiter)
|
||||
if (_GetDelimiterClassAt(result, wordDelimiters) != initialDelimiter)
|
||||
{
|
||||
// move off of delimiter
|
||||
bufferSize.DecrementInBounds(result);
|
||||
@@ -1203,11 +1187,8 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite
|
||||
auto copy = pos;
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
auto text = GetTextDataAt(copy)->data();
|
||||
auto delimiterClass = _GetDelimiterClass(text, wordDelimiters);
|
||||
|
||||
// started on a word, continue until the end of the word
|
||||
while (delimiterClass == DelimiterClass::RegularChar)
|
||||
while (_GetDelimiterClassAt(copy, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
{
|
||||
if (!bufferSize.IncrementInBounds(copy))
|
||||
{
|
||||
@@ -1215,8 +1196,6 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite
|
||||
// thus there is no next word
|
||||
return false;
|
||||
}
|
||||
text = GetTextDataAt(copy)->data();
|
||||
delimiterClass = _GetDelimiterClass(text, wordDelimiters);
|
||||
}
|
||||
|
||||
// we are already on/past the last RegularChar
|
||||
@@ -1226,7 +1205,7 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite
|
||||
}
|
||||
|
||||
// on whitespace, continue until the beginning of the next word
|
||||
while (delimiterClass != DelimiterClass::RegularChar)
|
||||
while (_GetDelimiterClassAt(copy, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
{
|
||||
if (!bufferSize.IncrementInBounds(copy))
|
||||
{
|
||||
@@ -1234,8 +1213,6 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite
|
||||
// there is no next word
|
||||
return false;
|
||||
}
|
||||
text = GetTextDataAt(copy)->data();
|
||||
delimiterClass = _GetDelimiterClass(text, wordDelimiters);
|
||||
}
|
||||
|
||||
// successful move, copy result out
|
||||
@@ -1256,11 +1233,8 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
|
||||
auto copy = pos;
|
||||
auto bufferSize = GetSize();
|
||||
|
||||
auto text = GetTextDataAt(copy)->data();
|
||||
auto delimiterClass = _GetDelimiterClass(text, wordDelimiters);
|
||||
|
||||
// started on whitespace/delimiter, continue until the end of the previous word
|
||||
while (delimiterClass != DelimiterClass::RegularChar)
|
||||
while (_GetDelimiterClassAt(copy, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
{
|
||||
if (!bufferSize.DecrementInBounds(copy))
|
||||
{
|
||||
@@ -1268,12 +1242,10 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
|
||||
// there is no previous word
|
||||
return false;
|
||||
}
|
||||
text = GetTextDataAt(copy)->data();
|
||||
delimiterClass = _GetDelimiterClass(text, wordDelimiters);
|
||||
}
|
||||
|
||||
// on a word, continue until the beginning of the word
|
||||
while (delimiterClass == DelimiterClass::RegularChar)
|
||||
while (_GetDelimiterClassAt(copy, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
{
|
||||
if (!bufferSize.DecrementInBounds(copy))
|
||||
{
|
||||
@@ -1281,8 +1253,6 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
|
||||
// there is no previous word
|
||||
return false;
|
||||
}
|
||||
text = GetTextDataAt(copy)->data();
|
||||
delimiterClass = _GetDelimiterClass(text, wordDelimiters);
|
||||
}
|
||||
|
||||
// successful move, copy result out
|
||||
@@ -1291,52 +1261,215 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get delimiter class for buffer cell data
|
||||
// - used for double click selection and uia word navigation
|
||||
// - Update pos to be the beginning of the current glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
// - cellChar: the char saved to the buffer cell under observation
|
||||
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// Return Value:
|
||||
// - pos - The COORD for the first cell of the current glyph (inclusive)
|
||||
const til::point TextBuffer::GetGlyphStart(const til::point pos) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
|
||||
const auto bufferSize = GetSize();
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
|
||||
{
|
||||
bufferSize.DecrementInBounds(resultPos, true);
|
||||
}
|
||||
|
||||
return resultPos;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update pos to be the end of the current glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// Return Value:
|
||||
// - pos - The COORD for the last cell of the current glyph (exclusive)
|
||||
const til::point TextBuffer::GetGlyphEnd(const til::point pos) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
|
||||
const auto bufferSize = GetSize();
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
|
||||
{
|
||||
bufferSize.IncrementInBounds(resultPos, true);
|
||||
}
|
||||
|
||||
// increment one more time to become exclusive
|
||||
bufferSize.IncrementInBounds(resultPos, true);
|
||||
return resultPos;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update pos to be the beginning of the next glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// - allowBottomExclusive - allow the nonexistent end-of-buffer cell to be encountered
|
||||
// Return Value:
|
||||
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
|
||||
// - pos - The COORD for the first cell of the current glyph (inclusive)
|
||||
bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
|
||||
// try to move. If we can't, we're done.
|
||||
const auto bufferSize = GetSize();
|
||||
const bool success = bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
|
||||
{
|
||||
bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
|
||||
}
|
||||
|
||||
pos = resultPos;
|
||||
return success;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update pos to be the beginning of the previous glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// - allowBottomExclusive - allow the nonexistent end-of-buffer cell to be encountered
|
||||
// Return Value:
|
||||
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
|
||||
// - pos - The COORD for the first cell of the previous glyph (inclusive)
|
||||
bool TextBuffer::MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
|
||||
// try to move. If we can't, we're done.
|
||||
const auto bufferSize = GetSize();
|
||||
const bool success = bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
|
||||
{
|
||||
bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
|
||||
}
|
||||
|
||||
pos = resultPos;
|
||||
return success;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines the line-by-line rectangles based on two COORDs
|
||||
// - expands the rectangles to support wide glyphs
|
||||
// - used for selection rects and UIA bounding rects
|
||||
// Arguments:
|
||||
// - start: a corner of the text region of interest (inclusive)
|
||||
// - end: the other corner of the text region of interest (inclusive)
|
||||
// - blockSelection: when enabled, only get the rectangular text region,
|
||||
// as opposed to the text extending to the left/right
|
||||
// buffer margins
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
TextBuffer::DelimiterClass TextBuffer::_GetDelimiterClass(const std::wstring_view cellChar, const std::wstring_view wordDelimiters) const noexcept
|
||||
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection) const
|
||||
{
|
||||
if (cellChar.at(0) <= UNICODE_SPACE)
|
||||
std::vector<SMALL_RECT> textRects;
|
||||
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
// (0,0) is the top-left of the screen
|
||||
// the physically "higher" coordinate is closer to the top-left
|
||||
// the physically "lower" coordinate is closer to the bottom-right
|
||||
const auto [higherCoord, lowerCoord] = bufferSize.CompareInBounds(start, end) <= 0 ?
|
||||
std::make_tuple(start, end) :
|
||||
std::make_tuple(end, start);
|
||||
|
||||
const auto textRectSize = base::ClampedNumeric<short>(1) + lowerCoord.Y - higherCoord.Y;
|
||||
textRects.reserve(textRectSize);
|
||||
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
|
||||
{
|
||||
return DelimiterClass::ControlChar;
|
||||
SMALL_RECT textRow;
|
||||
|
||||
textRow.Top = row;
|
||||
textRow.Bottom = row;
|
||||
|
||||
if (blockSelection || higherCoord.Y == lowerCoord.Y)
|
||||
{
|
||||
// set the left and right margin to the left-/right-most respectively
|
||||
textRow.Left = std::min(higherCoord.X, lowerCoord.X);
|
||||
textRow.Right = std::max(higherCoord.X, lowerCoord.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
textRow.Left = (row == higherCoord.Y) ? higherCoord.X : bufferSize.Left();
|
||||
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
|
||||
}
|
||||
|
||||
_ExpandTextRow(textRow);
|
||||
textRects.emplace_back(textRow);
|
||||
}
|
||||
else if (wordDelimiters.find(cellChar) != std::wstring_view::npos)
|
||||
|
||||
return textRects;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Expand the selection row according to include wide glyphs fully
|
||||
// - this is particularly useful for box selections (ALT + selection)
|
||||
// Arguments:
|
||||
// - selectionRow: the selection row to be expanded
|
||||
// Return Value:
|
||||
// - modifies selectionRow's Left and Right values to expand properly
|
||||
void TextBuffer::_ExpandTextRow(SMALL_RECT& textRow) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
// expand left side of rect
|
||||
COORD targetPoint{ textRow.Left, textRow.Top };
|
||||
if (GetCellDataAt(targetPoint)->DbcsAttr().IsTrailing())
|
||||
{
|
||||
return DelimiterClass::DelimiterChar;
|
||||
if (targetPoint.X == bufferSize.Left())
|
||||
{
|
||||
bufferSize.IncrementInBounds(targetPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferSize.DecrementInBounds(targetPoint);
|
||||
}
|
||||
textRow.Left = targetPoint.X;
|
||||
}
|
||||
else
|
||||
|
||||
// expand right side of rect
|
||||
targetPoint = { textRow.Right, textRow.Bottom };
|
||||
if (GetCellDataAt(targetPoint)->DbcsAttr().IsLeading())
|
||||
{
|
||||
return DelimiterClass::RegularChar;
|
||||
if (targetPoint.X == bufferSize.RightInclusive())
|
||||
{
|
||||
bufferSize.DecrementInBounds(targetPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferSize.IncrementInBounds(targetPoint);
|
||||
}
|
||||
textRow.Right = targetPoint.X;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the text data from the selected region and presents it in a clipboard-ready format (given little post-processing).
|
||||
// Arguments:
|
||||
// - lineSelection - true if entire line is being selected. False otherwise (box selection)
|
||||
// - trimTrailingWhitespace - setting flag removes trailing whitespace at the end of each row in selection
|
||||
// - selectionRects - the selection regions from which the data will be extracted from the buffer
|
||||
// - GetForegroundColor - function used to map TextAttribute to RGB COLORREF for foreground color
|
||||
// - GetBackgroundColor - function used to map TextAttribute to RGB COLORREF for foreground color
|
||||
// - includeCRLF - inject CRLF pairs to the end of each line
|
||||
// - trimTrailingWhitespace - remove the trailing whitespace at the end of each line
|
||||
// - textRects - the rectangular regions from which the data will be extracted from the buffer (i.e.: selection rects)
|
||||
// - GetForegroundColor - function used to map TextAttribute to RGB COLORREF for foreground color. If null, only extract the text.
|
||||
// - GetBackgroundColor - function used to map TextAttribute to RGB COLORREF for background color. If null, only extract the text.
|
||||
// Return Value:
|
||||
// - The text, background color, and foreground color data of the selected region of the text buffer.
|
||||
const TextBuffer::TextAndColor TextBuffer::GetTextForClipboard(const bool lineSelection,
|
||||
const bool trimTrailingWhitespace,
|
||||
const std::vector<SMALL_RECT>& selectionRects,
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor,
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor) const
|
||||
const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
|
||||
const bool trimTrailingWhitespace,
|
||||
const std::vector<SMALL_RECT>& selectionRects,
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor,
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor) const
|
||||
{
|
||||
TextAndColor data;
|
||||
const bool copyTextColor = GetForegroundColor && GetBackgroundColor;
|
||||
|
||||
// preallocate our vectors to reduce reallocs
|
||||
size_t const rows = selectionRects.size();
|
||||
data.text.reserve(rows);
|
||||
data.FgAttr.reserve(rows);
|
||||
data.BkAttr.reserve(rows);
|
||||
if (copyTextColor)
|
||||
{
|
||||
data.FgAttr.reserve(rows);
|
||||
data.BkAttr.reserve(rows);
|
||||
}
|
||||
|
||||
// for each row in the selection
|
||||
for (UINT i = 0; i < rows; i++)
|
||||
@@ -1355,24 +1488,31 @@ const TextBuffer::TextAndColor TextBuffer::GetTextForClipboard(const bool lineSe
|
||||
|
||||
// preallocate to avoid reallocs
|
||||
selectionText.reserve(gsl::narrow<size_t>(highlight.Width()) + 2); // + 2 for \r\n if we munged it
|
||||
selectionFgAttr.reserve(gsl::narrow<size_t>(highlight.Width()) + 2);
|
||||
selectionBkAttr.reserve(gsl::narrow<size_t>(highlight.Width()) + 2);
|
||||
if (copyTextColor)
|
||||
{
|
||||
selectionFgAttr.reserve(gsl::narrow<size_t>(highlight.Width()) + 2);
|
||||
selectionBkAttr.reserve(gsl::narrow<size_t>(highlight.Width()) + 2);
|
||||
}
|
||||
|
||||
// copy char data into the string buffer, skipping trailing bytes
|
||||
while (it)
|
||||
{
|
||||
const auto& cell = *it;
|
||||
auto cellData = cell.TextAttr();
|
||||
COLORREF const CellFgAttr = GetForegroundColor(cellData);
|
||||
COLORREF const CellBkAttr = GetBackgroundColor(cellData);
|
||||
|
||||
if (!cell.DbcsAttr().IsTrailing())
|
||||
{
|
||||
selectionText.append(cell.Chars());
|
||||
for (const wchar_t wch : cell.Chars())
|
||||
|
||||
if (copyTextColor)
|
||||
{
|
||||
selectionFgAttr.push_back(CellFgAttr);
|
||||
selectionBkAttr.push_back(CellBkAttr);
|
||||
auto cellData = cell.TextAttr();
|
||||
COLORREF const CellFgAttr = GetForegroundColor(cellData);
|
||||
COLORREF const CellBkAttr = GetBackgroundColor(cellData);
|
||||
for (const wchar_t wch : cell.Chars())
|
||||
{
|
||||
selectionFgAttr.push_back(CellFgAttr);
|
||||
selectionBkAttr.push_back(CellBkAttr);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning(suppress : 26444)
|
||||
@@ -1380,35 +1520,41 @@ const TextBuffer::TextAndColor TextBuffer::GetTextForClipboard(const bool lineSe
|
||||
it++;
|
||||
}
|
||||
|
||||
// trim trailing spaces if SHIFT key not held
|
||||
const bool forcedWrap = GetRowByOffset(iRow).GetCharRow().WasWrapForced();
|
||||
|
||||
if (trimTrailingWhitespace)
|
||||
{
|
||||
const ROW& Row = GetRowByOffset(iRow);
|
||||
|
||||
// FOR LINE SELECTION ONLY: if the row was wrapped, don't remove the spaces at the end.
|
||||
if (!lineSelection || !Row.GetCharRow().WasWrapForced())
|
||||
// if the row was NOT wrapped...
|
||||
if (!forcedWrap)
|
||||
{
|
||||
// remove the spaces at the end (aka trim the trailing whitespace)
|
||||
while (!selectionText.empty() && selectionText.back() == UNICODE_SPACE)
|
||||
{
|
||||
selectionText.pop_back();
|
||||
selectionFgAttr.pop_back();
|
||||
selectionBkAttr.pop_back();
|
||||
if (copyTextColor)
|
||||
{
|
||||
selectionFgAttr.pop_back();
|
||||
selectionBkAttr.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply CR/LF to the end of the final string, unless we're the last line.
|
||||
// a.k.a if we're earlier than the bottom, then apply CR/LF.
|
||||
if (i < selectionRects.size() - 1)
|
||||
// apply CR/LF to the end of the final string, unless we're the last line.
|
||||
// a.k.a if we're earlier than the bottom, then apply CR/LF.
|
||||
if (includeCRLF && i < selectionRects.size() - 1)
|
||||
{
|
||||
// if the row was NOT wrapped...
|
||||
if (!forcedWrap)
|
||||
{
|
||||
// FOR LINE SELECTION ONLY: if the row was wrapped, do not apply CR/LF.
|
||||
// a.k.a. if the row was NOT wrapped, then we can assume a CR/LF is proper
|
||||
// always apply \r\n for box selection
|
||||
if (!lineSelection || !GetRowByOffset(iRow).GetCharRow().WasWrapForced())
|
||||
{
|
||||
COLORREF const Blackness = RGB(0x00, 0x00, 0x00); // cant see CR/LF so just use black FG & BK
|
||||
// then we can assume a CR/LF is proper
|
||||
selectionText.push_back(UNICODE_CARRIAGERETURN);
|
||||
selectionText.push_back(UNICODE_LINEFEED);
|
||||
|
||||
selectionText.push_back(UNICODE_CARRIAGERETURN);
|
||||
selectionText.push_back(UNICODE_LINEFEED);
|
||||
if (copyTextColor)
|
||||
{
|
||||
// cant see CR/LF so just use black FG & BK
|
||||
COLORREF const Blackness = RGB(0x00, 0x00, 0x00);
|
||||
selectionFgAttr.push_back(Blackness);
|
||||
selectionFgAttr.push_back(Blackness);
|
||||
selectionBkAttr.push_back(Blackness);
|
||||
@@ -1418,8 +1564,11 @@ const TextBuffer::TextAndColor TextBuffer::GetTextForClipboard(const bool lineSe
|
||||
}
|
||||
|
||||
data.text.emplace_back(std::move(selectionText));
|
||||
data.FgAttr.emplace_back(std::move(selectionFgAttr));
|
||||
data.BkAttr.emplace_back(std::move(selectionBkAttr));
|
||||
if (copyTextColor)
|
||||
{
|
||||
data.FgAttr.emplace_back(std::move(selectionFgAttr));
|
||||
data.BkAttr.emplace_back(std::move(selectionBkAttr));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -1805,9 +1954,18 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
||||
// Arguments:
|
||||
// - oldBuffer - the text buffer to copy the contents FROM
|
||||
// - newBuffer - the text buffer to copy the contents TO
|
||||
// - lastCharacterViewport - Optional. If the caller knows that the last
|
||||
// nonspace character is in a particular Viewport, the caller can provide this
|
||||
// parameter as an optimization, as opposed to searching the entire buffer.
|
||||
// - positionInfo - Optional. The caller can provide a pair of rows in this
|
||||
// parameter and we'll calculate the position of the _end_ of those rows in
|
||||
// the new buffer. The rows's new value is placed back into this parameter.
|
||||
// Return Value:
|
||||
// - S_OK if we successfully copied the contents to the new buffer, otherwise an appropriate HRESULT.
|
||||
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
|
||||
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
TextBuffer& newBuffer,
|
||||
const std::optional<Viewport> lastCharacterViewport,
|
||||
std::optional<std::reference_wrapper<PositionInformation>> positionInfo)
|
||||
{
|
||||
Cursor& oldCursor = oldBuffer.GetCursor();
|
||||
Cursor& newCursor = newBuffer.GetCursor();
|
||||
@@ -1819,14 +1977,15 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
|
||||
// place the new cursor back on the equivalent character in
|
||||
// the new buffer.
|
||||
const COORD cOldCursorPos = oldCursor.GetPosition();
|
||||
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter();
|
||||
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
|
||||
|
||||
short const cOldRowsTotal = cOldLastChar.Y + 1;
|
||||
short const cOldColsTotal = oldBuffer.GetSize().Width();
|
||||
const short cOldRowsTotal = cOldLastChar.Y + 1;
|
||||
const short cOldColsTotal = oldBuffer.GetSize().Width();
|
||||
|
||||
COORD cNewCursorPos = { 0 };
|
||||
bool fFoundCursorPos = false;
|
||||
|
||||
bool foundOldMutable = false;
|
||||
bool foundOldVisible = false;
|
||||
HRESULT hr = S_OK;
|
||||
// Loop through all the rows of the old buffer and reprint them into the new buffer
|
||||
for (short iOldRow = 0; iOldRow < cOldRowsTotal; iOldRow++)
|
||||
@@ -1886,6 +2045,31 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
// If we found the old row that the caller was interested in, set the
|
||||
// out value of that parameter to the cursor's current Y position (the
|
||||
// new location of the _end_ of that row in the buffer).
|
||||
if (positionInfo.has_value())
|
||||
{
|
||||
if (!foundOldMutable)
|
||||
{
|
||||
if (iOldRow >= positionInfo.value().get().mutableViewportTop)
|
||||
{
|
||||
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().Y;
|
||||
foundOldMutable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundOldVisible)
|
||||
{
|
||||
if (iOldRow >= positionInfo.value().get().visibleViewportTop)
|
||||
{
|
||||
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().Y;
|
||||
foundOldVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// If we didn't have a full row to copy, insert a new
|
||||
|
||||
@@ -103,8 +103,7 @@ public:
|
||||
// Scroll needs access to this to quickly rotate around the buffer.
|
||||
bool IncrementCircularBuffer(const bool inVtMode = false);
|
||||
|
||||
COORD GetLastNonSpaceCharacter() const;
|
||||
COORD GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const;
|
||||
COORD GetLastNonSpaceCharacter(std::optional<const Microsoft::Console::Types::Viewport> viewOptional = std::nullopt) const;
|
||||
|
||||
Cursor& GetCursor() noexcept;
|
||||
const Cursor& GetCursor() const noexcept;
|
||||
@@ -135,6 +134,13 @@ public:
|
||||
bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const;
|
||||
bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
const til::point GetGlyphStart(const til::point pos) const;
|
||||
const til::point GetGlyphEnd(const til::point pos) const;
|
||||
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
|
||||
bool MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive = false) const;
|
||||
|
||||
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
|
||||
|
||||
class TextAndColor
|
||||
{
|
||||
public:
|
||||
@@ -143,11 +149,11 @@ public:
|
||||
std::vector<std::vector<COLORREF>> BkAttr;
|
||||
};
|
||||
|
||||
const TextAndColor GetTextForClipboard(const bool lineSelection,
|
||||
const bool trimTrailingWhitespace,
|
||||
const std::vector<SMALL_RECT>& selectionRects,
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor,
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor) const;
|
||||
const TextAndColor GetText(const bool lineSelection,
|
||||
const bool trimTrailingWhitespace,
|
||||
const std::vector<SMALL_RECT>& textRects,
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor = nullptr,
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor = nullptr) const;
|
||||
|
||||
static std::string GenHTML(const TextAndColor& rows,
|
||||
const int fontHeightPoints,
|
||||
@@ -160,7 +166,16 @@ public:
|
||||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor);
|
||||
|
||||
static HRESULT Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer);
|
||||
struct PositionInformation
|
||||
{
|
||||
short mutableViewportTop{ 0 };
|
||||
short visibleViewportTop{ 0 };
|
||||
};
|
||||
|
||||
static HRESULT Reflow(TextBuffer& oldBuffer,
|
||||
TextBuffer& newBuffer,
|
||||
const std::optional<Microsoft::Console::Types::Viewport> lastCharacterViewport,
|
||||
std::optional<std::reference_wrapper<PositionInformation>> positionInfo);
|
||||
|
||||
private:
|
||||
std::deque<ROW> _storage;
|
||||
@@ -193,13 +208,9 @@ private:
|
||||
ROW& _GetFirstRow();
|
||||
ROW& _GetPrevRowNoWrap(const ROW& row);
|
||||
|
||||
enum class DelimiterClass
|
||||
{
|
||||
ControlChar,
|
||||
DelimiterChar,
|
||||
RegularChar
|
||||
};
|
||||
DelimiterClass _GetDelimiterClass(const std::wstring_view cellChar, const std::wstring_view wordDelimiters) const noexcept;
|
||||
void _ExpandTextRow(SMALL_RECT& selectionRow) const;
|
||||
|
||||
const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
<!-- Resources -->
|
||||
<!-- This resw only defines things that are used in this package's AppxManifest,
|
||||
so it's not in the common resource items. -->
|
||||
<PRIResource Include="Resources\en-US\Resources.resw" />
|
||||
<PRIResource Include="Resources\Resources.language-en.resw" />
|
||||
<PRIResource Include="Resources\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(WindowsTerminalReleaseBuild)'!='true'">
|
||||
<!-- This is picked up by CascadiaResources.build.items. -->
|
||||
@@ -54,6 +55,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WindowsTerminal\WindowsTerminal.vcxproj" />
|
||||
<ProjectReference Include="..\..\host\exe\Host.EXE.vcxproj" />
|
||||
<ProjectReference Include="..\TerminalAzBridge\TerminalAzBridge.vcxproj" />
|
||||
</ItemGroup>
|
||||
<Target Name="OpenConsoleStompSourceProjectForWapProject" BeforeTargets="_ConvertItems">
|
||||
<ItemGroup>
|
||||
@@ -91,9 +93,7 @@
|
||||
roll up our subproject resources. We have to suppress that rule but keep part of its logic, because that rule is
|
||||
where the AppxPackagePayload items are created. -->
|
||||
<PropertyGroup>
|
||||
<!-- Only for MSBuild versions <= 16.4.0 -->
|
||||
<!-- TODO: Change this to hard less than once the 16.4.0 previews fix the bug. -->
|
||||
<WapProjBeforeGenerateAppxManifestDependsOn Condition="$(MSBuildVersion) <= '16.4.0'">
|
||||
<WapProjBeforeGenerateAppxManifestDependsOn>
|
||||
$([MSBuild]::Unescape('$(WapProjBeforeGenerateAppxManifestDependsOn.Replace('_RemoveAllNonWapUWPItems', '_OpenConsoleRemoveAllNonWapUWPItems'))'))
|
||||
</WapProjBeforeGenerateAppxManifestDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>ms-resource:AppName</DisplayName>
|
||||
<DisplayName>Windows Terminal</DisplayName>
|
||||
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
@@ -120,19 +120,7 @@
|
||||
<data name="AppDescription" xml:space="preserve">
|
||||
<value>The New Windows Terminal</value>
|
||||
</data>
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>Windows Terminal (Preview)</value>
|
||||
</data>
|
||||
<data name="AppDescriptionDev" xml:space="preserve">
|
||||
<value>The Windows Terminal, but Unofficial</value>
|
||||
</data>
|
||||
<data name="AppNameDev" xml:space="preserve">
|
||||
<value>Windows Terminal (Dev Build)</value>
|
||||
</data>
|
||||
<data name="AppShortName" xml:space="preserve">
|
||||
<value>Terminal</value>
|
||||
</data>
|
||||
<data name="AppShortNameDev" xml:space="preserve">
|
||||
<value>Terminal (Dev)</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
73
src/cascadia/CascadiaPackage/Resources/Resources.resw
Normal file
73
src/cascadia/CascadiaPackage/Resources/Resources.resw
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>Windows Terminal (Preview)</value>
|
||||
</data>
|
||||
<data name="AppNameDev" xml:space="preserve">
|
||||
<value>Windows Terminal (Dev Build)</value>
|
||||
</data>
|
||||
<data name="AppShortName" xml:space="preserve">
|
||||
<value>Terminal</value>
|
||||
</data>
|
||||
<data name="AppShortNameDev" xml:space="preserve">
|
||||
<value>Terminal (Dev)</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -42,6 +42,8 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(TestArbitraryArgs);
|
||||
TEST_METHOD(TestSplitPaneArgs);
|
||||
|
||||
TEST_METHOD(TestStringOverload);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -458,4 +460,27 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestStringOverload()
|
||||
{
|
||||
const std::string bindings0String{ R"([
|
||||
{ "command": "copy", "keys": "ctrl+c" }
|
||||
])" };
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
|
||||
VERIFY_IS_NOT_NULL(appKeyBindings);
|
||||
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
|
||||
appKeyBindings->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TEST_METHOD(TestTerminalArgsForBinding);
|
||||
|
||||
TEST_METHOD(ValidateKeybindingsWarnings);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -2089,4 +2091,48 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::ValidateKeybindingsWarnings()
|
||||
{
|
||||
const std::string badSettings{ R"(
|
||||
{
|
||||
"globals": {
|
||||
"defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{ "command": { "action": "splitPane", "split":"auto" }, "keys": [ "ctrl+alt+t", "ctrl+a" ] },
|
||||
{ "command": { "action": "moveFocus" }, "keys": [ "ctrl+a" ] },
|
||||
{ "command": { "action": "resizePane" }, "keys": [ "ctrl+b" ] }
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto settingsObject = VerifyParseSucceeded(badSettings);
|
||||
auto settings = CascadiaSettings::FromJson(settingsObject);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->_globals._keybindings->_keyShortcuts.size());
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings->_globals._keybindingsWarnings.size());
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::TooManyKeysForChord, settings->_globals._keybindingsWarnings.at(0));
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_globals._keybindingsWarnings.at(1));
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_globals._keybindingsWarnings.at(2));
|
||||
|
||||
settings->_ValidateKeybindings();
|
||||
|
||||
VERIFY_ARE_EQUAL(4u, settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.at(0));
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::TooManyKeysForChord, settings->_warnings.at(1));
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(2));
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(3));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "HwndTerminal.hpp"
|
||||
#include <windowsx.h>
|
||||
#include "../../types/TermControlUiaProvider.hpp"
|
||||
#include <DefaultSettings.h>
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
@@ -14,13 +16,54 @@ using namespace ::Microsoft::Terminal::Core;
|
||||
|
||||
static LPCWSTR term_window_class = L"HwndTerminalClass";
|
||||
|
||||
static LRESULT CALLBACK HwndTerminalWndProc(
|
||||
LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
|
||||
HWND hwnd,
|
||||
UINT uMsg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam) noexcept
|
||||
{
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
#pragma warning(suppress : 26490) // Win32 APIs can only store void*, have to use reinterpret_cast
|
||||
HwndTerminal* terminal = reinterpret_cast<HwndTerminal*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
|
||||
if (terminal)
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_GETOBJECT:
|
||||
if (lParam == UiaRootObjectId)
|
||||
{
|
||||
return UiaReturnRawElementProvider(hwnd, wParam, lParam, terminal->_GetUiaProvider());
|
||||
}
|
||||
break;
|
||||
case WM_LBUTTONDOWN:
|
||||
LOG_IF_FAILED(terminal->_StartSelection(lParam));
|
||||
return 0;
|
||||
case WM_MOUSEMOVE:
|
||||
if (WI_IsFlagSet(wParam, MK_LBUTTON))
|
||||
{
|
||||
LOG_IF_FAILED(terminal->_MoveSelection(lParam));
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_RBUTTONDOWN:
|
||||
if (terminal->_terminal->IsSelectionActive())
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false);
|
||||
LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true));
|
||||
terminal->_terminal->ClearSelection();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
else
|
||||
{
|
||||
terminal->_PasteTextFromClipboard();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
static bool RegisterTermClass(HINSTANCE hInstance) noexcept
|
||||
@@ -32,7 +75,7 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
|
||||
}
|
||||
|
||||
wc.style = 0;
|
||||
wc.lpfnWndProc = HwndTerminalWndProc;
|
||||
wc.lpfnWndProc = HwndTerminal::HwndTerminalWndProc;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hInstance = hInstance;
|
||||
@@ -47,7 +90,11 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
|
||||
|
||||
HwndTerminal::HwndTerminal(HWND parentHwnd) :
|
||||
_desiredFont{ DEFAULT_FONT_FACE, 0, 10, { 0, 14 }, CP_UTF8 },
|
||||
_actualFont{ DEFAULT_FONT_FACE, 0, 10, { 0, 14 }, CP_UTF8, false }
|
||||
_actualFont{ DEFAULT_FONT_FACE, 0, 10, { 0, 14 }, CP_UTF8, false },
|
||||
_uiaProvider{ nullptr },
|
||||
_uiaProviderInitialized{ false },
|
||||
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
|
||||
_pfnWriteCallback{ nullptr }
|
||||
{
|
||||
HINSTANCE hInstance = wil::GetModuleInstanceHandle();
|
||||
|
||||
@@ -69,6 +116,9 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
|
||||
nullptr,
|
||||
hInstance,
|
||||
nullptr));
|
||||
|
||||
#pragma warning(suppress : 26490) // Win32 APIs can only store void*, have to use reinterpret_cast
|
||||
SetWindowLongPtr(_hwnd.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +157,7 @@ HRESULT HwndTerminal::Initialize()
|
||||
_terminal->Create(COORD{ 80, 25 }, 1000, *_renderer);
|
||||
_terminal->SetDefaultBackground(RGB(5, 27, 80));
|
||||
_terminal->SetDefaultForeground(RGB(255, 255, 255));
|
||||
|
||||
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); });
|
||||
localPointerToThread->EnablePainting();
|
||||
|
||||
return S_OK;
|
||||
@@ -118,31 +168,39 @@ void HwndTerminal::RegisterScrollCallback(std::function<void(int, int, int)> cal
|
||||
_terminal->SetScrollPositionChangedCallback(callback);
|
||||
}
|
||||
|
||||
void HwndTerminal::_WriteTextToConnection(const std::wstring& input) noexcept
|
||||
{
|
||||
if (!_pfnWriteCallback)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto callingText{ wil::make_cotaskmem_string(input.data(), input.size()) };
|
||||
_pfnWriteCallback(callingText.release());
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
void HwndTerminal::RegisterWriteCallback(const void _stdcall callback(wchar_t*))
|
||||
{
|
||||
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept {
|
||||
const wchar_t* text = input.c_str();
|
||||
const size_t textChars = wcslen(text) + 1;
|
||||
const size_t textBytes = textChars * sizeof(wchar_t);
|
||||
wchar_t* callingText = nullptr;
|
||||
_pfnWriteCallback = callback;
|
||||
}
|
||||
|
||||
callingText = static_cast<wchar_t*>(::CoTaskMemAlloc(textBytes));
|
||||
::Microsoft::Console::Types::IUiaData* HwndTerminal::GetUiaData() const noexcept
|
||||
{
|
||||
return _terminal.get();
|
||||
}
|
||||
|
||||
if (callingText == nullptr)
|
||||
{
|
||||
callback(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
wcscpy_s(callingText, textChars, text);
|
||||
|
||||
callback(callingText);
|
||||
}
|
||||
});
|
||||
HWND HwndTerminal::GetHwnd() const noexcept
|
||||
{
|
||||
return _hwnd.get();
|
||||
}
|
||||
|
||||
void HwndTerminal::_UpdateFont(int newDpi)
|
||||
{
|
||||
_currentDpi = newDpi;
|
||||
auto lock = _terminal->LockForWriting();
|
||||
|
||||
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
|
||||
@@ -150,6 +208,33 @@ void HwndTerminal::_UpdateFont(int newDpi)
|
||||
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);
|
||||
}
|
||||
|
||||
IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
|
||||
{
|
||||
if (nullptr == _uiaProvider && !_uiaProviderInitialized)
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock;
|
||||
try
|
||||
{
|
||||
#pragma warning(suppress : 26441) // The lock is named, this appears to be a false positive
|
||||
lock = _terminal->LockForWriting();
|
||||
if (_uiaProviderInitialized)
|
||||
{
|
||||
return _uiaProvider.Get();
|
||||
}
|
||||
|
||||
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, this->GetUiaData(), this));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
_uiaProvider = nullptr;
|
||||
}
|
||||
_uiaProviderInitialized = true;
|
||||
}
|
||||
|
||||
return _uiaProvider.Get();
|
||||
}
|
||||
|
||||
HRESULT HwndTerminal::Refresh(const SIZE windowSize, _Out_ COORD* dimensions)
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, dimensions);
|
||||
@@ -186,10 +271,29 @@ void HwndTerminal::SendOutput(std::wstring_view data)
|
||||
|
||||
HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal)
|
||||
{
|
||||
auto _terminal = std::make_unique<HwndTerminal>(parentHwnd);
|
||||
// In order for UIA to hook up properly there needs to be a "static" window hosting the
|
||||
// inner win32 control. If the static window is not present then WM_GETOBJECT messages
|
||||
// will not reach the child control, and the uia element will not be present in the tree.
|
||||
auto _hostWindow = CreateWindowEx(
|
||||
0,
|
||||
L"static",
|
||||
nullptr,
|
||||
WS_CHILD |
|
||||
WS_CLIPCHILDREN |
|
||||
WS_CLIPSIBLINGS |
|
||||
WS_VISIBLE,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
parentHwnd,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0);
|
||||
auto _terminal = std::make_unique<HwndTerminal>(_hostWindow);
|
||||
RETURN_IF_FAILED(_terminal->Initialize());
|
||||
|
||||
*hwnd = _terminal->_hwnd.get();
|
||||
*hwnd = _hostWindow;
|
||||
*terminal = _terminal.release();
|
||||
|
||||
return S_OK;
|
||||
@@ -217,6 +321,15 @@ HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double heig
|
||||
{
|
||||
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
|
||||
|
||||
LOG_IF_WIN32_BOOL_FALSE(SetWindowPos(
|
||||
publicTerminal->GetHwnd(),
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
static_cast<int>(width),
|
||||
static_cast<int>(height),
|
||||
0));
|
||||
|
||||
const SIZE windowSize{ static_cast<short>(width), static_cast<short>(height) };
|
||||
return publicTerminal->Refresh(windowSize, dimensions);
|
||||
}
|
||||
@@ -233,45 +346,54 @@ void _stdcall TerminalUserScroll(void* terminal, int viewTop)
|
||||
publicTerminal->_terminal->UserScrollViewport(viewTop);
|
||||
}
|
||||
|
||||
HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed)
|
||||
HRESULT HwndTerminal::_StartSelection(LPARAM lParam) noexcept
|
||||
try
|
||||
{
|
||||
COORD terminalPosition = { cursorPosition };
|
||||
const bool altPressed = GetKeyState(VK_MENU) < 0;
|
||||
COORD cursorPosition{
|
||||
GET_X_LPARAM(lParam),
|
||||
GET_Y_LPARAM(lParam),
|
||||
};
|
||||
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
const auto fontSize = publicTerminal->_actualFont.GetSize();
|
||||
const auto fontSize = this->_actualFont.GetSize();
|
||||
|
||||
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.X == 0);
|
||||
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.Y == 0);
|
||||
|
||||
terminalPosition.X /= fontSize.X;
|
||||
terminalPosition.Y /= fontSize.Y;
|
||||
cursorPosition.X /= fontSize.X;
|
||||
cursorPosition.Y /= fontSize.Y;
|
||||
|
||||
publicTerminal->_terminal->SetSelectionAnchor(terminalPosition);
|
||||
publicTerminal->_terminal->SetBoxSelection(altPressed);
|
||||
this->_terminal->SetSelectionAnchor(cursorPosition);
|
||||
this->_terminal->SetBlockSelection(altPressed);
|
||||
|
||||
publicTerminal->_renderer->TriggerSelection();
|
||||
this->_renderer->TriggerSelection();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition)
|
||||
HRESULT HwndTerminal::_MoveSelection(LPARAM lParam) noexcept
|
||||
try
|
||||
{
|
||||
COORD terminalPosition = { cursorPosition };
|
||||
COORD cursorPosition{
|
||||
GET_X_LPARAM(lParam),
|
||||
GET_Y_LPARAM(lParam),
|
||||
};
|
||||
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
const auto fontSize = publicTerminal->_actualFont.GetSize();
|
||||
const auto fontSize = this->_actualFont.GetSize();
|
||||
|
||||
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.X == 0);
|
||||
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.Y == 0);
|
||||
|
||||
terminalPosition.X /= fontSize.X;
|
||||
terminalPosition.Y /= fontSize.Y;
|
||||
cursorPosition.X /= fontSize.X;
|
||||
cursorPosition.Y /= fontSize.Y;
|
||||
|
||||
publicTerminal->_terminal->SetEndSelectionPosition(terminalPosition);
|
||||
publicTerminal->_renderer->TriggerSelection();
|
||||
this->_terminal->SetSelectionEnd(cursorPosition);
|
||||
this->_renderer->TriggerSelection();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
void _stdcall TerminalClearSelection(void* terminal)
|
||||
{
|
||||
@@ -405,11 +527,181 @@ void _stdcall TerminalBlinkCursor(void* terminal)
|
||||
return;
|
||||
}
|
||||
|
||||
publicTerminal->_terminal->SetCursorVisible(!publicTerminal->_terminal->IsCursorVisible());
|
||||
publicTerminal->_terminal->SetCursorOn(!publicTerminal->_terminal->IsCursorOn());
|
||||
}
|
||||
|
||||
void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible)
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
publicTerminal->_terminal->SetCursorVisible(visible);
|
||||
publicTerminal->_terminal->SetCursorOn(visible);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Copies the text given onto the global system clipboard.
|
||||
// Arguments:
|
||||
// - rows - Rows of text data to copy
|
||||
// - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise
|
||||
HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting)
|
||||
{
|
||||
std::wstring finalString;
|
||||
|
||||
// Concatenate strings into one giant string to put onto the clipboard.
|
||||
for (const auto& str : rows.text)
|
||||
{
|
||||
finalString += str;
|
||||
}
|
||||
|
||||
// allocate the final clipboard data
|
||||
const size_t cchNeeded = finalString.size() + 1;
|
||||
const size_t cbNeeded = sizeof(wchar_t) * cchNeeded;
|
||||
wil::unique_hglobal globalHandle(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbNeeded));
|
||||
RETURN_LAST_ERROR_IF_NULL(globalHandle.get());
|
||||
|
||||
PWSTR pwszClipboard = static_cast<PWSTR>(GlobalLock(globalHandle.get()));
|
||||
RETURN_LAST_ERROR_IF_NULL(pwszClipboard);
|
||||
|
||||
// The pattern gets a bit strange here because there's no good wil built-in for global lock of this type.
|
||||
// Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock).
|
||||
const HRESULT hr = StringCchCopyW(pwszClipboard, cchNeeded, finalString.data());
|
||||
GlobalUnlock(globalHandle.get());
|
||||
RETURN_IF_FAILED(hr);
|
||||
|
||||
// Set global data to clipboard
|
||||
RETURN_LAST_ERROR_IF(!OpenClipboard(_hwnd.get()));
|
||||
|
||||
{ // Clipboard Scope
|
||||
auto clipboardCloser = wil::scope_exit([]() noexcept {
|
||||
LOG_LAST_ERROR_IF(!CloseClipboard());
|
||||
});
|
||||
|
||||
RETURN_LAST_ERROR_IF(!EmptyClipboard());
|
||||
RETURN_LAST_ERROR_IF_NULL(SetClipboardData(CF_UNICODETEXT, globalHandle.get()));
|
||||
|
||||
if (fAlsoCopyFormatting)
|
||||
{
|
||||
const auto& fontData = _actualFont;
|
||||
int const iFontHeightPoints = fontData.GetUnscaledSize().Y * 72 / this->_currentDpi;
|
||||
const COLORREF bgColor = _terminal->GetBackgroundColor(_terminal->GetDefaultBrushColors());
|
||||
|
||||
std::string HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor, "Hwnd Console Host");
|
||||
_CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format");
|
||||
|
||||
std::string RTFToPlaceOnClip = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor);
|
||||
_CopyToSystemClipboard(RTFToPlaceOnClip, L"Rich Text Format");
|
||||
}
|
||||
}
|
||||
|
||||
// only free if we failed.
|
||||
// the memory has to remain allocated if we successfully placed it on the clipboard.
|
||||
// Releasing the smart pointer will leave it allocated as we exit scope.
|
||||
globalHandle.release();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Copies the given string onto the global system clipboard in the specified format
|
||||
// Arguments:
|
||||
// - stringToCopy - The string to copy
|
||||
// - lpszFormat - the name of the format
|
||||
HRESULT HwndTerminal::_CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat)
|
||||
{
|
||||
const size_t cbData = stringToCopy.size() + 1; // +1 for '\0'
|
||||
if (cbData)
|
||||
{
|
||||
wil::unique_hglobal globalHandleData(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbData));
|
||||
RETURN_LAST_ERROR_IF_NULL(globalHandleData.get());
|
||||
|
||||
PSTR pszClipboardHTML = static_cast<PSTR>(GlobalLock(globalHandleData.get()));
|
||||
RETURN_LAST_ERROR_IF_NULL(pszClipboardHTML);
|
||||
|
||||
// The pattern gets a bit strange here because there's no good wil built-in for global lock of this type.
|
||||
// Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock).
|
||||
const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, cbData, stringToCopy.data());
|
||||
GlobalUnlock(globalHandleData.get());
|
||||
RETURN_IF_FAILED(hr2);
|
||||
|
||||
UINT const CF_FORMAT = RegisterClipboardFormatW(lpszFormat);
|
||||
RETURN_LAST_ERROR_IF(0 == CF_FORMAT);
|
||||
|
||||
RETURN_LAST_ERROR_IF_NULL(SetClipboardData(CF_FORMAT, globalHandleData.get()));
|
||||
|
||||
// only free if we failed.
|
||||
// the memory has to remain allocated if we successfully placed it on the clipboard.
|
||||
// Releasing the smart pointer will leave it allocated as we exit scope.
|
||||
globalHandleData.release();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void HwndTerminal::_PasteTextFromClipboard() noexcept
|
||||
{
|
||||
// Get paste data from clipboard
|
||||
if (!OpenClipboard(_hwnd.get()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE ClipboardDataHandle = GetClipboardData(CF_UNICODETEXT);
|
||||
if (ClipboardDataHandle == nullptr)
|
||||
{
|
||||
CloseClipboard();
|
||||
return;
|
||||
}
|
||||
|
||||
PCWCH pwstr = static_cast<PCWCH>(GlobalLock(ClipboardDataHandle));
|
||||
|
||||
_StringPaste(pwstr);
|
||||
|
||||
GlobalUnlock(ClipboardDataHandle);
|
||||
|
||||
CloseClipboard();
|
||||
}
|
||||
|
||||
void HwndTerminal::_StringPaste(const wchar_t* const pData) noexcept
|
||||
{
|
||||
if (pData == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::wstring text(pData);
|
||||
_WriteTextToConnection(text);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
COORD HwndTerminal::GetFontSize() const
|
||||
{
|
||||
return _actualFont.GetSize();
|
||||
}
|
||||
|
||||
RECT HwndTerminal::GetBounds() const noexcept
|
||||
{
|
||||
RECT windowRect;
|
||||
GetWindowRect(_hwnd.get(), &windowRect);
|
||||
return windowRect;
|
||||
}
|
||||
|
||||
RECT HwndTerminal::GetPadding() const noexcept
|
||||
{
|
||||
return { 0 };
|
||||
}
|
||||
|
||||
double HwndTerminal::GetScaleFactor() const noexcept
|
||||
{
|
||||
return static_cast<double>(_currentDpi) / static_cast<double>(USER_DEFAULT_SCREEN_DPI);
|
||||
}
|
||||
|
||||
void HwndTerminal::ChangeViewport(const SMALL_RECT NewWindow)
|
||||
{
|
||||
_terminal->UserScrollViewport(NewWindow.Top);
|
||||
}
|
||||
|
||||
HRESULT HwndTerminal::GetHostUiaProvider(IRawElementProviderSimple** provider) noexcept
|
||||
{
|
||||
return UiaHostProviderFromHwnd(_hwnd.get(), provider);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
#include "../../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include <UIAutomationCore.h>
|
||||
#include "../../types/IControlAccessibilityInfo.h"
|
||||
#include "../../types/TermControlUiaProvider.hpp"
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
@@ -25,8 +28,6 @@ __declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(void* terminal, dou
|
||||
__declspec(dllexport) HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
|
||||
__declspec(dllexport) void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
|
||||
__declspec(dllexport) void _stdcall TerminalUserScroll(void* terminal, int viewTop);
|
||||
__declspec(dllexport) HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed);
|
||||
__declspec(dllexport) HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition);
|
||||
__declspec(dllexport) void _stdcall TerminalClearSelection(void* terminal);
|
||||
__declspec(dllexport) const wchar_t* _stdcall TerminalGetSelection(void* terminal);
|
||||
__declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
|
||||
@@ -39,20 +40,35 @@ __declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
|
||||
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
|
||||
};
|
||||
|
||||
struct HwndTerminal
|
||||
struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
|
||||
{
|
||||
public:
|
||||
HwndTerminal(HWND hwnd);
|
||||
|
||||
HwndTerminal(const HwndTerminal&) = default;
|
||||
HwndTerminal(HwndTerminal&&) = default;
|
||||
HwndTerminal& operator=(const HwndTerminal&) = default;
|
||||
HwndTerminal& operator=(HwndTerminal&&) = default;
|
||||
~HwndTerminal() = default;
|
||||
|
||||
HRESULT Initialize();
|
||||
void SendOutput(std::wstring_view data);
|
||||
HRESULT Refresh(const SIZE windowSize, _Out_ COORD* dimensions);
|
||||
void RegisterScrollCallback(std::function<void(int, int, int)> callback);
|
||||
void RegisterWriteCallback(const void _stdcall callback(wchar_t*));
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const noexcept;
|
||||
HWND GetHwnd() const noexcept;
|
||||
|
||||
static LRESULT CALLBACK HwndTerminalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
|
||||
private:
|
||||
wil::unique_hwnd _hwnd;
|
||||
FontInfoDesired _desiredFont;
|
||||
FontInfo _actualFont;
|
||||
int _currentDpi;
|
||||
bool _uiaProviderInitialized;
|
||||
std::function<void(wchar_t*)> _pfnWriteCallback;
|
||||
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
|
||||
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
||||
|
||||
@@ -63,8 +79,6 @@ private:
|
||||
friend HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
|
||||
friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
|
||||
friend void _stdcall TerminalUserScroll(void* terminal, int viewTop);
|
||||
friend HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed);
|
||||
friend HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition);
|
||||
friend void _stdcall TerminalClearSelection(void* terminal);
|
||||
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
|
||||
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
|
||||
@@ -73,5 +87,23 @@ private:
|
||||
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
|
||||
friend void _stdcall TerminalBlinkCursor(void* terminal);
|
||||
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
|
||||
|
||||
void _UpdateFont(int newDpi);
|
||||
void _WriteTextToConnection(const std::wstring& text) noexcept;
|
||||
HRESULT _CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting);
|
||||
HRESULT _CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat);
|
||||
void _PasteTextFromClipboard() noexcept;
|
||||
void _StringPaste(const wchar_t* const pData) noexcept;
|
||||
|
||||
HRESULT _StartSelection(LPARAM lParam) noexcept;
|
||||
HRESULT _MoveSelection(LPARAM lParam) noexcept;
|
||||
IRawElementProviderSimple* _GetUiaProvider() noexcept;
|
||||
|
||||
// Inherited via IControlAccessibilityInfo
|
||||
COORD GetFontSize() const override;
|
||||
RECT GetBounds() const noexcept override;
|
||||
double GetScaleFactor() const noexcept override;
|
||||
void ChangeViewport(const SMALL_RECT NewWindow) override;
|
||||
HRESULT GetHostUiaProvider(IRawElementProviderSimple** provider) noexcept override;
|
||||
RECT GetPadding() const noexcept override;
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
instead of APISet forwarders for easier Windows 7 compatibility. -->
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN // If this is not defined, windows.h includes commdlg.h which defines FindText globally and conflicts with UIAutomation ITextRangeProvider.
|
||||
#endif
|
||||
|
||||
#include <LibraryIncludes.h>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "Utils.h"
|
||||
#include "TerminalWarnings.h"
|
||||
|
||||
// Notes on defining ActionArgs and ActionEventArgs:
|
||||
// * All properties specific to an action should be defined as an ActionArgs
|
||||
@@ -26,6 +27,8 @@
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
using FromJsonResult = std::tuple<winrt::TerminalApp::IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
|
||||
|
||||
struct ActionEventArgs : public ActionEventArgsT<ActionEventArgs>
|
||||
{
|
||||
ActionEventArgs() = default;
|
||||
@@ -104,7 +107,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<CopyTextArgs>();
|
||||
@@ -112,7 +115,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
args->_TrimWhitespace = trimWhitespace.asBool();
|
||||
}
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,12 +134,12 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<NewTabArgs>();
|
||||
args->_TerminalArgs = NewTerminalArgs::FromJson(json);
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -157,7 +160,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
@@ -165,7 +168,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
args->_TabIndex = tabIndex.asUInt();
|
||||
}
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -221,7 +224,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<ResizePaneArgs>();
|
||||
@@ -229,7 +232,14 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
args->_Direction = ParseDirection(directionString.asString());
|
||||
}
|
||||
return *args;
|
||||
if (args->_Direction == TerminalApp::Direction::None)
|
||||
{
|
||||
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
|
||||
}
|
||||
else
|
||||
{
|
||||
return { *args, {} };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -250,7 +260,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<MoveFocusArgs>();
|
||||
@@ -258,7 +268,14 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
args->_Direction = ParseDirection(directionString.asString());
|
||||
}
|
||||
return *args;
|
||||
if (args->_Direction == TerminalApp::Direction::None)
|
||||
{
|
||||
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
|
||||
}
|
||||
else
|
||||
{
|
||||
return { *args, {} };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -279,7 +296,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<AdjustFontSizeArgs>();
|
||||
@@ -287,7 +304,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
args->_Delta = jsonDelta.asInt();
|
||||
}
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -314,13 +331,26 @@ namespace winrt::TerminalApp::implementation
|
||||
return TerminalApp::SplitState::None;
|
||||
};
|
||||
|
||||
// Possible SplitType values
|
||||
static constexpr std::string_view DuplicateKey{ "duplicate" };
|
||||
static TerminalApp::SplitType ParseSplitModeState(const std::string& stateString)
|
||||
{
|
||||
if (stateString == DuplicateKey)
|
||||
{
|
||||
return TerminalApp::SplitType::Duplicate;
|
||||
}
|
||||
return TerminalApp::SplitType::Manual;
|
||||
}
|
||||
|
||||
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
|
||||
{
|
||||
SplitPaneArgs() = default;
|
||||
GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::None);
|
||||
GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr);
|
||||
GETSET_PROPERTY(winrt::TerminalApp::SplitType, SplitMode, winrt::TerminalApp::SplitType::Manual);
|
||||
|
||||
static constexpr std::string_view SplitKey{ "split" };
|
||||
static constexpr std::string_view SplitModeKey{ "splitMode" };
|
||||
|
||||
public:
|
||||
bool Equals(const IActionArgs& other)
|
||||
@@ -329,11 +359,12 @@ namespace winrt::TerminalApp::implementation
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_SplitStyle == _SplitStyle &&
|
||||
otherAsUs->_TerminalArgs == _TerminalArgs;
|
||||
otherAsUs->_TerminalArgs == _TerminalArgs &&
|
||||
otherAsUs->_SplitMode == _SplitMode;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<SplitPaneArgs>();
|
||||
@@ -342,7 +373,11 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
args->_SplitStyle = ParseSplitState(jsonStyle.asString());
|
||||
}
|
||||
return *args;
|
||||
if (auto jsonStyle{ json[JsonKey(SplitModeKey)] })
|
||||
{
|
||||
args->_SplitMode = ParseSplitModeState(jsonStyle.asString());
|
||||
}
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ namespace TerminalApp
|
||||
Horizontal = 2
|
||||
};
|
||||
|
||||
enum SplitType
|
||||
{
|
||||
Manual = 0,
|
||||
Duplicate = 1
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass NewTerminalArgs {
|
||||
NewTerminalArgs();
|
||||
String Commandline;
|
||||
@@ -82,5 +88,6 @@ namespace TerminalApp
|
||||
{
|
||||
SplitState SplitStyle { get; };
|
||||
NewTerminalArgs TerminalArgs { get; };
|
||||
SplitType SplitMode { get; };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
else if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::SplitPaneArgs>())
|
||||
{
|
||||
_SplitPane(realArgs.SplitStyle(), realArgs.TerminalArgs());
|
||||
_SplitPane(realArgs.SplitStyle(), realArgs.SplitMode(), realArgs.TerminalArgs());
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace winrt::TerminalApp::implementation
|
||||
static Windows::System::VirtualKeyModifiers ConvertVKModifiers(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
|
||||
|
||||
// Defined in AppKeyBindingsSerialization.cpp
|
||||
void LayerJson(const Json::Value& json);
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(const Json::Value& json);
|
||||
Json::Value ToJson();
|
||||
|
||||
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
|
||||
|
||||
@@ -146,6 +146,9 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
|
||||
{ FindKey, ShortcutAction::Find },
|
||||
};
|
||||
|
||||
using ParseResult = std::tuple<IActionArgs, std::vector<TerminalApp::SettingsLoadWarnings>>;
|
||||
using ParseActionFunction = std::function<ParseResult(const Json::Value&)>;
|
||||
|
||||
// Function Description:
|
||||
// - Creates a function that can be used to generate a SplitPaneArgs for the
|
||||
// legacy Split[SplitState] actions. These actions don't accept args from
|
||||
@@ -157,12 +160,12 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
|
||||
// Return Value:
|
||||
// - A function that can be used to "parse" json into one of the legacy
|
||||
// Split[SplitState] args.
|
||||
std::function<IActionArgs(const Json::Value&)> LegacyParseSplitPaneArgs(SplitState style)
|
||||
ParseActionFunction LegacyParseSplitPaneArgs(SplitState style)
|
||||
{
|
||||
auto pfn = [style](const Json::Value & /*value*/) -> IActionArgs {
|
||||
auto pfn = [style](const Json::Value & /*value*/) -> ParseResult {
|
||||
auto args = winrt::make_self<winrt::TerminalApp::implementation::SplitPaneArgs>();
|
||||
args->SplitStyle(style);
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
};
|
||||
return pfn;
|
||||
}
|
||||
@@ -178,12 +181,12 @@ std::function<IActionArgs(const Json::Value&)> LegacyParseSplitPaneArgs(SplitSta
|
||||
// Return Value:
|
||||
// - A function that can be used to "parse" json into one of the legacy
|
||||
// MoveFocus[Direction] args.
|
||||
std::function<IActionArgs(const Json::Value&)> LegacyParseMoveFocusArgs(Direction direction)
|
||||
ParseActionFunction LegacyParseMoveFocusArgs(Direction direction)
|
||||
{
|
||||
auto pfn = [direction](const Json::Value & /*value*/) -> IActionArgs {
|
||||
auto pfn = [direction](const Json::Value & /*value*/) -> ParseResult {
|
||||
auto args = winrt::make_self<winrt::TerminalApp::implementation::MoveFocusArgs>();
|
||||
args->Direction(direction);
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
};
|
||||
return pfn;
|
||||
}
|
||||
@@ -199,12 +202,12 @@ std::function<IActionArgs(const Json::Value&)> LegacyParseMoveFocusArgs(Directio
|
||||
// Return Value:
|
||||
// - A function that can be used to "parse" json into one of the legacy
|
||||
// ResizePane[Direction] args.
|
||||
std::function<IActionArgs(const Json::Value&)> LegacyParseResizePaneArgs(Direction direction)
|
||||
ParseActionFunction LegacyParseResizePaneArgs(Direction direction)
|
||||
{
|
||||
auto pfn = [direction](const Json::Value & /*value*/) -> IActionArgs {
|
||||
auto pfn = [direction](const Json::Value & /*value*/) -> ParseResult {
|
||||
auto args = winrt::make_self<winrt::TerminalApp::implementation::ResizePaneArgs>();
|
||||
args->Direction(direction);
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
};
|
||||
return pfn;
|
||||
}
|
||||
@@ -220,14 +223,14 @@ std::function<IActionArgs(const Json::Value&)> LegacyParseResizePaneArgs(Directi
|
||||
// Return Value:
|
||||
// - A function that can be used to "parse" json into one of the legacy
|
||||
// NewTabWithProfile[Index] args.
|
||||
std::function<IActionArgs(const Json::Value&)> LegacyParseNewTabWithProfileArgs(int index)
|
||||
ParseActionFunction LegacyParseNewTabWithProfileArgs(int index)
|
||||
{
|
||||
auto pfn = [index](const Json::Value & /*value*/) -> IActionArgs {
|
||||
auto pfn = [index](const Json::Value & /*value*/) -> ParseResult {
|
||||
auto args = winrt::make_self<winrt::TerminalApp::implementation::NewTabArgs>();
|
||||
auto newTerminalArgs = winrt::make_self<winrt::TerminalApp::implementation::NewTerminalArgs>();
|
||||
newTerminalArgs->ProfileIndex(index);
|
||||
args->TerminalArgs(*newTerminalArgs);
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
};
|
||||
return pfn;
|
||||
}
|
||||
@@ -243,12 +246,12 @@ std::function<IActionArgs(const Json::Value&)> LegacyParseNewTabWithProfileArgs(
|
||||
// Return Value:
|
||||
// - A function that can be used to "parse" json into one of the legacy
|
||||
// SwitchToTab[Index] args.
|
||||
std::function<IActionArgs(const Json::Value&)> LegacyParseSwitchToTabArgs(int index)
|
||||
ParseActionFunction LegacyParseSwitchToTabArgs(int index)
|
||||
{
|
||||
auto pfn = [index](const Json::Value & /*value*/) -> IActionArgs {
|
||||
auto pfn = [index](const Json::Value & /*value*/) -> ParseResult {
|
||||
auto args = winrt::make_self<winrt::TerminalApp::implementation::SwitchToTabArgs>();
|
||||
args->TabIndex(index);
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
};
|
||||
return pfn;
|
||||
}
|
||||
@@ -261,11 +264,11 @@ std::function<IActionArgs(const Json::Value&)> LegacyParseSwitchToTabArgs(int in
|
||||
// - direction: the direction to create the parse function for.
|
||||
// Return Value:
|
||||
// - A CopyTextArgs with TrimWhitespace set to true, to emulate "CopyTextWithoutNewlines".
|
||||
IActionArgs LegacyParseCopyTextWithoutNewlinesArgs(const Json::Value& /*json*/)
|
||||
ParseResult LegacyParseCopyTextWithoutNewlinesArgs(const Json::Value& /*json*/)
|
||||
{
|
||||
auto args = winrt::make_self<winrt::TerminalApp::implementation::CopyTextArgs>();
|
||||
args->TrimWhitespace(false);
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
};
|
||||
|
||||
// Function Description:
|
||||
@@ -276,12 +279,12 @@ IActionArgs LegacyParseCopyTextWithoutNewlinesArgs(const Json::Value& /*json*/)
|
||||
// - delta: the font size delta to create the parse function for.
|
||||
// Return Value:
|
||||
// - A function that can be used to "parse" json into an AdjustFontSizeArgs.
|
||||
std::function<IActionArgs(const Json::Value&)> LegacyParseAdjustFontSizeArgs(int delta)
|
||||
ParseActionFunction LegacyParseAdjustFontSizeArgs(int delta)
|
||||
{
|
||||
auto pfn = [delta](const Json::Value & /*value*/) -> IActionArgs {
|
||||
auto pfn = [delta](const Json::Value & /*value*/) -> ParseResult {
|
||||
auto args = winrt::make_self<winrt::TerminalApp::implementation::AdjustFontSizeArgs>();
|
||||
args->Delta(delta);
|
||||
return *args;
|
||||
return { *args, {} };
|
||||
};
|
||||
return pfn;
|
||||
}
|
||||
@@ -291,7 +294,7 @@ std::function<IActionArgs(const Json::Value&)> LegacyParseAdjustFontSizeArgs(int
|
||||
// from json. Each type of IActionArgs that can accept arbitrary args should be
|
||||
// placed into this map, with the corresponding deserializer function as the
|
||||
// value.
|
||||
static const std::map<ShortcutAction, std::function<IActionArgs(const Json::Value&)>, std::less<>> argParsers{
|
||||
static const std::map<ShortcutAction, ParseActionFunction, std::less<>> argParsers{
|
||||
{ ShortcutAction::CopyText, winrt::TerminalApp::implementation::CopyTextArgs::FromJson },
|
||||
{ ShortcutAction::CopyTextWithoutNewlines, LegacyParseCopyTextWithoutNewlinesArgs },
|
||||
|
||||
@@ -427,8 +430,14 @@ static ShortcutAction GetActionFromString(const std::string_view actionString)
|
||||
// `"unbound"`, then we'll clear the keybinding from the existing keybindings.
|
||||
// Arguments:
|
||||
// - json: and array of JsonObject's to deserialize into our _keyShortcuts mapping.
|
||||
void winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::Value& json)
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::Value& json)
|
||||
{
|
||||
// It's possible that the user provided keybindings have some warnings in
|
||||
// them - problems that we should alert the user to, but we can recover
|
||||
// from. Most of these warnings cannot be detected later in the Validate
|
||||
// settings phase, so we'll collect them now.
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> warnings;
|
||||
|
||||
for (const auto& value : json)
|
||||
{
|
||||
if (!value.isObject())
|
||||
@@ -441,11 +450,22 @@ void winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::V
|
||||
|
||||
if (keys)
|
||||
{
|
||||
if (!keys.isArray() || keys.size() != 1)
|
||||
const auto validString = keys.isString();
|
||||
const auto validArray = keys.isArray() && keys.size() == 1;
|
||||
|
||||
// GH#4239 - If the user provided more than one key
|
||||
// chord to a "keys" array, warn the user here.
|
||||
// TODO: GH#1334 - remove this check.
|
||||
if (keys.isArray() && keys.size() > 1)
|
||||
{
|
||||
warnings.push_back(::TerminalApp::SettingsLoadWarnings::TooManyKeysForChord);
|
||||
}
|
||||
|
||||
if (!validString && !validArray)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto keyChordString = winrt::to_hstring(keys[0].asString());
|
||||
const auto keyChordString = keys.isString() ? winrt::to_hstring(keys.asString()) : winrt::to_hstring(keys[0].asString());
|
||||
// Invalid is our placeholder that the action was not parsed.
|
||||
ShortcutAction action = ShortcutAction::Invalid;
|
||||
|
||||
@@ -484,13 +504,21 @@ void winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::V
|
||||
// does, we'll try to deserialize any "args" that were provided with
|
||||
// the binding.
|
||||
IActionArgs args{ nullptr };
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> parseWarnings;
|
||||
const auto deserializersIter = argParsers.find(action);
|
||||
if (deserializersIter != argParsers.end())
|
||||
{
|
||||
auto pfn = deserializersIter->second;
|
||||
if (pfn)
|
||||
{
|
||||
args = pfn(argsVal);
|
||||
std::tie(args, parseWarnings) = pfn(argsVal);
|
||||
}
|
||||
warnings.insert(warnings.end(), parseWarnings.begin(), parseWarnings.end());
|
||||
|
||||
// if an arg parser was registered, but failed, bail
|
||||
if (pfn && args == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,4 +549,6 @@ void winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::V
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
@@ -27,14 +27,17 @@ namespace winrt
|
||||
// !!! IMPORTANT !!!
|
||||
// Make sure that these keys are in the same order as the
|
||||
// SettingsLoadWarnings/Errors enum is!
|
||||
static const std::array<std::wstring_view, 5> settingsLoadWarningsLabels {
|
||||
static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadWarnings::WARNINGS_SIZE)> settingsLoadWarningsLabels {
|
||||
USES_RESOURCE(L"MissingDefaultProfileText"),
|
||||
USES_RESOURCE(L"DuplicateProfileText"),
|
||||
USES_RESOURCE(L"UnknownColorSchemeText"),
|
||||
USES_RESOURCE(L"InvalidBackgroundImage"),
|
||||
USES_RESOURCE(L"InvalidIcon")
|
||||
USES_RESOURCE(L"InvalidIcon"),
|
||||
USES_RESOURCE(L"AtLeastOneKeybindingWarning"),
|
||||
USES_RESOURCE(L"TooManyKeysForChord"),
|
||||
USES_RESOURCE(L"MissingRequiredParameter")
|
||||
};
|
||||
static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels {
|
||||
static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadErrors::ERRORS_SIZE)> settingsLoadErrorsLabels {
|
||||
USES_RESOURCE(L"NoProfilesText"),
|
||||
USES_RESOURCE(L"AllProfilesHiddenText")
|
||||
};
|
||||
@@ -112,6 +115,27 @@ static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceD
|
||||
return textRun;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns whether the user is either a member of the Administrators group or
|
||||
// is currently elevated.
|
||||
// Return Value:
|
||||
// - true if the user is an administrator
|
||||
static bool _isUserAdmin() noexcept
|
||||
try
|
||||
{
|
||||
SID_IDENTIFIER_AUTHORITY ntAuthority{ SECURITY_NT_AUTHORITY };
|
||||
wil::unique_sid adminGroupSid{};
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
BOOL b;
|
||||
THROW_IF_WIN32_BOOL_FALSE(CheckTokenMembership(NULL, adminGroupSid.get(), &b));
|
||||
return !!b;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
AppLogic::AppLogic() :
|
||||
@@ -128,6 +152,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// The TerminalPage has to be constructed during our construction, to
|
||||
// make sure that there's a terminal page for callers of
|
||||
// SetTitleBarContent
|
||||
_isElevated = _isUserAdmin();
|
||||
_root = winrt::make_self<TerminalPage>();
|
||||
}
|
||||
|
||||
@@ -142,6 +167,17 @@ namespace winrt::TerminalApp::implementation
|
||||
return _isUwp;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called around the codebase to discover if Terminal is running elevated
|
||||
// Arguments:
|
||||
// - <none> - reports internal state
|
||||
// Return Value:
|
||||
// - True if elevated, false otherwise.
|
||||
bool AppLogic::IsElevated() const noexcept
|
||||
{
|
||||
return _isElevated;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called by UWP context invoker to let us know that we may have to change some of our behaviors
|
||||
// for being a UWP
|
||||
@@ -364,11 +400,44 @@ namespace winrt::TerminalApp::implementation
|
||||
// Use the default profile to determine how big of a window we need.
|
||||
const auto [_, settings] = _settings->BuildSettings(nullptr);
|
||||
|
||||
// TODO MSFT:21150597 - If the global setting "Always show tab bar" is
|
||||
auto proposedSize = TermControl::GetProposedDimensions(settings, dpi);
|
||||
|
||||
const float scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
|
||||
|
||||
// GH#2061 - If the global setting "Always show tab bar" is
|
||||
// set or if "Show tabs in title bar" is set, then we'll need to add
|
||||
// the height of the tab bar here.
|
||||
if (_settings->GlobalSettings().GetShowTabsInTitlebar())
|
||||
{
|
||||
// If we're showing the tabs in the titlebar, we need to use a
|
||||
// TitlebarContol here to calculate how much space to reserve.
|
||||
//
|
||||
// We'll create a fake TitlebarControl, and we'll propose an
|
||||
// available size to it with Measure(). After Measure() is called,
|
||||
// the TitlebarControl's DesiredSize will contain the _unscaled_
|
||||
// size that the titlebar would like to use. We'll use that as part
|
||||
// of the height calculation here.
|
||||
auto titlebar = TitlebarControl{ static_cast<uint64_t>(0) };
|
||||
titlebar.Measure({ SHRT_MAX, SHRT_MAX });
|
||||
proposedSize.Y += (titlebar.DesiredSize().Height) * scale;
|
||||
}
|
||||
else if (_settings->GlobalSettings().GetAlwaysShowTabs())
|
||||
{
|
||||
// Otherwise, let's use a TabRowControl to calculate how much extra
|
||||
// space we'll need.
|
||||
//
|
||||
// Similarly to above, we'll measure it with an arbitrarily large
|
||||
// available space, to make sure we get all the space it wants.
|
||||
auto tabControl = TabRowControl();
|
||||
tabControl.Measure({ SHRT_MAX, SHRT_MAX });
|
||||
|
||||
return TermControl::GetProposedDimensions(settings, dpi);
|
||||
// For whatever reason, there's about 6px of unaccounted-for space
|
||||
// in the application. I couldn't tell you where these 6px are
|
||||
// coming from, but they need to be included in this math.
|
||||
proposedSize.Y += (tabControl.DesiredSize().Height + 6) * scale;
|
||||
}
|
||||
|
||||
return proposedSize;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -704,6 +773,41 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Implements the F7 handler (per GH#638)
|
||||
// Return value:
|
||||
// - whether F7 was handled
|
||||
bool AppLogic::OnF7Pressed()
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
// Manually bubble the OnF7Pressed event up through the focus tree.
|
||||
auto xamlRoot{ _root->XamlRoot() };
|
||||
auto focusedObject{ Windows::UI::Xaml::Input::FocusManager::GetFocusedElement(xamlRoot) };
|
||||
do
|
||||
{
|
||||
if (auto f7Listener{ focusedObject.try_as<IF7Listener>() })
|
||||
{
|
||||
if (f7Listener.OnF7Pressed())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// otherwise, keep walking. bubble the event manually.
|
||||
}
|
||||
|
||||
if (auto focusedElement{ focusedObject.try_as<Windows::UI::Xaml::FrameworkElement>() })
|
||||
{
|
||||
focusedObject = focusedElement.Parent();
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // we hit a non-FE object, stop bubbling.
|
||||
}
|
||||
} while (focusedObject);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Used to tell the app that the 'X' button has been clicked and
|
||||
// the user wants to close the app. We kick off the close warning
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void Create();
|
||||
bool IsUwp() const noexcept;
|
||||
void RunAsUwp();
|
||||
bool IsElevated() const noexcept;
|
||||
void LoadSettings();
|
||||
[[nodiscard]] std::shared_ptr<::TerminalApp::CascadiaSettings> GetSettings() const noexcept;
|
||||
|
||||
@@ -38,6 +39,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
hstring Title();
|
||||
void TitlebarClicked();
|
||||
bool OnF7Pressed();
|
||||
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
@@ -46,6 +48,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
private:
|
||||
bool _isUwp{ false };
|
||||
bool _isElevated{ false };
|
||||
|
||||
// If you add controls here, but forget to null them either here or in
|
||||
// the ctor, you're going to have a bad time. It'll mysteriously fail to
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import "../TerminalPage.idl";
|
||||
import "../ShortcutActionDispatch.idl";
|
||||
import "../IF7Listener.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
@@ -12,7 +13,7 @@ namespace TerminalApp
|
||||
MaximizedMode,
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass AppLogic {
|
||||
[default_interface] runtimeclass AppLogic: IF7Listener {
|
||||
AppLogic();
|
||||
|
||||
// For your own sanity, it's better to do setup outside the ctor.
|
||||
@@ -24,6 +25,7 @@ namespace TerminalApp
|
||||
|
||||
Boolean IsUwp();
|
||||
void RunAsUwp();
|
||||
Boolean IsElevated();
|
||||
|
||||
Int32 SetStartupCommandline(String[] commands);
|
||||
String EarlyExitMessage { get; };
|
||||
|
||||
@@ -206,9 +206,11 @@ void CascadiaSettings::_ValidateSettings()
|
||||
// there's _NO_ keys bound to any actions. That's highly irregular, and
|
||||
// likely an indication of an error somehow.
|
||||
|
||||
// TODO:GH#3522 With variable args to keybindings, it's possible that a user
|
||||
// GH#3522 - With variable args to keybindings, it's possible that a user
|
||||
// set a keybinding without all the required args for an action. Display a
|
||||
// warning if an action didn't have a required arg.
|
||||
// This will also catch other keybinding warnings, like from GH#4239
|
||||
_ValidateKeybindings();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -651,3 +653,23 @@ GUID CascadiaSettings::_GetProfileForIndex(std::optional<int> index) const
|
||||
}
|
||||
return profileGuid;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - If there were any warnings we generated while parsing the user's
|
||||
// keybindings, add them to the list of warnings here. If there were warnings
|
||||
// generated in this way, we'll add a AtLeastOneKeybindingWarning, which will
|
||||
// act as a header for the other warnings
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CascadiaSettings::_ValidateKeybindings()
|
||||
{
|
||||
auto keybindingWarnings = _globals.GetKeybindingsWarnings();
|
||||
|
||||
if (!keybindingWarnings.empty())
|
||||
{
|
||||
_warnings.push_back(::TerminalApp::SettingsLoadWarnings::AtLeastOneKeybindingWarning);
|
||||
_warnings.insert(_warnings.end(), keybindingWarnings.begin(), keybindingWarnings.end());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ private:
|
||||
void _RemoveHiddenProfiles();
|
||||
void _ValidateAllSchemesExist();
|
||||
void _ValidateMediaResources();
|
||||
void _ValidateKeybindings();
|
||||
|
||||
friend class TerminalAppLocalTests::SettingsTests;
|
||||
friend class TerminalAppLocalTests::ProfileTests;
|
||||
|
||||
@@ -43,6 +43,7 @@ static constexpr std::wstring_view SystemThemeValue{ L"system" };
|
||||
|
||||
GlobalAppSettings::GlobalAppSettings() :
|
||||
_keybindings{ winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>() },
|
||||
_keybindingsWarnings{},
|
||||
_colorSchemes{},
|
||||
_defaultProfile{},
|
||||
_alwaysShowTabs{ true },
|
||||
@@ -330,7 +331,14 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
|
||||
if (auto keybindings{ json[JsonKey(KeybindingsKey)] })
|
||||
{
|
||||
_keybindings->LayerJson(keybindings);
|
||||
auto warnings = _keybindings->LayerJson(keybindings);
|
||||
// It's possible that the user provided keybindings have some warnings
|
||||
// in them - problems that we should alert the user to, but we can
|
||||
// recover from. Most of these warnings cannot be detected later in the
|
||||
// Validate settings phase, so we'll collect them now. If there were any
|
||||
// warnings generated from parsing these keybindings, add them to our
|
||||
// list of warnings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
}
|
||||
|
||||
if (auto snapToGridOnResize{ json[JsonKey(SnapToGridOnResizeKey)] })
|
||||
@@ -537,3 +545,17 @@ void GlobalAppSettings::AddColorScheme(ColorScheme scheme)
|
||||
std::wstring name{ scheme.GetName() };
|
||||
_colorSchemes[name] = std::move(scheme);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Return the warnings that we've collected during parsing the JSON for the
|
||||
// keybindings. It's possible that the user provided keybindings have some
|
||||
// warnings in them - problems that we should alert the user to, but we can
|
||||
// recover from.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
std::vector<TerminalApp::SettingsLoadWarnings> GlobalAppSettings::GetKeybindingsWarnings() const
|
||||
{
|
||||
return _keybindingsWarnings;
|
||||
}
|
||||
|
||||
@@ -83,11 +83,14 @@ public:
|
||||
|
||||
void ApplyToSettings(winrt::Microsoft::Terminal::Settings::TerminalSettings& settings) const noexcept;
|
||||
|
||||
std::vector<TerminalApp::SettingsLoadWarnings> GetKeybindingsWarnings() const;
|
||||
|
||||
GETSET_PROPERTY(bool, SnapToGridOnResize, true);
|
||||
|
||||
private:
|
||||
GUID _defaultProfile;
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::AppKeyBindings> _keybindings;
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> _keybindingsWarnings;
|
||||
|
||||
std::unordered_map<std::wstring, ColorScheme> _colorSchemes;
|
||||
|
||||
|
||||
16
src/cascadia/TerminalApp/IF7Listener.idl
Normal file
16
src/cascadia/TerminalApp/IF7Listener.idl
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
// C++/winrt makes it difficult to share this idl between two projects,
|
||||
// Instead, we just pin the uuid and include it in both TermControl and App
|
||||
// If you update this one, please update the one in TerminalControl\TermControl.idl
|
||||
// If you change this interface, please update the guid.
|
||||
// If you press F7 and get a runtime error, go make sure both copies are the same.
|
||||
[uuid("339e1a87-5315-4da6-96f0-565549b6472b")]
|
||||
interface IF7Listener
|
||||
{
|
||||
Boolean OnF7Pressed();
|
||||
}
|
||||
}
|
||||
@@ -131,16 +131,24 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
</ResourceDictionary>
|
||||
</StackPanel.Resources>
|
||||
|
||||
<Button Height="36.0" MinWidth="46.0" Width="46.0" x:Name="MinimizeButton" Style="{StaticResource CaptionButton}" Click="_MinimizeClick"
|
||||
AutomationProperties.Name="Minimize">
|
||||
<Button Height="36.0" MinWidth="46.0" Width="46.0"
|
||||
x:Name="MinimizeButton"
|
||||
x:Uid="WindowMinimizeButton"
|
||||
Style="{StaticResource CaptionButton}"
|
||||
Click="_MinimizeClick"
|
||||
AutomationProperties.AccessibilityView="Raw">
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
<x:String x:Key="CaptionButtonPath">M 0 0 H 10</x:String>
|
||||
</ResourceDictionary>
|
||||
</Button.Resources>
|
||||
</Button>
|
||||
<Button Height="36.0" MinWidth="46.0" Width="46.0" x:Name="MaximizeButton" Style="{StaticResource CaptionButton}" Click="_MaximizeClick"
|
||||
AutomationProperties.Name="Maximize">
|
||||
<Button Height="36.0" MinWidth="46.0" Width="46.0"
|
||||
x:Name="MaximizeButton"
|
||||
x:Uid="WindowMaximizeButton"
|
||||
Style="{StaticResource CaptionButton}"
|
||||
Click="_MaximizeClick"
|
||||
AutomationProperties.AccessibilityView="Raw">
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
<x:String x:Key="CaptionButtonPath">M 0 0 H 10 V 10 H 0 V 0</x:String>
|
||||
@@ -148,8 +156,12 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
</ResourceDictionary>
|
||||
</Button.Resources>
|
||||
</Button>
|
||||
<Button Height="36.0" MinWidth="46.0" Width="46.0" x:Name="CloseButton" Style="{StaticResource CaptionButton}" Click="_CloseClick"
|
||||
AutomationProperties.Name="Close">
|
||||
<Button Height="36.0" MinWidth="46.0" Width="46.0"
|
||||
x:Name="CloseButton"
|
||||
x:Uid="WindowCloseButton"
|
||||
Style="{StaticResource CaptionButton}"
|
||||
Click="_CloseClick"
|
||||
AutomationProperties.AccessibilityView="Raw">
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
@@ -803,10 +803,10 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
|
||||
const auto paneSizes = _CalcChildrenSizes(rootSize.Width);
|
||||
|
||||
auto firstColDef = Controls::ColumnDefinition();
|
||||
firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first));
|
||||
firstColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
|
||||
|
||||
auto secondColDef = Controls::ColumnDefinition();
|
||||
secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second));
|
||||
secondColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
|
||||
|
||||
_root.ColumnDefinitions().Append(firstColDef);
|
||||
_root.ColumnDefinitions().Append(secondColDef);
|
||||
@@ -819,10 +819,10 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
|
||||
const auto paneSizes = _CalcChildrenSizes(rootSize.Height);
|
||||
|
||||
auto firstRowDef = Controls::RowDefinition();
|
||||
firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first));
|
||||
firstRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
|
||||
|
||||
auto secondRowDef = Controls::RowDefinition();
|
||||
secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second));
|
||||
secondRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
|
||||
|
||||
_root.RowDefinitions().Append(firstRowDef);
|
||||
_root.RowDefinitions().Append(secondRowDef);
|
||||
|
||||
@@ -49,6 +49,8 @@ static constexpr std::string_view BackgroundImageKey{ "backgroundImage" };
|
||||
static constexpr std::string_view BackgroundImageOpacityKey{ "backgroundImageOpacity" };
|
||||
static constexpr std::string_view BackgroundImageStretchModeKey{ "backgroundImageStretchMode" };
|
||||
static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" };
|
||||
static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" };
|
||||
static constexpr std::string_view AntialiasingModeKey{ "antialiasingMode" };
|
||||
|
||||
// Possible values for closeOnExit
|
||||
static constexpr std::string_view CloseOnExitAlways{ "always" };
|
||||
@@ -83,8 +85,10 @@ static constexpr std::string_view ImageAlignmentTopRight{ "topRight" };
|
||||
static constexpr std::string_view ImageAlignmentBottomLeft{ "bottomLeft" };
|
||||
static constexpr std::string_view ImageAlignmentBottomRight{ "bottomRight" };
|
||||
|
||||
// Terminal effects
|
||||
static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" };
|
||||
// Possible values for TextAntialiasingMode
|
||||
static constexpr std::wstring_view AntialiasingModeGrayscale{ L"grayscale" };
|
||||
static constexpr std::wstring_view AntialiasingModeCleartype{ L"cleartype" };
|
||||
static constexpr std::wstring_view AntialiasingModeAliased{ L"aliased" };
|
||||
|
||||
Profile::Profile() :
|
||||
Profile(std::nullopt)
|
||||
@@ -124,7 +128,8 @@ Profile::Profile(const std::optional<GUID>& guid) :
|
||||
_backgroundImageOpacity{},
|
||||
_backgroundImageStretchMode{},
|
||||
_backgroundImageAlignment{},
|
||||
_retroTerminalEffect{}
|
||||
_retroTerminalEffect{},
|
||||
_antialiasingMode{ TextAntialiasingMode::Grayscale }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -178,6 +183,7 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
|
||||
terminalSettings.CursorShape(_cursorShape);
|
||||
|
||||
// Fill in the remaining properties from the profile
|
||||
terminalSettings.ProfileName(_name);
|
||||
terminalSettings.UseAcrylic(_useAcrylic);
|
||||
terminalSettings.TintOpacity(_acrylicTransparency);
|
||||
|
||||
@@ -257,6 +263,8 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
|
||||
terminalSettings.RetroTerminalEffect(_retroTerminalEffect.value());
|
||||
}
|
||||
|
||||
terminalSettings.AntialiasingMode(_antialiasingMode);
|
||||
|
||||
return terminalSettings;
|
||||
}
|
||||
|
||||
@@ -377,6 +385,8 @@ Json::Value Profile::ToJson() const
|
||||
root[JsonKey(RetroTerminalEffectKey)] = _retroTerminalEffect.value();
|
||||
}
|
||||
|
||||
root[JsonKey(AntialiasingModeKey)] = SerializeTextAntialiasingMode(_antialiasingMode).data();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -742,6 +752,12 @@ void Profile::LayerJson(const Json::Value& json)
|
||||
JsonUtils::GetOptionalValue(json, BackgroundImageAlignmentKey, _backgroundImageAlignment, &Profile::_ConvertJsonToAlignment);
|
||||
|
||||
JsonUtils::GetOptionalValue(json, RetroTerminalEffectKey, _retroTerminalEffect, Profile::_ConvertJsonToBool);
|
||||
|
||||
if (json.isMember(JsonKey(AntialiasingModeKey)))
|
||||
{
|
||||
auto antialiasingMode{ json[JsonKey(AntialiasingModeKey)] };
|
||||
_antialiasingMode = ParseTextAntialiasingMode(GetWstringFromJson(antialiasingMode));
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::SetFontFace(std::wstring fontFace) noexcept
|
||||
@@ -1349,3 +1365,49 @@ void Profile::SetRetroTerminalEffect(bool value) noexcept
|
||||
{
|
||||
_retroTerminalEffect = value;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a user-specified antialiasing mode
|
||||
// corresponding TextAntialiasingMode enum value
|
||||
// Arguments:
|
||||
// - antialiasingMode: The string value from the settings file to parse
|
||||
// Return Value:
|
||||
// - The corresponding enum value which maps to the string provided by the user
|
||||
TextAntialiasingMode Profile::ParseTextAntialiasingMode(const std::wstring& antialiasingMode)
|
||||
{
|
||||
if (antialiasingMode == AntialiasingModeCleartype)
|
||||
{
|
||||
return TextAntialiasingMode::Cleartype;
|
||||
}
|
||||
else if (antialiasingMode == AntialiasingModeAliased)
|
||||
{
|
||||
return TextAntialiasingMode::Aliased;
|
||||
}
|
||||
else if (antialiasingMode == AntialiasingModeGrayscale)
|
||||
{
|
||||
return TextAntialiasingMode::Grayscale;
|
||||
}
|
||||
// default behavior for invalid data
|
||||
return TextAntialiasingMode::Grayscale;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a TextAntialiasingMode to its corresponding
|
||||
// string value.
|
||||
// Arguments:
|
||||
// - antialiasingMode: The enum value to convert to a string.
|
||||
// Return Value:
|
||||
// - The string value for the given TextAntialiasingMode
|
||||
std::wstring_view Profile::SerializeTextAntialiasingMode(const TextAntialiasingMode antialiasingMode)
|
||||
{
|
||||
switch (antialiasingMode)
|
||||
{
|
||||
case TextAntialiasingMode::Cleartype:
|
||||
return AntialiasingModeCleartype;
|
||||
case TextAntialiasingMode::Aliased:
|
||||
return AntialiasingModeAliased;
|
||||
default:
|
||||
case TextAntialiasingMode::Grayscale:
|
||||
return AntialiasingModeGrayscale;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,9 @@ private:
|
||||
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
|
||||
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
|
||||
|
||||
static winrt::Microsoft::Terminal::Settings::TextAntialiasingMode ParseTextAntialiasingMode(const std::wstring& antialiasingMode);
|
||||
static std::wstring_view SerializeTextAntialiasingMode(const winrt::Microsoft::Terminal::Settings::TextAntialiasingMode antialiasingMode);
|
||||
|
||||
static GUID _GenerateGuidForProfile(const std::wstring& name, const std::optional<std::wstring>& source) noexcept;
|
||||
|
||||
static bool _ConvertJsonToBool(const Json::Value& json);
|
||||
@@ -166,6 +169,8 @@ private:
|
||||
|
||||
std::optional<std::wstring> _icon;
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::TextAntialiasingMode _antialiasingMode;
|
||||
|
||||
friend class TerminalAppLocalTests::SettingsTests;
|
||||
friend class TerminalAppLocalTests::ProfileTests;
|
||||
friend class TerminalAppUnitTests::JsonTests;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -223,6 +223,18 @@ Temporarily using the Windows Terminal default settings.
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>Found a profile with an invalid "icon". Defaulting that profile to have no icon. Make sure that when setting an "icon", the value is a valid file path to an image.</value>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Warnings were found while parsing your keybindings:
|
||||
</value>
|
||||
</data>
|
||||
<data name="TooManyKeysForChord" xml:space="preserve">
|
||||
<value> Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.
|
||||
</value>
|
||||
</data>
|
||||
<data name="MissingRequiredParameter" xml:space="preserve">
|
||||
<value> Found a keybinding that was missing a required parameter value. This keybinding will be ignored.
|
||||
</value>
|
||||
</data>
|
||||
<data name="CmdCommandArgDesc" xml:space="preserve">
|
||||
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
|
||||
</data>
|
||||
@@ -256,4 +268,19 @@ Temporarily using the Windows Terminal default settings.
|
||||
<data name="CmdStartingDirArgDesc" xml:space="preserve">
|
||||
<value>Open in the given directory instead of the profile's set startingDirectory</value>
|
||||
</data>
|
||||
<data name="NewTabSplitButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.HelpText" xml:space="preserve">
|
||||
<value>Press the button to open a new terminal tab with your default profile. Open the flyout to select which profile you want to open.</value>
|
||||
</data>
|
||||
<data name="NewTabSplitButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>New Tab</value>
|
||||
</data>
|
||||
<data name="WindowCloseButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
</data>
|
||||
<data name="WindowMaximizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Maximize</value>
|
||||
</data>
|
||||
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Minimize</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -22,6 +22,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<mux:TabView.TabStripFooter>
|
||||
<mux:SplitButton
|
||||
x:Name="NewTabButton"
|
||||
x:Uid="NewTabSplitButton"
|
||||
Click="OnNewTabButtonClick"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -29,7 +30,8 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
UseLayoutRounding="true"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontWeight="SemiLight"
|
||||
FontSize="12">
|
||||
FontSize="12"
|
||||
AutomationProperties.AccessibilityView="Control">
|
||||
<!-- U+E710 is the fancy plus icon. -->
|
||||
<mux:SplitButton.Resources>
|
||||
<!-- Override the SplitButton* resources to match the tab view's button's styles. -->
|
||||
|
||||
@@ -63,6 +63,14 @@ namespace winrt::TerminalApp::implementation
|
||||
_tabView = _tabRow.TabView();
|
||||
_rearranging = false;
|
||||
|
||||
// GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs.
|
||||
// Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process,
|
||||
// but that process is running at a different IL than us.
|
||||
// For now, we're disabling elevated drag.
|
||||
const auto isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated();
|
||||
_tabView.CanReorderTabs(!isElevated);
|
||||
_tabView.CanDragTabs(!isElevated);
|
||||
|
||||
_tabView.TabDragStarting([weakThis{ get_weak() }](auto&& /*o*/, auto&& /*a*/) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
@@ -372,6 +380,7 @@ namespace winrt::TerminalApp::implementation
|
||||
WUX::Controls::IconSourceElement iconElement;
|
||||
iconElement.IconSource(iconSource);
|
||||
profileMenuItem.Icon(iconElement);
|
||||
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
|
||||
}
|
||||
|
||||
if (profile.GetGuid() == defaultProfileGuid)
|
||||
@@ -510,7 +519,6 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Create a connection based on the values in our settings object.
|
||||
const auto connection = _CreateConnectionFromSettings(profileGuid, settings);
|
||||
|
||||
TermControl term{ settings, connection };
|
||||
|
||||
// Add the new tab to the list of our tabs.
|
||||
@@ -602,8 +610,10 @@ namespace winrt::TerminalApp::implementation
|
||||
profile->GetConnectionType() == AzureConnectionType &&
|
||||
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
|
||||
{
|
||||
connection = TerminalConnection::AzureConnection(settings.InitialRows(),
|
||||
settings.InitialCols());
|
||||
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better
|
||||
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
|
||||
azBridgePath.replace_filename(L"TerminalAzBridge.exe");
|
||||
connection = TerminalConnection::ConptyConnection(azBridgePath.wstring(), L".", L"Azure", settings.InitialRows(), settings.InitialCols(), winrt::guid());
|
||||
}
|
||||
|
||||
else if (profile->HasConnectionType() &&
|
||||
@@ -1042,10 +1052,12 @@ namespace winrt::TerminalApp::implementation
|
||||
// Arguments:
|
||||
// - splitType: one value from the TerminalApp::SplitState enum, indicating how the
|
||||
// new pane should be split from its parent.
|
||||
// - splitMode: value from TerminalApp::SplitType enum, indicating the profile to be used in the newly split pane.
|
||||
// - newTerminalArgs: An object that may contain a blob of parameters to
|
||||
// control which profile is created and with possible other
|
||||
// configurations. See CascadiaSettings::BuildSettings for more details.
|
||||
void TerminalPage::_SplitPane(const TerminalApp::SplitState splitType,
|
||||
const TerminalApp::SplitType splitMode,
|
||||
const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs)
|
||||
{
|
||||
// Do nothing if we're requesting no split.
|
||||
@@ -1064,7 +1076,24 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
auto focusedTab = _GetStrongTabImpl(*indexOpt);
|
||||
|
||||
const auto [realGuid, controlSettings] = _settings->BuildSettings(newTerminalArgs);
|
||||
winrt::Microsoft::Terminal::Settings::TerminalSettings controlSettings;
|
||||
GUID realGuid;
|
||||
bool profileFound = false;
|
||||
|
||||
if (splitMode == TerminalApp::SplitType::Duplicate)
|
||||
{
|
||||
std::optional<GUID> current_guid = focusedTab->GetFocusedProfile();
|
||||
if (current_guid)
|
||||
{
|
||||
profileFound = true;
|
||||
controlSettings = _settings->BuildSettings(current_guid.value());
|
||||
realGuid = current_guid.value();
|
||||
}
|
||||
}
|
||||
if (!profileFound)
|
||||
{
|
||||
std::tie(realGuid, controlSettings) = _settings->BuildSettings(newTerminalArgs);
|
||||
}
|
||||
|
||||
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
|
||||
|
||||
@@ -1323,7 +1352,7 @@ namespace winrt::TerminalApp::implementation
|
||||
bool TerminalPage::_CopyText(const bool trimTrailingWhitespace)
|
||||
{
|
||||
const auto control = _GetActiveControl();
|
||||
return control.CopySelectionToClipboard(trimTrailingWhitespace);
|
||||
return control.CopySelectionToClipboard(!trimTrailingWhitespace);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// Todo: add more event implementations here
|
||||
// MSFT:20641986: Add keybindings for New Window
|
||||
void _Scroll(int delta);
|
||||
void _SplitPane(const winrt::TerminalApp::SplitState splitType, const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs = nullptr);
|
||||
void _SplitPane(const winrt::TerminalApp::SplitState splitType, const winrt::TerminalApp::SplitType splitMode = winrt::TerminalApp::SplitType::Manual, const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs = nullptr);
|
||||
void _ResizePane(const Direction& direction);
|
||||
void _ScrollPage(int delta);
|
||||
void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord);
|
||||
|
||||
@@ -25,7 +25,11 @@ namespace TerminalApp
|
||||
DuplicateProfile = 1,
|
||||
UnknownColorScheme = 2,
|
||||
InvalidBackgroundImage = 3,
|
||||
InvalidIcon = 4
|
||||
InvalidIcon = 4,
|
||||
AtLeastOneKeybindingWarning = 5,
|
||||
TooManyKeysForChord = 6,
|
||||
MissingRequiredParameter = 7,
|
||||
WARNINGS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder.
|
||||
};
|
||||
|
||||
// SettingsLoadWarnings are scenarios where the settings had invalid state
|
||||
@@ -33,7 +37,8 @@ namespace TerminalApp
|
||||
enum class SettingsLoadErrors : uint32_t
|
||||
{
|
||||
NoProfiles = 0,
|
||||
AllProfilesHidden = 1
|
||||
AllProfilesHidden = 1,
|
||||
ERRORS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder.
|
||||
};
|
||||
|
||||
// This is a helper class to wrap up a SettingsLoadErrors into a proper
|
||||
|
||||
@@ -63,13 +63,13 @@ std::vector<TerminalApp::Profile> WslDistroGenerator::GenerateProfiles()
|
||||
nullptr,
|
||||
&si,
|
||||
&pi));
|
||||
switch (WaitForSingleObject(pi.hProcess, INFINITE))
|
||||
switch (WaitForSingleObject(pi.hProcess, 2000))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
break;
|
||||
case WAIT_ABANDONED:
|
||||
case WAIT_TIMEOUT:
|
||||
THROW_HR(ERROR_CHILD_NOT_COMPLETE);
|
||||
return profiles;
|
||||
case WAIT_FAILED:
|
||||
THROW_LAST_ERROR();
|
||||
default:
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"icon": "ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png",
|
||||
"padding": "8, 8, 8, 8",
|
||||
"snapOnInput": true,
|
||||
"useAcrylic": false
|
||||
"useAcrylic": false,
|
||||
"antialiasingMode": "grayscale"
|
||||
},
|
||||
{
|
||||
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
|
||||
@@ -48,7 +49,8 @@
|
||||
"icon": "ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png",
|
||||
"padding": "8, 8, 8, 8",
|
||||
"snapOnInput": true,
|
||||
"useAcrylic": false
|
||||
"useAcrylic": false,
|
||||
"antialiasingMode": "grayscale"
|
||||
}
|
||||
],
|
||||
"schemes":
|
||||
@@ -203,55 +205,55 @@
|
||||
],
|
||||
"keybindings":
|
||||
[
|
||||
{ "command": "closePane", "keys": [ "ctrl+shift+w" ] },
|
||||
{ "command": "closeWindow", "keys": [ "alt+f4" ] },
|
||||
{ "command": "copy", "keys": [ "ctrl+shift+c" ] },
|
||||
{ "command": "copy", "keys": [ "ctrl+insert" ] },
|
||||
{ "command": "decreaseFontSize", "keys": [ "ctrl+-" ] },
|
||||
{ "command": "duplicateTab", "keys": [ "ctrl+shift+d" ] },
|
||||
{ "command": "increaseFontSize", "keys": [ "ctrl+=" ] },
|
||||
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": [ "alt+down" ] },
|
||||
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": [ "alt+left" ] },
|
||||
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": [ "alt+right" ] },
|
||||
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": [ "alt+up" ] },
|
||||
{ "command": "newTab", "keys": [ "ctrl+shift+t" ] },
|
||||
{ "command": { "action": "newTab", "index": 0 }, "keys": ["ctrl+shift+1"] },
|
||||
{ "command": { "action": "newTab", "index": 1 }, "keys": ["ctrl+shift+2"] },
|
||||
{ "command": { "action": "newTab", "index": 2 }, "keys": ["ctrl+shift+3"] },
|
||||
{ "command": { "action": "newTab", "index": 3 }, "keys": ["ctrl+shift+4"] },
|
||||
{ "command": { "action": "newTab", "index": 4 }, "keys": ["ctrl+shift+5"] },
|
||||
{ "command": { "action": "newTab", "index": 5 }, "keys": ["ctrl+shift+6"] },
|
||||
{ "command": { "action": "newTab", "index": 6 }, "keys": ["ctrl+shift+7"] },
|
||||
{ "command": { "action": "newTab", "index": 7 }, "keys": ["ctrl+shift+8"] },
|
||||
{ "command": { "action": "newTab", "index": 8 }, "keys": ["ctrl+shift+9"] },
|
||||
{ "command": "nextTab", "keys": [ "ctrl+tab" ] },
|
||||
{ "command": "openNewTabDropdown", "keys": [ "ctrl+shift+space" ] },
|
||||
{ "command": "openSettings", "keys": [ "ctrl+," ] },
|
||||
{ "command": "paste", "keys": [ "ctrl+shift+v" ] },
|
||||
{ "command": "paste", "keys": [ "shift+insert" ] },
|
||||
{ "command": "prevTab", "keys": [ "ctrl+shift+tab" ] },
|
||||
{ "command": "resetFontSize", "keys": ["ctrl+0"]},
|
||||
{ "command": { "action": "resizePane", "direction": "down" }, "keys": [ "alt+shift+down" ] },
|
||||
{ "command": { "action": "resizePane", "direction": "left" }, "keys": [ "alt+shift+left" ] },
|
||||
{ "command": { "action": "resizePane", "direction": "right" }, "keys": [ "alt+shift+right" ] },
|
||||
{ "command": { "action": "resizePane", "direction": "up" }, "keys": [ "alt+shift+up" ] },
|
||||
{ "command": "scrollDown", "keys": [ "ctrl+shift+down" ] },
|
||||
{ "command": "scrollDownPage", "keys": [ "ctrl+shift+pgdn" ] },
|
||||
{ "command": "scrollUp", "keys": [ "ctrl+shift+up" ] },
|
||||
{ "command": "scrollUpPage", "keys": [ "ctrl+shift+pgup" ] },
|
||||
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": [ "alt+shift+-" ] },
|
||||
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": [ "alt+shift+plus" ] },
|
||||
{ "command": { "action": "switchToTab", "index": 0 }, "keys": ["ctrl+alt+1"] },
|
||||
{ "command": { "action": "switchToTab", "index": 1 }, "keys": ["ctrl+alt+2"] },
|
||||
{ "command": { "action": "switchToTab", "index": 2 }, "keys": ["ctrl+alt+3"] },
|
||||
{ "command": { "action": "switchToTab", "index": 3 }, "keys": ["ctrl+alt+4"] },
|
||||
{ "command": { "action": "switchToTab", "index": 4 }, "keys": ["ctrl+alt+5"] },
|
||||
{ "command": { "action": "switchToTab", "index": 5 }, "keys": ["ctrl+alt+6"] },
|
||||
{ "command": { "action": "switchToTab", "index": 6 }, "keys": ["ctrl+alt+7"] },
|
||||
{ "command": { "action": "switchToTab", "index": 7 }, "keys": ["ctrl+alt+8"] },
|
||||
{ "command": { "action": "switchToTab", "index": 8 }, "keys": ["ctrl+alt+9"] },
|
||||
{ "command": "find", "keys": [ "ctrl+shift+f" ] },
|
||||
{ "command": "toggleFullscreen", "keys": [ "alt+enter" ] },
|
||||
{ "command": "toggleFullscreen", "keys": [ "f11" ] }
|
||||
{ "command": "closePane", "keys": "ctrl+shift+w" },
|
||||
{ "command": "closeWindow", "keys": "alt+f4" },
|
||||
{ "command": "copy", "keys": "ctrl+shift+c" },
|
||||
{ "command": "copy", "keys": "ctrl+insert" },
|
||||
{ "command": "decreaseFontSize", "keys": "ctrl+-" },
|
||||
{ "command": "duplicateTab", "keys": "ctrl+shift+d" },
|
||||
{ "command": "increaseFontSize", "keys": "ctrl+=" },
|
||||
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down" },
|
||||
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" },
|
||||
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" },
|
||||
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" },
|
||||
{ "command": "newTab", "keys": "ctrl+shift+t" },
|
||||
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1" },
|
||||
{ "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2" },
|
||||
{ "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3" },
|
||||
{ "command": { "action": "newTab", "index": 3 }, "keys": "ctrl+shift+4" },
|
||||
{ "command": { "action": "newTab", "index": 4 }, "keys": "ctrl+shift+5" },
|
||||
{ "command": { "action": "newTab", "index": 5 }, "keys": "ctrl+shift+6" },
|
||||
{ "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7" },
|
||||
{ "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8" },
|
||||
{ "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9" },
|
||||
{ "command": "nextTab", "keys": "ctrl+tab" },
|
||||
{ "command": "openNewTabDropdown", "keys": "ctrl+shift+space" },
|
||||
{ "command": "openSettings", "keys": "ctrl+," },
|
||||
{ "command": "paste", "keys": "ctrl+shift+v" },
|
||||
{ "command": "paste", "keys": "shift+insert" },
|
||||
{ "command": "prevTab", "keys": "ctrl+shift+tab" },
|
||||
{ "command": "resetFontSize", "keys": "ctrl+0" },
|
||||
{ "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down" },
|
||||
{ "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left" },
|
||||
{ "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right" },
|
||||
{ "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up" },
|
||||
{ "command": "scrollDown", "keys": "ctrl+shift+down" },
|
||||
{ "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" },
|
||||
{ "command": "scrollUp", "keys": "ctrl+shift+up" },
|
||||
{ "command": "scrollUpPage", "keys": "ctrl+shift+pgup" },
|
||||
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": "alt+shift+-" },
|
||||
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": "alt+shift+plus" },
|
||||
{ "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1" },
|
||||
{ "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2" },
|
||||
{ "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3" },
|
||||
{ "command": { "action": "switchToTab", "index": 3 }, "keys": "ctrl+alt+4" },
|
||||
{ "command": { "action": "switchToTab", "index": 4 }, "keys": "ctrl+alt+5" },
|
||||
{ "command": { "action": "switchToTab", "index": 5 }, "keys": "ctrl+alt+6" },
|
||||
{ "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7" },
|
||||
{ "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8" },
|
||||
{ "command": { "action": "switchToTab", "index": 8 }, "keys": "ctrl+alt+9" },
|
||||
{ "command": "find", "keys": "ctrl+shift+f" },
|
||||
{ "command": "toggleFullscreen", "keys": "alt+enter" },
|
||||
{ "command": "toggleFullscreen", "keys": "f11" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
<ItemGroup>
|
||||
<!-- If you add idl files here, make sure to include their implementation's
|
||||
header in TerminalApp.vcxproj (as well as in this file) -->
|
||||
<Midl Include="../IF7Listener.idl" />
|
||||
<Midl Include="../App.idl">
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</Midl>
|
||||
@@ -213,7 +214,7 @@
|
||||
</ItemGroup>
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
<PRIResource Include="../Resources/en-US/Resources.resw" />
|
||||
<PRIResource Include="../Resources/Resources.language-en.resw" />
|
||||
<None Include="../packages.config" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
@@ -354,4 +355,4 @@
|
||||
<Target Name="_TerminalAppGenerateUserSettingsH" Inputs="..\userDefaults.json" Outputs="Generated Files\userDefaults.h" BeforeTargets="BeforeClCompile">
|
||||
<Exec Command="powershell.exe -noprofile –ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile ..\userDefaults.json -OutPath '"Generated Files\userDefaults.h"' -VariableName UserSettingsJson" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <winrt/Windows.UI.Xaml.Hosting.h>
|
||||
#include "winrt/Windows.UI.Xaml.Markup.h"
|
||||
#include "winrt/Windows.UI.Xaml.Documents.h"
|
||||
#include "winrt/Windows.UI.Xaml.Automation.h"
|
||||
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
|
||||
|
||||
#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
|
||||
|
||||
87
src/cascadia/TerminalAzBridge/ConsoleInputReader.cpp
Normal file
87
src/cascadia/TerminalAzBridge/ConsoleInputReader.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ConsoleInputReader.h"
|
||||
#include "unicode.hpp"
|
||||
|
||||
ConsoleInputReader::ConsoleInputReader(HANDLE handle) :
|
||||
_handle(handle)
|
||||
{
|
||||
_buffer.resize(BufferSize);
|
||||
_convertedString.reserve(BufferSize);
|
||||
}
|
||||
|
||||
void ConsoleInputReader::SetWindowSizeChangedCallback(std::function<void()> callback)
|
||||
{
|
||||
_windowSizeChangedCallback = std::move(callback);
|
||||
}
|
||||
|
||||
std::optional<std::wstring_view> ConsoleInputReader::Read()
|
||||
{
|
||||
DWORD readCount{ 0 };
|
||||
|
||||
_convertedString.clear();
|
||||
while (_convertedString.empty())
|
||||
{
|
||||
_buffer.resize(BufferSize);
|
||||
BOOL succeeded =
|
||||
ReadConsoleInputW(_handle, _buffer.data(), gsl::narrow_cast<DWORD>(_buffer.size()), &readCount);
|
||||
if (!succeeded)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
_buffer.resize(readCount);
|
||||
for (auto it = _buffer.begin(); it != _buffer.end(); ++it)
|
||||
{
|
||||
if (it->EventType == WINDOW_BUFFER_SIZE_EVENT && _windowSizeChangedCallback)
|
||||
{
|
||||
_windowSizeChangedCallback();
|
||||
}
|
||||
else if (it->EventType == KEY_EVENT)
|
||||
{
|
||||
const auto& keyEvent = it->Event.KeyEvent;
|
||||
if (keyEvent.bKeyDown || (!keyEvent.bKeyDown && keyEvent.wVirtualKeyCode == VK_MENU))
|
||||
{
|
||||
// Got a high surrogate at the end of the buffer
|
||||
if (IS_HIGH_SURROGATE(keyEvent.uChar.UnicodeChar))
|
||||
{
|
||||
_highSurrogate.emplace(keyEvent.uChar.UnicodeChar);
|
||||
continue; // we've consumed it -- only dispatch it if we get a low
|
||||
}
|
||||
|
||||
if (IS_LOW_SURROGATE(keyEvent.uChar.UnicodeChar))
|
||||
{
|
||||
// No matter what we do, we want to destructively consume the high surrogate
|
||||
if (const auto oldHighSurrogate{ std::exchange(_highSurrogate, std::nullopt) })
|
||||
{
|
||||
_convertedString.push_back(*_highSurrogate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we get a low without a high surrogate, we've done everything we can.
|
||||
// This is an illegal state.
|
||||
_convertedString.push_back(UNICODE_REPLACEMENT);
|
||||
continue; // onto the next event
|
||||
}
|
||||
}
|
||||
|
||||
// (\0 with a scancode is probably a modifier key, not a VT input key)
|
||||
if (keyEvent.uChar.UnicodeChar != L'\0' || keyEvent.wVirtualScanCode == 0)
|
||||
{
|
||||
if (_highSurrogate) // non-destructive: we don't want to set it to nullopt needlessly for every character
|
||||
{
|
||||
// If we get a high surrogate *here*, we didn't find a low surrogate.
|
||||
// This state is also illegal.
|
||||
_convertedString.push_back(UNICODE_REPLACEMENT);
|
||||
_highSurrogate.reset();
|
||||
}
|
||||
_convertedString.push_back(keyEvent.uChar.UnicodeChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _convertedString;
|
||||
}
|
||||
33
src/cascadia/TerminalAzBridge/ConsoleInputReader.h
Normal file
33
src/cascadia/TerminalAzBridge/ConsoleInputReader.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*++
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
|
||||
ConsoleInputReader.h
|
||||
|
||||
Abstract:
|
||||
|
||||
This file contains a class whose sole purpose is to
|
||||
abstract away a bunch of details you usually need to
|
||||
know to read VT from a console input handle.
|
||||
|
||||
--*/
|
||||
|
||||
class ConsoleInputReader
|
||||
{
|
||||
public:
|
||||
ConsoleInputReader(HANDLE handle);
|
||||
void SetWindowSizeChangedCallback(std::function<void()> callback);
|
||||
std::optional<std::wstring_view> Read();
|
||||
|
||||
private:
|
||||
static constexpr size_t BufferSize{ 128 };
|
||||
|
||||
HANDLE _handle;
|
||||
std::wstring _convertedString;
|
||||
std::vector<INPUT_RECORD> _buffer;
|
||||
std::optional<wchar_t> _highSurrogate;
|
||||
std::function<void()> _windowSizeChangedCallback;
|
||||
};
|
||||
71
src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj
Normal file
71
src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{067F0A06-FCB7-472C-96E9-B03B54E8E18D}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>TerminalAzBridge</RootNamespace>
|
||||
<ProjectName>TerminalAzBridge</ProjectName>
|
||||
<TargetName>TerminalAzBridge</TargetName>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<OpenConsoleUniversalApp>false</OpenConsoleUniversalApp>
|
||||
<ApplicationType>Windows Store</ApplicationType>
|
||||
<NoOutputRedirection>true</NoOutputRedirection>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateManifest>true</GenerateManifest>
|
||||
<EmbedManifest>true</EmbedManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Source Files -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="ConsoleInputReader.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="ConsoleInputReader.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<!-- Dependencies -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj">
|
||||
<Project>{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
This ItemGroup and the Globals PropertyGroup below it are required in order
|
||||
to enable F5 debugging for the unpackaged application
|
||||
-->
|
||||
<ItemGroup>
|
||||
<PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_general.xml" />
|
||||
<PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_local_windows.xml" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
|
||||
</Project>
|
||||
104
src/cascadia/TerminalAzBridge/main.cpp
Normal file
104
src/cascadia/TerminalAzBridge/main.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "winrt/Microsoft.Terminal.TerminalConnection.h"
|
||||
#include "ConsoleInputReader.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
||||
|
||||
static COORD GetConsoleScreenSize(HANDLE outputHandle)
|
||||
{
|
||||
CONSOLE_SCREEN_BUFFER_INFOEX csbiex{};
|
||||
csbiex.cbSize = sizeof(csbiex);
|
||||
GetConsoleScreenBufferInfoEx(outputHandle, &csbiex);
|
||||
return {
|
||||
(csbiex.srWindow.Right - csbiex.srWindow.Left) + 1,
|
||||
(csbiex.srWindow.Bottom - csbiex.srWindow.Top) + 1
|
||||
};
|
||||
}
|
||||
|
||||
static ConnectionState RunConnectionToCompletion(const ITerminalConnection& connection, HANDLE outputHandle, HANDLE inputHandle)
|
||||
{
|
||||
connection.TerminalOutput([outputHandle](const winrt::hstring& output) {
|
||||
WriteConsoleW(outputHandle, output.data(), output.size(), nullptr, nullptr);
|
||||
});
|
||||
|
||||
// Detach a thread to spin the console read indefinitely.
|
||||
// This application exits when the connection is closed, so
|
||||
// the connection's lifetime will outlast this thread.
|
||||
std::thread([connection, outputHandle, inputHandle] {
|
||||
ConsoleInputReader reader{ inputHandle };
|
||||
reader.SetWindowSizeChangedCallback([&]() {
|
||||
const auto size = GetConsoleScreenSize(outputHandle);
|
||||
|
||||
connection.Resize(size.Y, size.X);
|
||||
});
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto input = reader.Read();
|
||||
if (input)
|
||||
{
|
||||
connection.WriteInput(*input);
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
|
||||
std::condition_variable stateChangeVar;
|
||||
std::optional<ConnectionState> state;
|
||||
std::mutex stateMutex;
|
||||
|
||||
connection.StateChanged([&](auto&& /*s*/, auto&& /*e*/) {
|
||||
std::unique_lock<std::mutex> lg{ stateMutex };
|
||||
state = connection.State();
|
||||
stateChangeVar.notify_all();
|
||||
});
|
||||
|
||||
connection.Start();
|
||||
|
||||
std::unique_lock<std::mutex> lg{ stateMutex };
|
||||
stateChangeVar.wait(lg, [&]() {
|
||||
if (!state.has_value())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return state.value() == ConnectionState::Closed || state.value() == ConnectionState::Failed;
|
||||
});
|
||||
|
||||
return state.value();
|
||||
}
|
||||
|
||||
int wmain(int /*argc*/, wchar_t** /*argv*/)
|
||||
{
|
||||
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
||||
|
||||
DWORD inputMode{}, outputMode{};
|
||||
HANDLE conIn{ GetStdHandle(STD_INPUT_HANDLE) }, conOut{ GetStdHandle(STD_OUTPUT_HANDLE) };
|
||||
UINT codepage{ GetConsoleCP() }, outputCodepage{ GetConsoleOutputCP() };
|
||||
|
||||
RETURN_IF_WIN32_BOOL_FALSE(GetConsoleMode(conIn, &inputMode));
|
||||
RETURN_IF_WIN32_BOOL_FALSE(GetConsoleMode(conOut, &outputMode));
|
||||
|
||||
RETURN_IF_WIN32_BOOL_FALSE(SetConsoleMode(conIn, ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT));
|
||||
RETURN_IF_WIN32_BOOL_FALSE(SetConsoleMode(conOut, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WRAP_AT_EOL_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN));
|
||||
RETURN_IF_WIN32_BOOL_FALSE(SetConsoleCP(CP_UTF8));
|
||||
RETURN_IF_WIN32_BOOL_FALSE(SetConsoleOutputCP(CP_UTF8));
|
||||
|
||||
auto restoreConsoleModes = wil::scope_exit([&]() {
|
||||
SetConsoleMode(conIn, inputMode);
|
||||
SetConsoleMode(conOut, outputMode);
|
||||
SetConsoleCP(codepage);
|
||||
SetConsoleOutputCP(outputCodepage);
|
||||
});
|
||||
|
||||
const auto size = GetConsoleScreenSize(conOut);
|
||||
|
||||
AzureConnection azureConn{ gsl::narrow_cast<uint32_t>(size.Y), gsl::narrow_cast<uint32_t>(size.X) };
|
||||
|
||||
const auto state = RunConnectionToCompletion(azureConn, conOut, conIn);
|
||||
|
||||
return state == ConnectionState::Closed ? 0 : 1;
|
||||
}
|
||||
4
src/cascadia/TerminalAzBridge/packages.config
Normal file
4
src/cascadia/TerminalAzBridge/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
|
||||
</packages>
|
||||
4
src/cascadia/TerminalAzBridge/pch.cpp
Normal file
4
src/cascadia/TerminalAzBridge/pch.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
36
src/cascadia/TerminalAzBridge/pch.h
Normal file
36
src/cascadia/TerminalAzBridge/pch.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- pch.h
|
||||
|
||||
Abstract:
|
||||
- Contains external headers to include in the precompile phase of console build process.
|
||||
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Ignore checked iterators warning from VC compiler.
|
||||
#define _SCL_SECURE_NO_WARNINGS
|
||||
|
||||
// Block minwindef.h min/max macros to prevent <algorithm> conflict
|
||||
#define NOMINMAX
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <unknwn.h>
|
||||
|
||||
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "../inc/LibraryIncludes.h"
|
||||
|
||||
#include <wil/cppwinrt.h>
|
||||
|
||||
#include <winrt/Windows.system.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
#include <wil/resource.h>
|
||||
#include <wil/win32_helpers.h>
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <LibraryResources.h>
|
||||
#include <unicode.hpp>
|
||||
|
||||
#include "AzureConnection.g.cpp"
|
||||
|
||||
@@ -32,6 +33,38 @@ static constexpr int CurrentCredentialVersion = 1;
|
||||
static constexpr auto PasswordVaultResourceName = L"Terminal";
|
||||
static constexpr auto HttpUserAgent = L"Terminal/0.0";
|
||||
|
||||
#define FAILOUT_IF_OPTIONAL_EMPTY(optional) \
|
||||
do \
|
||||
{ \
|
||||
if (!((optional).has_value())) \
|
||||
{ \
|
||||
return E_FAIL; \
|
||||
} \
|
||||
} while (0, 0)
|
||||
|
||||
static constexpr int USER_INPUT_COLOR = 93; // yellow - the color of something the user can type
|
||||
static constexpr int USER_INFO_COLOR = 97; // white - the color of clarifying information
|
||||
|
||||
static inline std::wstring _colorize(const unsigned int colorCode, const std::wstring_view text)
|
||||
{
|
||||
return wil::str_printf<std::wstring>(L"\x1b[%um%.*s\x1b[m", colorCode, gsl::narrow_cast<size_t>(text.size()), text.data());
|
||||
}
|
||||
|
||||
// Takes N resource names, loads the first one as a format string, and then
|
||||
// loads all the remaining ones into the %s arguments in the first one after
|
||||
// colorizing them in the USER_INPUT_COLOR.
|
||||
// This is intended to be used to drop UserEntry resources into an existing string.
|
||||
template<typename... Args>
|
||||
static inline std::wstring _formatResWithColoredUserInputOptions(const std::wstring_view resourceKey, Args&&... args)
|
||||
{
|
||||
return wil::str_printf<std::wstring>(GetLibraryResourceString(resourceKey).data(), (_colorize(USER_INPUT_COLOR, GetLibraryResourceString(args)).data())...);
|
||||
}
|
||||
|
||||
static inline std::wstring _formatTenantLine(int tenantNumber, const std::wstring_view tenantName, const std::wstring_view tenantID)
|
||||
{
|
||||
return wil::str_printf<std::wstring>(RS_(L"AzureIthTenant").data(), _colorize(USER_INPUT_COLOR, std::to_wstring(tenantNumber)).data(), _colorize(USER_INFO_COLOR, tenantName).data(), tenantID.data());
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
// This function exists because the clientID only gets added by the release pipelines
|
||||
@@ -45,8 +78,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
AzureConnection::AzureConnection(const uint32_t initialRows, const uint32_t initialCols) :
|
||||
_initialRows{ initialRows },
|
||||
_initialCols{ initialCols },
|
||||
_maxStored{},
|
||||
_maxSize{},
|
||||
_expiry{}
|
||||
{
|
||||
}
|
||||
@@ -55,7 +86,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// - helper that will write an unterminated string (generally, from a resource) to the output stream.
|
||||
// Arguments:
|
||||
// - str: the string to write.
|
||||
void AzureConnection::_WriteStringWithNewline(const winrt::hstring& str)
|
||||
void AzureConnection::_WriteStringWithNewline(const std::wstring_view str)
|
||||
{
|
||||
_TerminalOutputHandlers(str + L"\r\n");
|
||||
}
|
||||
@@ -79,6 +110,35 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_transitionToState(ConnectionState::Connecting);
|
||||
}
|
||||
|
||||
std::optional<std::wstring> AzureConnection::_ReadUserInput(InputMode mode)
|
||||
{
|
||||
std::unique_lock<std::mutex> inputLock{ _inputMutex };
|
||||
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
_currentInputMode = mode;
|
||||
|
||||
_TerminalOutputHandlers(L"\x1b[92m"); // Make prompted user input green
|
||||
|
||||
_inputEvent.wait(inputLock, [this, mode]() {
|
||||
return _currentInputMode != mode || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
|
||||
_TerminalOutputHandlers(L"\x1b[m");
|
||||
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::wstring readInput{};
|
||||
_userInput.swap(readInput);
|
||||
return readInput;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - handles the different possible inputs in the different states
|
||||
@@ -92,108 +152,46 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the input differently depending on which state we're in
|
||||
switch (_state)
|
||||
{
|
||||
// The user has stored connection settings, let them choose one of them, create a new one or remove all stored ones
|
||||
case AzureState::AccessStored:
|
||||
{
|
||||
const auto s = winrt::to_string(data);
|
||||
int storeNum = -1;
|
||||
try
|
||||
{
|
||||
storeNum = std::stoi(s);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
if (data == RS_(L"AzureUserEntry_RemoveStored"))
|
||||
{
|
||||
_removeOrNew = true;
|
||||
}
|
||||
else if (data == RS_(L"AzureUserEntry_NewLogin"))
|
||||
{
|
||||
_removeOrNew = false;
|
||||
}
|
||||
|
||||
if (_removeOrNew.has_value())
|
||||
{
|
||||
_canProceed.notify_one();
|
||||
}
|
||||
else
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureInvalidAccessInput"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (storeNum >= _maxStored)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNumOutOfBoundsError"));
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_storedNumber = storeNum;
|
||||
_canProceed.notify_one();
|
||||
return;
|
||||
}
|
||||
// The user has multiple tenants in their Azure account, let them choose one of them
|
||||
case AzureState::TenantChoice:
|
||||
{
|
||||
int tenantNum = -1;
|
||||
try
|
||||
{
|
||||
tenantNum = std::stoi(winrt::to_string(data));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNonNumberError"));
|
||||
return;
|
||||
}
|
||||
if (tenantNum >= _maxSize)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNumOutOfBoundsError"));
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_tenantNumber = tenantNum;
|
||||
_canProceed.notify_one();
|
||||
return;
|
||||
}
|
||||
// User has the option to save their connection settings for future logins
|
||||
case AzureState::StoreTokens:
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
if (data == RS_(L"AzureUserEntry_Yes"))
|
||||
{
|
||||
_store = true;
|
||||
}
|
||||
else if (data == RS_(L"AzureUserEntry_No"))
|
||||
{
|
||||
_store = false;
|
||||
}
|
||||
|
||||
if (_store.has_value())
|
||||
{
|
||||
_canProceed.notify_one();
|
||||
}
|
||||
else
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureInvalidStoreInput"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We are connected, send user's input over the websocket
|
||||
case AzureState::TermConnected:
|
||||
if (_state == AzureState::TermConnected)
|
||||
{
|
||||
// If we're connected, we don't need to do any fun input shenanigans.
|
||||
websocket_outgoing_message msg;
|
||||
const auto str = winrt::to_string(data);
|
||||
msg.set_utf8_message(str);
|
||||
|
||||
_cloudShellSocket.send(msg).get();
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock{ _inputMutex };
|
||||
if (data.size() > 0 && (gsl::at(data, 0) == UNICODE_BACKSPACE || gsl::at(data, 0) == UNICODE_DEL)) // BS or DEL
|
||||
{
|
||||
if (_userInput.size() > 0)
|
||||
{
|
||||
_userInput.pop_back();
|
||||
_TerminalOutputHandlers(L"\x08 \x08"); // overstrike the character with a space
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_TerminalOutputHandlers(data); // echo back
|
||||
|
||||
switch (_currentInputMode)
|
||||
{
|
||||
case InputMode::Line:
|
||||
if (data.size() > 0 && gsl::at(data, 0) == UNICODE_CARRIAGERETURN)
|
||||
{
|
||||
_TerminalOutputHandlers(L"\r\n"); // we probably got a \r, so we need to advance to the next line.
|
||||
_currentInputMode = InputMode::None; // toggling the mode indicates completion
|
||||
_inputEvent.notify_one();
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
default:
|
||||
std::copy(data.cbegin(), data.cend(), std::back_inserter(_userInput));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method description:
|
||||
@@ -231,7 +229,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
if (_transitionToState(ConnectionState::Closing))
|
||||
{
|
||||
_canProceed.notify_all();
|
||||
_inputEvent.notify_all();
|
||||
|
||||
if (_state == AzureState::TermConnected)
|
||||
{
|
||||
// Close the websocket connection
|
||||
@@ -404,7 +403,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_FALSE;
|
||||
}
|
||||
_maxStored = 0;
|
||||
|
||||
int numTenants{ 0 };
|
||||
for (const auto& entry : credList)
|
||||
{
|
||||
auto nameJson = json::value::parse(entry.UserName().c_str());
|
||||
@@ -422,12 +422,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
continue;
|
||||
}
|
||||
|
||||
winrt::hstring tenantLine{ wil::str_printf<std::wstring>(RS_(L"AzureIthTenant").c_str(), _maxStored, nameJson.at(L"displayName").as_string().c_str(), nameJson.at(L"tenantID").as_string().c_str()) };
|
||||
_WriteStringWithNewline(tenantLine);
|
||||
_maxStored++;
|
||||
_WriteStringWithNewline(_formatTenantLine(numTenants, nameJson.at(L"displayName").as_string(), nameJson.at(L"tenantID").as_string()));
|
||||
numTenants++;
|
||||
}
|
||||
|
||||
if (!_maxStored)
|
||||
if (!numTenants)
|
||||
{
|
||||
if (oldVersionEncountered)
|
||||
{
|
||||
@@ -439,33 +438,53 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
}
|
||||
|
||||
_WriteStringWithNewline(RS_(L"AzureEnterTenant"));
|
||||
_WriteStringWithNewline(RS_(L"AzureNewLogin"));
|
||||
_WriteStringWithNewline(RS_(L"AzureRemoveStored"));
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureNewLogin"), USES_RESOURCE(L"AzureUserEntry_NewLogin")));
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureRemoveStored"), USES_RESOURCE(L"AzureUserEntry_RemoveStored")));
|
||||
|
||||
std::unique_lock<std::mutex> storedLock{ _commonMutex };
|
||||
_canProceed.wait(storedLock, [=]() {
|
||||
return (_storedNumber >= 0 && _storedNumber < _maxStored) || _removeOrNew.has_value() || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
int selectedTenant{ -1 };
|
||||
do
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
else if (_removeOrNew.has_value() && _removeOrNew.value())
|
||||
{
|
||||
// User wants to remove the stored settings
|
||||
_RemoveCredentials();
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
else if (_removeOrNew.has_value() && !_removeOrNew.value())
|
||||
{
|
||||
// User wants to login with a different account
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
auto maybeTenantSelection = _ReadUserInput(InputMode::Line);
|
||||
FAILOUT_IF_OPTIONAL_EMPTY(maybeTenantSelection);
|
||||
|
||||
const auto& tenantSelection = maybeTenantSelection.value();
|
||||
if (tenantSelection == RS_(L"AzureUserEntry_RemoveStored"))
|
||||
{
|
||||
// User wants to remove the stored settings
|
||||
_RemoveCredentials();
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
else if (tenantSelection == RS_(L"AzureUserEntry_NewLogin"))
|
||||
{
|
||||
// User wants to login with a different account
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
selectedTenant = std::stoi(tenantSelection);
|
||||
if (selectedTenant < 0 || selectedTenant >= numTenants)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNumOutOfBoundsError"));
|
||||
continue; // go 'round again
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// suppress exceptions in conversion
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, we didn't break out of the loop early and need to go 'round again
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureInvalidAccessInput"), USES_RESOURCE(L"AzureUserEntry_NewLogin"), USES_RESOURCE(L"AzureUserEntry_RemoveStored")));
|
||||
} while (true);
|
||||
|
||||
// User wants to login with one of the saved connection settings
|
||||
auto desiredCredential = credList.GetAt(_storedNumber);
|
||||
auto desiredCredential = credList.GetAt(selectedTenant);
|
||||
desiredCredential.RetrievePassword();
|
||||
auto nameJson = json::value::parse(desiredCredential.UserName().c_str());
|
||||
auto passWordJson = json::value::parse(desiredCredential.Password().c_str());
|
||||
@@ -567,27 +586,43 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
try
|
||||
{
|
||||
const auto tenantListAsArray = _tenantList.as_array();
|
||||
_maxSize = gsl::narrow<int>(tenantListAsArray.size());
|
||||
for (int i = 0; i < _maxSize; i++)
|
||||
auto numTenants = gsl::narrow<int>(tenantListAsArray.size());
|
||||
for (int i = 0; i < numTenants; i++)
|
||||
{
|
||||
const auto& tenant = tenantListAsArray.at(i);
|
||||
const auto [tenantId, tenantDisplayName] = _crackTenant(tenant);
|
||||
winrt::hstring tenantLine{ wil::str_printf<std::wstring>(RS_(L"AzureIthTenant").c_str(), i, tenantDisplayName.c_str(), tenantId.c_str()) };
|
||||
_WriteStringWithNewline(tenantLine);
|
||||
_WriteStringWithNewline(_formatTenantLine(i, tenantDisplayName, tenantId));
|
||||
}
|
||||
_WriteStringWithNewline(RS_(L"AzureEnterTenant"));
|
||||
// Use a lock to wait for the user to input a valid number
|
||||
std::unique_lock<std::mutex> tenantNumberLock{ _commonMutex };
|
||||
_canProceed.wait(tenantNumberLock, [=]() {
|
||||
return (_tenantNumber >= 0 && _tenantNumber < _maxSize) || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
const auto& chosenTenant = tenantListAsArray.at(_tenantNumber);
|
||||
int selectedTenant{ -1 };
|
||||
do
|
||||
{
|
||||
auto maybeTenantSelection = _ReadUserInput(InputMode::Line);
|
||||
FAILOUT_IF_OPTIONAL_EMPTY(maybeTenantSelection);
|
||||
|
||||
const auto& tenantSelection = maybeTenantSelection.value();
|
||||
try
|
||||
{
|
||||
selectedTenant = std::stoi(tenantSelection);
|
||||
|
||||
if (selectedTenant < 0 || selectedTenant >= numTenants)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNumOutOfBoundsError"));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// suppress exceptions in conversion
|
||||
}
|
||||
|
||||
// if we got here, we didn't break out of the loop early and need to go 'round again
|
||||
_WriteStringWithNewline(RS_(L"AzureNonNumberError"));
|
||||
} while (true);
|
||||
|
||||
const auto& chosenTenant = tenantListAsArray.at(selectedTenant);
|
||||
std::tie(_tenantID, _displayName) = _crackTenant(chosenTenant);
|
||||
|
||||
// We have to refresh now that we have the tenantID
|
||||
@@ -609,24 +644,28 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// - S_OK otherwise
|
||||
HRESULT AzureConnection::_StoreHelper()
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureStorePrompt"));
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureStorePrompt"), USES_RESOURCE(L"AzureUserEntry_Yes"), USES_RESOURCE(L"AzureUserEntry_No")));
|
||||
// Wait for user input
|
||||
std::unique_lock<std::mutex> storeLock{ _commonMutex };
|
||||
_canProceed.wait(storeLock, [=]() {
|
||||
return _store.has_value() || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
do
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
auto maybeStoreCredentials = _ReadUserInput(InputMode::Line);
|
||||
FAILOUT_IF_OPTIONAL_EMPTY(maybeStoreCredentials);
|
||||
|
||||
if (_store.value())
|
||||
{
|
||||
// User has opted to store the connection settings
|
||||
_StoreCredential();
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensStored"));
|
||||
}
|
||||
const auto& storeCredentials = maybeStoreCredentials.value();
|
||||
if (storeCredentials == RS_(L"AzureUserEntry_Yes"))
|
||||
{
|
||||
_StoreCredential();
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensStored"));
|
||||
break;
|
||||
}
|
||||
else if (storeCredentials == RS_(L"AzureUserEntry_No"))
|
||||
{
|
||||
break; // we're done, but the user wants nothing.
|
||||
}
|
||||
|
||||
// if we got here, we didn't break out of the loop early and need to go 'round again
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureInvalidStoreInput"), USES_RESOURCE(L"AzureUserEntry_Yes"), USES_RESOURCE(L"AzureUserEntry_No")));
|
||||
} while (true);
|
||||
|
||||
_state = AzureState::TermConnecting;
|
||||
return S_OK;
|
||||
@@ -654,11 +693,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_WriteStringWithNewline(RS_(L"AzureSuccess"));
|
||||
|
||||
// Request for a terminal for said cloud shell
|
||||
// We only support bash for now, so don't bother with the user's preferred shell
|
||||
// fyi: we can't call powershell yet because it sends VT sequences we don't support yet
|
||||
// TODO: GitHub #1883
|
||||
//const auto shellType = settingsResponse.at(L"properties").at(L"preferredShellType").as_string();
|
||||
const auto shellType = L"bash";
|
||||
const auto shellType = settingsResponse.at(L"properties").at(L"preferredShellType").as_string();
|
||||
_WriteStringWithNewline(RS_(L"AzureRequestingTerminal"));
|
||||
const auto socketUri = _GetTerminal(shellType);
|
||||
_TerminalOutputHandlers(L"\r\n");
|
||||
@@ -668,6 +703,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
connReqTask.wait();
|
||||
|
||||
_state = AzureState::TermConnected;
|
||||
|
||||
std::wstring queuedUserInput{};
|
||||
std::swap(_userInput, queuedUserInput);
|
||||
if (queuedUserInput.size() > 0)
|
||||
{
|
||||
WriteInput(static_cast<winrt::hstring>(queuedUserInput)); // send the user's queued up input back through
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -833,9 +876,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
http_request shellRequest(L"PUT");
|
||||
shellRequest.set_request_uri(L"providers/Microsoft.Portal/consoles/default?api-version=2018-10-01");
|
||||
_HeaderHelper(shellRequest);
|
||||
const auto innerBody = json::value::parse(U("{ \"osType\" : \"linux\" }"));
|
||||
json::value body;
|
||||
body[U("properties")] = innerBody;
|
||||
// { "properties": { "osType": "linux" } }
|
||||
auto body = json::value::object({ { U("properties"), json::value::object({ { U("osType"), json::value::string(U("linux")) } }) } });
|
||||
shellRequest.set_body(body);
|
||||
|
||||
// Send the request and get the response as a json value
|
||||
@@ -914,17 +956,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_WriteStringWithNewline(RS_(L"AzureNoTokens"));
|
||||
return;
|
||||
}
|
||||
while (credList.Size() > 0)
|
||||
|
||||
for (const auto& cred : credList)
|
||||
{
|
||||
try
|
||||
{
|
||||
vault.Remove(credList.GetAt(0));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensRemoved"));
|
||||
return;
|
||||
vault.Remove(cred);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensRemoved"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
private:
|
||||
uint32_t _initialRows{};
|
||||
uint32_t _initialCols{};
|
||||
int _storedNumber{ -1 };
|
||||
int _maxStored;
|
||||
int _tenantNumber{ -1 };
|
||||
int _maxSize;
|
||||
std::condition_variable _canProceed;
|
||||
std::mutex _commonMutex;
|
||||
|
||||
enum class AzureState
|
||||
{
|
||||
@@ -51,9 +45,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
AzureState _state{ AzureState::AccessStored };
|
||||
|
||||
std::optional<bool> _store;
|
||||
std::optional<bool> _removeOrNew;
|
||||
|
||||
wil::unique_handle _hOutputThread;
|
||||
|
||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
||||
@@ -67,17 +58,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
const utility::string_t _loginUri{ U("https://login.microsoftonline.com/") };
|
||||
const utility::string_t _resourceUri{ U("https://management.azure.com/") };
|
||||
const utility::string_t _wantedResource{ U("https://management.core.windows.net/") };
|
||||
int _expireLimit{ 2700 };
|
||||
const int _expireLimit{ 2700 };
|
||||
web::json::value _tenantList;
|
||||
utility::string_t _displayName;
|
||||
utility::string_t _tenantID;
|
||||
utility::string_t _accessToken;
|
||||
utility::string_t _refreshToken;
|
||||
int _expiry;
|
||||
int _expiry{ 0 };
|
||||
utility::string_t _cloudShellUri;
|
||||
utility::string_t _terminalID;
|
||||
|
||||
void _WriteStringWithNewline(const winrt::hstring& str);
|
||||
void _WriteStringWithNewline(const std::wstring_view str);
|
||||
web::json::value _RequestHelper(web::http::client::http_client theClient, web::http::http_request theRequest);
|
||||
web::json::value _GetDeviceCode();
|
||||
web::json::value _WaitForUser(utility::string_t deviceCode, int pollInterval, int expiresIn);
|
||||
@@ -90,6 +81,18 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
void _StoreCredential();
|
||||
void _RemoveCredentials();
|
||||
|
||||
enum class InputMode
|
||||
{
|
||||
None = 0,
|
||||
Line
|
||||
};
|
||||
InputMode _currentInputMode{ InputMode::None };
|
||||
std::wstring _userInput;
|
||||
std::condition_variable _inputEvent;
|
||||
std::mutex _inputMutex;
|
||||
|
||||
std::optional<std::wstring> _ReadUserInput(InputMode mode);
|
||||
|
||||
web::websockets::client::websocket_client _cloudShellSocket;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
try
|
||||
{
|
||||
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
|
||||
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, 0, &_inPipe, &_outPipe, &_hPC));
|
||||
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK, &_inPipe, &_outPipe, &_hPC));
|
||||
THROW_IF_FAILED(_LaunchAttachedClient());
|
||||
|
||||
_startTime = std::chrono::high_resolution_clock::now();
|
||||
@@ -359,6 +359,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
DWORD ConptyConnection::_OutputThread()
|
||||
{
|
||||
// Keep us alive until the output thread terminates; the destructor
|
||||
// won't wait for us, and the known exit points _do_.
|
||||
auto strongThis{ get_strong() };
|
||||
|
||||
// process the data of the output pipe in a loop
|
||||
while (true)
|
||||
{
|
||||
|
||||
@@ -123,14 +123,14 @@
|
||||
<data name="AzureEnterTenant" xml:space="preserve">
|
||||
<value>Please enter the desired tenant number.</value>
|
||||
</data>
|
||||
<data name="AzureNewLogin" xml:space="preserve">
|
||||
<value>Enter "n" to login with a different account.</value>
|
||||
<data name="AzureNewLogin" xml:space="default">
|
||||
<value>Enter %s to login with a different account</value>
|
||||
</data>
|
||||
<data name="AzureRemoveStored" xml:space="preserve">
|
||||
<value>Enter "r" to remove the above saved connection settings.</value>
|
||||
<data name="AzureRemoveStored" xml:space="default">
|
||||
<value>Enter %s to remove the above saved connection settings.</value>
|
||||
</data>
|
||||
<data name="AzureInvalidAccessInput" xml:space="preserve">
|
||||
<value>Please enter a valid number to access the stored connection settings, n to make a new one, or r to remove the stored ones.</value>
|
||||
<data name="AzureInvalidAccessInput" xml:space="default">
|
||||
<value>Please enter a valid number to access the stored connection settings, %s to make a new one, or %s to remove the stored ones.</value>
|
||||
</data>
|
||||
<data name="AzureNonNumberError" xml:space="preserve">
|
||||
<value>Please enter a number.</value>
|
||||
@@ -144,11 +144,11 @@
|
||||
<data name="AzureNoCloudAccount" xml:space="preserve">
|
||||
<value>You have not set up your cloud shell account yet. Please go to https://shell.azure.com to set it up.</value>
|
||||
</data>
|
||||
<data name="AzureStorePrompt" xml:space="preserve">
|
||||
<value>Do you want to save these connection settings for future logins? [y/n]</value>
|
||||
<data name="AzureStorePrompt" xml:space="default">
|
||||
<value>Do you want to save these connection settings for future logins? [%s/%s]</value>
|
||||
</data>
|
||||
<data name="AzureInvalidStoreInput" xml:space="preserve">
|
||||
<value>Please enter y or n</value>
|
||||
<data name="AzureInvalidStoreInput" xml:space="default">
|
||||
<value>Please enter %s or %s</value>
|
||||
</data>
|
||||
<data name="AzureRequestingCloud" xml:space="preserve">
|
||||
<value>Requesting a cloud shell instance...</value>
|
||||
@@ -183,8 +183,8 @@
|
||||
<data name="AzureUnknownTenantName" xml:space="preserve">
|
||||
<value><unknown tenant name></value>
|
||||
</data>
|
||||
<data name="AzureIthTenant" xml:space="preserve">
|
||||
<value>Tenant %d: %s (%s)</value>
|
||||
<data name="AzureIthTenant" xml:space="default">
|
||||
<value>Tenant %s: %s (%s)</value>
|
||||
</data>
|
||||
<data name="AzureSuccessfullyAuthenticated" xml:space="preserve">
|
||||
<value>Authenticated.</value>
|
||||
@@ -217,4 +217,4 @@ If this resource spans multiple lines, it will not be displayed properly. Yeah.<
|
||||
<data name="TelnetInternetOrServerIssue" xml:space="preserve">
|
||||
<value>Could not connect to telnet server.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
@@ -59,7 +59,7 @@
|
||||
<Midl Include="TelnetConnection.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources/en-US/Resources.resw" />
|
||||
<PRIResource Include="Resources/Resources.language-en.resw" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
@@ -95,4 +95,4 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="..\..\..\packages\vcpkg-telnetpp.1.0.1\build\native\vcpkg-telnetpp.targets" Condition="Exists('..\..\..\packages\vcpkg-telnetpp.1.0.1\build\native\vcpkg-telnetpp.targets')" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -117,27 +117,57 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CaseSensitivityButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<data name="SearchBox_CaseSensitivity.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Match Case</value>
|
||||
<comment>The tooltip text for CaseSensitivityButton</comment>
|
||||
<comment>The tooltip text for the case sensitivity button on the search box control.</comment>
|
||||
</data>
|
||||
<data name="CloseButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<data name="SearchBox_Close.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
<comment>The tooltip text for CloseButton</comment>
|
||||
<comment>The tooltip text for the close button on the search box control.</comment>
|
||||
</data>
|
||||
<data name="GoBackwardButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<data name="SearchBox_SearchBackwards.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Find Up</value>
|
||||
<comment>The tooltip text for GoBackward Button</comment>
|
||||
<comment>The tooltip text for the search backward button.</comment>
|
||||
</data>
|
||||
<data name="GoForwardButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<data name="SearchBox_SearchForwards.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Find Down</value>
|
||||
<comment>The tooltip text for GoForward Button</comment>
|
||||
<comment>The tooltip text for the search forward button.</comment>
|
||||
</data>
|
||||
<data name="TextBoxLocalizedText.PlaceholderText" xml:space="preserve">
|
||||
<data name="SearchBox_TextBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Find...</value>
|
||||
<comment>The placeholder text in the search dialog TextBox</comment>
|
||||
<comment>The placeholder text in the search box control.</comment>
|
||||
</data>
|
||||
<data name="DragFileCaption" xml:space="preserve">
|
||||
<value>Copy path to file</value>
|
||||
<comment>The displayed caption for dragging a file onto a terminal.</comment>
|
||||
</data>
|
||||
<data name="SearchBox_CaseSensitivity.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Case Sensitivity</value>
|
||||
<comment>The name of the case sensitivity button on the search box control for accessibility.</comment>
|
||||
</data>
|
||||
<data name="SearchBox_SearchForwards.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Search Forward</value>
|
||||
<comment>The name of the search forward button for accessibility.</comment>
|
||||
</data>
|
||||
<data name="SearchBox_SearchBackwards.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Search Backward</value>
|
||||
<comment>The name of the search backward button for accessibility.</comment>
|
||||
</data>
|
||||
<data name="SearchBox_TextBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Search Text</value>
|
||||
<comment>The name of the text box on the search box control for accessibility.</comment>
|
||||
</data>
|
||||
<data name="TerminalControl_ControlType" xml:space="preserve">
|
||||
<value>terminal</value>
|
||||
<comment>The type of control that the terminal ahderes to. Used to identify how a user can interact with this kind of control.</comment>
|
||||
</data>
|
||||
<data name="SearchBox_Close.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Close Search Box</value>
|
||||
</data>
|
||||
<data name="TermControl_RendererFailedTextBlock.Text" xml:space="preserve">
|
||||
<value>This terminal has encountered an issue with the graphics driver and it could not recover in time. It has been suspended.</value>
|
||||
</data>
|
||||
<data name="TermControl_RendererRetryButton.Content" xml:space="preserve">
|
||||
<value>Resume</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -153,29 +153,44 @@
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Style="{ThemeResource SearchBoxBackground}" Padding="5" CornerRadius="0,0,2,2">
|
||||
<TextBox x:Name="TextBox" x:Uid="TextBoxLocalizedText" AutomationProperties.Name="Search Text" CornerRadius="2" Width="160"
|
||||
PlaceholderForeground="{ThemeResource TextBoxPlaceholderTextThemeBrush}" FontSize="15" KeyDown = "TextBoxKeyDown"
|
||||
Margin="5" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBox x:Name="TextBox"
|
||||
x:Uid="SearchBox_TextBox"
|
||||
CornerRadius="2"
|
||||
Width="160"
|
||||
PlaceholderForeground="{ThemeResource TextBoxPlaceholderTextThemeBrush}"
|
||||
FontSize="15"
|
||||
KeyDown="TextBoxKeyDown"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
</TextBox>
|
||||
|
||||
<ToggleButton x:Name="GoBackwardButton" x:Uid="GoBackwardButtonLocalizedText" AutomationProperties.Name="Set Search Backward"
|
||||
HorizontalAlignment="Right" Style="{StaticResource ToggleButtonStyle}"
|
||||
Click="GoBackwardClicked" IsChecked="True">
|
||||
<ToggleButton x:Name="GoBackwardButton"
|
||||
x:Uid="SearchBox_SearchBackwards"
|
||||
HorizontalAlignment="Right"
|
||||
Style="{StaticResource ToggleButtonStyle}"
|
||||
Click="GoBackwardClicked"
|
||||
IsChecked="True">
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" Style="{ThemeResource FontIconStyle}"/>
|
||||
</ToggleButton>
|
||||
<ToggleButton x:Name="GoForwardButton" x:Uid="GoForwardButtonLocalizedText"
|
||||
AutomationProperties.Name="Set Search Forward" Style="{StaticResource ToggleButtonStyle}"
|
||||
<ToggleButton x:Name="GoForwardButton"
|
||||
x:Uid="SearchBox_SearchForwards"
|
||||
Style="{StaticResource ToggleButtonStyle}"
|
||||
Click="GoForwardClicked">
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" Style="{ThemeResource FontIconStyle}"/>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton x:Name="CaseSensitivityButton" x:Uid="CaseSensitivityButtonLocalizedText"
|
||||
AutomationProperties.Name="CaseSensitivity" Style="{StaticResource ToggleButtonStyle}">
|
||||
<ToggleButton x:Name="CaseSensitivityButton"
|
||||
x:Uid="SearchBox_CaseSensitivity"
|
||||
Style="{StaticResource ToggleButtonStyle}">
|
||||
<PathIcon Data="M8.87305 10H7.60156L6.5625 7.25195H2.40625L1.42871 10H0.150391L3.91016 0.197266H5.09961L8.87305 10ZM6.18652 6.21973L4.64844 2.04297C4.59831 1.90625 4.54818 1.6875 4.49805 1.38672H4.4707C4.42513 1.66471 4.37272 1.88346 4.31348 2.04297L2.78906 6.21973H6.18652ZM15.1826 10H14.0615V8.90625H14.0342C13.5465 9.74479 12.8288 10.1641 11.8809 10.1641C11.1836 10.1641 10.6367 9.97949 10.2402 9.61035C9.84831 9.24121 9.65234 8.7513 9.65234 8.14062C9.65234 6.83268 10.4225 6.07161 11.9629 5.85742L14.0615 5.56348C14.0615 4.37402 13.5807 3.7793 12.6191 3.7793C11.776 3.7793 11.015 4.06641 10.3359 4.64062V3.49219C11.0241 3.05469 11.8171 2.83594 12.7148 2.83594C14.36 2.83594 15.1826 3.70638 15.1826 5.44727V10ZM14.0615 6.45898L12.373 6.69141C11.8535 6.76432 11.4616 6.89421 11.1973 7.08105C10.9329 7.26335 10.8008 7.58919 10.8008 8.05859C10.8008 8.40039 10.9215 8.68066 11.1631 8.89941C11.4092 9.11361 11.735 9.2207 12.1406 9.2207C12.6966 9.2207 13.1546 9.02702 13.5146 8.63965C13.8792 8.24772 14.0615 7.75326 14.0615 7.15625V6.45898Z"/>
|
||||
</ToggleButton>
|
||||
|
||||
<Button x:Name="CloseButton" x:Uid="CloseButtonLocalizedText" AutomationProperties.Name="Close"
|
||||
Padding="0" Click="CloseClick" Style="{ ThemeResource ButtonStyle}">
|
||||
<Button x:Name="CloseButton"
|
||||
x:Uid="SearchBox_Close"
|
||||
Padding="0"
|
||||
Click="CloseClick"
|
||||
Style="{ThemeResource ButtonStyle}">
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" FontSize="12"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
@@ -17,43 +17,17 @@ using namespace winrt::Windows::UI::Xaml;
|
||||
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
TSFInputControl::TSFInputControl() :
|
||||
_editContext{ nullptr }
|
||||
_editContext{ nullptr },
|
||||
_inComposition{ false },
|
||||
_activeTextStart{ 0 }
|
||||
{
|
||||
_Create();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates XAML controls for displaying user input and hooks up CoreTextEditContext handlers
|
||||
// for handling text input from the Text Services Framework.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TSFInputControl::_Create()
|
||||
{
|
||||
// TextBlock for user input form TSF
|
||||
_textBlock = Controls::TextBlock();
|
||||
_textBlock.Visibility(Visibility::Collapsed);
|
||||
_textBlock.IsTextSelectionEnabled(false);
|
||||
_textBlock.TextDecorations(TextDecorations::Underline);
|
||||
|
||||
// Canvas for controlling exact position of the TextBlock
|
||||
_canvas = Windows::UI::Xaml::Controls::Canvas();
|
||||
_canvas.Visibility(Visibility::Collapsed);
|
||||
|
||||
// add the Textblock to the Canvas
|
||||
_canvas.Children().Append(_textBlock);
|
||||
|
||||
// set the content of this control to be the Canvas
|
||||
this->Content(_canvas);
|
||||
InitializeComponent();
|
||||
|
||||
// Create a CoreTextEditingContext for since we are acting like a custom edit control
|
||||
auto manager = Core::CoreTextServicesManager::GetForCurrentView();
|
||||
_editContext = manager.CreateEditContext();
|
||||
|
||||
// sets the Input Pane display policy to Manual for now so that it can manually show the
|
||||
// software keyboard when the control gains focus and dismiss it when the control loses focus.
|
||||
// TODO GitHub #3639: Should Input Pane display policy be Automatic
|
||||
// InputPane is manually shown inside of TermControl.
|
||||
_editContext.InputPaneDisplayPolicy(Core::CoreTextInputPaneDisplayPolicy::Manual);
|
||||
|
||||
// set the input scope to Text because this control is for any text.
|
||||
@@ -117,6 +91,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Clears the input buffer and tells the text server to clear their buffer as well.
|
||||
// Also clears the TextBlock and sets the active text starting point to 0.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TSFInputControl::ClearBuffer()
|
||||
{
|
||||
if (!_inputBuffer.empty())
|
||||
{
|
||||
TextBlock().Text(L"");
|
||||
const auto bufLen = ::base::ClampedNumeric<int32_t>(_inputBuffer.length());
|
||||
_inputBuffer.clear();
|
||||
_editContext.NotifyFocusLeave();
|
||||
_editContext.NotifyTextChanged({ 0, bufLen }, 0, { 0, 0 });
|
||||
_editContext.NotifyFocusEnter();
|
||||
_activeTextStart = 0;
|
||||
_inComposition = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handler for LayoutRequested event by CoreEditContext responsible
|
||||
// for returning the current position the IME should be placed
|
||||
@@ -138,32 +134,31 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Get the cursor position in text buffer position
|
||||
auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
|
||||
_CurrentCursorPositionHandlers(*this, *cursorArgs);
|
||||
const COORD cursorPos = { gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().X), gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().Y) };
|
||||
const COORD cursorPos = { ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().X), ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().Y) };
|
||||
|
||||
// Get Font Info as we use this is the pixel size for characters in the display
|
||||
auto fontArgs = winrt::make_self<FontInfoEventArgs>();
|
||||
_CurrentFontInfoHandlers(*this, *fontArgs);
|
||||
|
||||
const float fontWidth = fontArgs->FontSize().Width;
|
||||
const float fontHeight = fontArgs->FontSize().Height;
|
||||
const auto fontWidth = fontArgs->FontSize().Width;
|
||||
const auto fontHeight = fontArgs->FontSize().Height;
|
||||
|
||||
// Convert text buffer cursor position to client coordinate position within the window
|
||||
COORD clientCursorPos;
|
||||
COORD screenCursorPos;
|
||||
THROW_IF_FAILED(ShortMult(cursorPos.X, gsl::narrow<SHORT>(fontWidth), &clientCursorPos.X));
|
||||
THROW_IF_FAILED(ShortMult(cursorPos.Y, gsl::narrow<SHORT>(fontHeight), &clientCursorPos.Y));
|
||||
clientCursorPos.X = ::base::ClampMul(cursorPos.X, ::base::ClampedNumeric<short>(fontWidth));
|
||||
clientCursorPos.Y = ::base::ClampMul(cursorPos.Y, ::base::ClampedNumeric<short>(fontHeight));
|
||||
|
||||
// Convert from client coordinate to screen coordinate by adding window position
|
||||
THROW_IF_FAILED(ShortAdd(clientCursorPos.X, gsl::narrow_cast<SHORT>(windowBounds.X), &screenCursorPos.X));
|
||||
THROW_IF_FAILED(ShortAdd(clientCursorPos.Y, gsl::narrow_cast<SHORT>(windowBounds.Y), &screenCursorPos.Y));
|
||||
COORD screenCursorPos;
|
||||
screenCursorPos.X = ::base::ClampAdd(clientCursorPos.X, ::base::ClampedNumeric<short>(windowBounds.X));
|
||||
screenCursorPos.Y = ::base::ClampAdd(clientCursorPos.Y, ::base::ClampedNumeric<short>(windowBounds.Y));
|
||||
|
||||
// get any offset (margin + tabs, etc..) of the control within the window
|
||||
const auto offsetPoint = this->TransformToVisual(nullptr).TransformPoint(winrt::Windows::Foundation::Point(0, 0));
|
||||
|
||||
// add the margin offsets if any
|
||||
const auto currentMargin = this->Margin();
|
||||
THROW_IF_FAILED(ShortAdd(screenCursorPos.X, gsl::narrow_cast<SHORT>(offsetPoint.X), &screenCursorPos.X));
|
||||
THROW_IF_FAILED(ShortAdd(screenCursorPos.Y, gsl::narrow_cast<SHORT>(offsetPoint.Y), &screenCursorPos.Y));
|
||||
screenCursorPos.X = ::base::ClampAdd(screenCursorPos.X, ::base::ClampedNumeric<short>(offsetPoint.X));
|
||||
screenCursorPos.Y = ::base::ClampAdd(screenCursorPos.Y, ::base::ClampedNumeric<short>(offsetPoint.Y));
|
||||
|
||||
// Get scale factor for view
|
||||
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
|
||||
@@ -177,18 +172,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
request.LayoutBounds().ControlBounds(ScaleRect(controlRect, scaleFactor));
|
||||
|
||||
// position textblock to cursor position
|
||||
_canvas.SetLeft(_textBlock, clientCursorPos.X);
|
||||
_canvas.SetTop(_textBlock, static_cast<double>(clientCursorPos.Y));
|
||||
|
||||
// width is cursor to end of canvas
|
||||
_textBlock.Width(200); // TODO GitHub #3640: Determine proper Width
|
||||
_textBlock.Height(fontHeight);
|
||||
Canvas().SetLeft(TextBlock(), clientCursorPos.X);
|
||||
Canvas().SetTop(TextBlock(), ::base::ClampedNumeric<double>(clientCursorPos.Y));
|
||||
|
||||
TextBlock().Height(fontHeight);
|
||||
// calculate FontSize in pixels from DIPs
|
||||
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
|
||||
_textBlock.FontSize(fontSizePx);
|
||||
TextBlock().FontSize(fontSizePx);
|
||||
|
||||
_textBlock.FontFamily(Media::FontFamily(fontArgs->FontFace()));
|
||||
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -201,8 +193,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TSFInputControl::_compositionStartedHandler(CoreTextEditContext sender, CoreTextCompositionStartedEventArgs const& /*args*/)
|
||||
{
|
||||
_canvas.Visibility(Visibility::Visible);
|
||||
_textBlock.Visibility(Visibility::Visible);
|
||||
_inComposition = true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -215,30 +206,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TSFInputControl::_compositionCompletedHandler(CoreTextEditContext sender, CoreTextCompositionCompletedEventArgs const& /*args*/)
|
||||
{
|
||||
_inComposition = false;
|
||||
|
||||
// only need to do work if the current buffer has text
|
||||
if (!_inputBuffer.empty())
|
||||
{
|
||||
// call event handler with data handled by parent
|
||||
_compositionCompletedHandlers(_inputBuffer);
|
||||
|
||||
// clear the buffer for next round
|
||||
const auto bufferLength = gsl::narrow_cast<int32_t>(_inputBuffer.length());
|
||||
_inputBuffer.clear();
|
||||
_textBlock.Text(L"");
|
||||
|
||||
// indicate text is now 0
|
||||
_editContext.NotifyTextChanged({ 0, bufferLength }, 0, { 0, 0 });
|
||||
|
||||
// hide the controls until composition starts again
|
||||
_canvas.Visibility(Visibility::Collapsed);
|
||||
_textBlock.Visibility(Visibility::Collapsed);
|
||||
_SendAndClearText();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handler for FocusRemoved event by CoreEditContext responsible
|
||||
// for removing focus for the TSFInputControl control accordingly
|
||||
// when focus was forcibly removed from text input control. (TODO GitHub #3644)
|
||||
// when focus was forcibly removed from text input control.
|
||||
// NOTE: Documentation says application should handle this event
|
||||
// Arguments:
|
||||
// - sender: CoreTextEditContext sending the request. Not used in method.
|
||||
@@ -265,7 +245,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
try
|
||||
{
|
||||
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition));
|
||||
const auto textEnd = ::base::ClampMin<size_t>(range.EndCaretPosition, _inputBuffer.length());
|
||||
const auto length = ::base::ClampSub<size_t>(textEnd, range.StartCaretPosition);
|
||||
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, length);
|
||||
|
||||
args.Request().Text(textRequested);
|
||||
}
|
||||
@@ -310,17 +292,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TSFInputControl::_textUpdatingHandler(CoreTextEditContext sender, CoreTextTextUpdatingEventArgs const& args)
|
||||
{
|
||||
const auto text = args.Text();
|
||||
const auto incomingText = args.Text();
|
||||
const auto range = args.Range();
|
||||
|
||||
try
|
||||
{
|
||||
_inputBuffer = _inputBuffer.replace(
|
||||
range.StartCaretPosition,
|
||||
static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition),
|
||||
text);
|
||||
::base::ClampSub<size_t>(range.EndCaretPosition, range.StartCaretPosition),
|
||||
incomingText);
|
||||
|
||||
_textBlock.Text(_inputBuffer);
|
||||
// Emojis/Kaomojis/Symbols chosen through the IME without starting composition
|
||||
// will be sent straight through to the terminal.
|
||||
if (!_inComposition)
|
||||
{
|
||||
_SendAndClearText();
|
||||
}
|
||||
else
|
||||
{
|
||||
Canvas().Visibility(Visibility::Visible);
|
||||
const auto text = _inputBuffer.substr(_activeTextStart);
|
||||
TextBlock().Text(text);
|
||||
}
|
||||
|
||||
// Notify the TSF that the update succeeded
|
||||
args.Result(CoreTextTextUpdatingResult::Succeeded);
|
||||
@@ -334,6 +327,27 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send the portion of the textBuffer starting at _activeTextStart to the end of the buffer.
|
||||
// Then clear the TextBlock and hide it until the next time text is received.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TSFInputControl::_SendAndClearText()
|
||||
{
|
||||
const auto text = _inputBuffer.substr(_activeTextStart);
|
||||
|
||||
_compositionCompletedHandlers(text);
|
||||
|
||||
_activeTextStart = _inputBuffer.length();
|
||||
|
||||
TextBlock().Text(L"");
|
||||
|
||||
// hide the controls until text input starts again
|
||||
Canvas().Visibility(Visibility::Collapsed);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handler for FormatUpdating event by CoreEditContext responsible
|
||||
// for handling different format updates for a particular range of text.
|
||||
|
||||
@@ -36,11 +36,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
void NotifyFocusEnter();
|
||||
void NotifyFocusLeave();
|
||||
void ClearBuffer();
|
||||
|
||||
void Close();
|
||||
|
||||
static void OnCompositionChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
TYPED_EVENT(CurrentCursorPosition, TerminalControl::TSFInputControl, TerminalControl::CursorPositionEventArgs);
|
||||
TYPED_EVENT(CurrentFontInfo, TerminalControl::TSFInputControl, TerminalControl::FontInfoEventArgs);
|
||||
@@ -67,14 +66,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionStarted_revoker _compositionStartedRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionCompleted_revoker _compositionCompletedRevoker;
|
||||
|
||||
Windows::UI::Xaml::Controls::Canvas _canvas;
|
||||
Windows::UI::Xaml::Controls::TextBlock _textBlock;
|
||||
|
||||
Windows::UI::Text::Core::CoreTextEditContext _editContext;
|
||||
|
||||
std::wstring _inputBuffer;
|
||||
|
||||
void _Create();
|
||||
bool _inComposition;
|
||||
size_t _activeTextStart;
|
||||
void _SendAndClearText();
|
||||
};
|
||||
}
|
||||
namespace winrt::Microsoft::Terminal::TerminalControl::factory_implementation
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
|
||||
void NotifyFocusEnter();
|
||||
void NotifyFocusLeave();
|
||||
void ClearBuffer();
|
||||
|
||||
void Close();
|
||||
}
|
||||
|
||||
17
src/cascadia/TerminalControl/TSFInputControl.xaml
Normal file
17
src/cascadia/TerminalControl/TSFInputControl.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<UserControl
|
||||
x:Class="Microsoft.Terminal.TerminalControl.TSFInputControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="768"
|
||||
d:DesignWidth="1024">
|
||||
|
||||
<Canvas x:Name="Canvas"
|
||||
Visibility="Collapsed">
|
||||
<TextBlock x:Name="TextBlock"
|
||||
IsTextSelectionEnabled="false"
|
||||
TextDecorations="Underline" />
|
||||
</Canvas>
|
||||
</UserControl>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,13 +61,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
winrt::fire_and_forget UpdateSettings(Settings::IControlSettings newSettings);
|
||||
|
||||
hstring Title();
|
||||
hstring GetProfileName() const;
|
||||
|
||||
bool CopySelectionToClipboard(bool trimTrailingWhitespace);
|
||||
bool CopySelectionToClipboard(bool collapseText);
|
||||
void PasteTextFromClipboard();
|
||||
void Close();
|
||||
Windows::Foundation::Size CharacterDimensions() const;
|
||||
Windows::Foundation::Size MinimumSize() const;
|
||||
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const;
|
||||
Windows::Foundation::Size MinimumSize();
|
||||
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension);
|
||||
|
||||
void ScrollViewport(int viewTop);
|
||||
int GetScrollOffset();
|
||||
@@ -77,15 +78,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void ResetFontSize();
|
||||
|
||||
winrt::fire_and_forget SwapChainChanged();
|
||||
winrt::fire_and_forget _RendererEnteredErrorState();
|
||||
void _RenderRetryButton_Click(IInspectable const& button, IInspectable const& args);
|
||||
|
||||
void CreateSearchBoxControl();
|
||||
|
||||
bool OnF7Pressed();
|
||||
|
||||
~TermControl();
|
||||
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
|
||||
const FontInfo GetActualFont() const;
|
||||
const Windows::UI::Xaml::Thickness GetPadding() const;
|
||||
const Windows::UI::Xaml::Thickness GetPadding();
|
||||
|
||||
TerminalConnection::ConnectionState ConnectionState() const;
|
||||
|
||||
@@ -105,20 +110,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
|
||||
TerminalConnection::ITerminalConnection _connection;
|
||||
bool _initializedTerminal;
|
||||
|
||||
Windows::UI::Xaml::Controls::Grid _root;
|
||||
Windows::UI::Xaml::Controls::Image _bgImageLayer;
|
||||
Windows::UI::Xaml::Controls::SwapChainPanel _swapChainPanel;
|
||||
Windows::UI::Xaml::Controls::Primitives::ScrollBar _scrollBar;
|
||||
|
||||
winrt::com_ptr<SearchBoxControl> _searchBox;
|
||||
|
||||
TSFInputControl _tsfInputControl;
|
||||
|
||||
event_token _connectionOutputEventToken;
|
||||
TermControl::Tapped_revoker _tappedRevoker;
|
||||
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
|
||||
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
||||
@@ -159,26 +157,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// imported from WinUser
|
||||
// Used for PointerPoint.Timestamp Property (https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.pointerpoint.timestamp#Windows_UI_Input_PointerPoint_Timestamp)
|
||||
Timestamp _multiClickTimer;
|
||||
Timestamp _lastMouseClick;
|
||||
Timestamp _lastMouseClickTimestamp;
|
||||
unsigned int _multiClickCounter;
|
||||
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPos;
|
||||
std::optional<winrt::Windows::Foundation::Point> _clickDragStartPos{ std::nullopt };
|
||||
|
||||
// Event revokers -- we need to deregister ourselves before we die,
|
||||
// lest we get callbacks afterwards.
|
||||
winrt::Windows::UI::Xaml::Controls::Control::SizeChanged_revoker _sizeChangedRevoker;
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::CompositionScaleChanged_revoker _compositionScaleChangedRevoker;
|
||||
std::optional<winrt::Windows::Foundation::Point> _focusRaisedClickPos;
|
||||
bool _clickDrag;
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
|
||||
|
||||
void _Create();
|
||||
void _ApplyUISettings();
|
||||
void _InitializeBackgroundBrush();
|
||||
winrt::fire_and_forget _BackgroundColorChanged(const uint32_t color);
|
||||
bool _InitializeTerminal();
|
||||
void _UpdateFont(const bool initialUpdate = false);
|
||||
void _SetFontSize(int fontSize);
|
||||
void _TappedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e);
|
||||
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e);
|
||||
void _PointerPressedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
@@ -219,6 +213,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
|
||||
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
|
||||
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
|
||||
bool _CanSendVTMouseInput();
|
||||
|
||||
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
|
||||
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);
|
||||
|
||||
@@ -7,6 +7,17 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
delegate void FontSizeChangedEventArgs(Int32 width, Int32 height, Boolean isInitialChange);
|
||||
delegate void ScrollPositionChangedEventArgs(Int32 viewTop, Int32 viewHeight, Int32 bufferLength);
|
||||
|
||||
// C++/winrt makes it difficult to share this idl between two projects,
|
||||
// Instead, we just pin the uuid and include it in both TermControl and App
|
||||
// If you update this one, please update TerminalApp\IF7Listener.idl.
|
||||
// If you change this interface, please update the guid.
|
||||
// If you press F7 and get a runtime error, go make sure both copies are the same.
|
||||
[uuid("339e1a87-5315-4da6-96f0-565549b6472b")]
|
||||
interface IF7Listener
|
||||
{
|
||||
Boolean OnF7Pressed();
|
||||
}
|
||||
|
||||
runtimeclass CopyToClipboardEventArgs
|
||||
{
|
||||
String Text { get; };
|
||||
@@ -19,7 +30,7 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
void HandleClipboardData(String data);
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl
|
||||
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IF7Listener
|
||||
{
|
||||
TermControl();
|
||||
TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
@@ -41,7 +52,7 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
|
||||
String Title { get; };
|
||||
|
||||
Boolean CopySelectionToClipboard(Boolean trimTrailingWhitespace);
|
||||
Boolean CopySelectionToClipboard(Boolean collapseText);
|
||||
void PasteTextFromClipboard();
|
||||
void Close();
|
||||
Windows.Foundation.Size CharacterDimensions { get; };
|
||||
|
||||
99
src/cascadia/TerminalControl/TermControl.xaml
Normal file
99
src/cascadia/TerminalControl/TermControl.xaml
Normal file
@@ -0,0 +1,99 @@
|
||||
<UserControl
|
||||
x:Class="Microsoft.Terminal.TerminalControl.TermControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.Terminal.TerminalControl"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
d:DesignHeight="768"
|
||||
d:DesignWidth="1024"
|
||||
TabNavigation="Cycle"
|
||||
IsTabStop="True"
|
||||
AllowFocusOnInteraction="True"
|
||||
AllowDrop="True"
|
||||
Drop="_DragDropHandler"
|
||||
DragOver="_DragOverHandler"
|
||||
Tapped="_TappedHandler"
|
||||
PointerWheelChanged="_MouseWheelHandler"
|
||||
PreviewKeyDown="_KeyDownHandler"
|
||||
CharacterReceived="_CharacterHandler"
|
||||
GotFocus="_GotFocusHandler"
|
||||
LostFocus="_LostFocusHandler">
|
||||
<!--
|
||||
TODO GH#4031: We've investigated whether we should be using KeyDown or PreviewKeyDown
|
||||
but not necessarily come to a consensus. It's possible that moving input to the SwapChainPanel
|
||||
will solve a bunch of the search input issues we found last time we switched.
|
||||
-->
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Image x:Name="BackgroundImage"
|
||||
AutomationProperties.AccessibilityView="Raw" />
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0">
|
||||
<SwapChainPanel x:Name="SwapChainPanel"
|
||||
SizeChanged="_SwapChainSizeChanged"
|
||||
CompositionScaleChanged="_SwapChainScaleChanged"
|
||||
PointerPressed="_PointerPressedHandler"
|
||||
PointerMoved="_PointerMovedHandler"
|
||||
PointerReleased="_PointerReleasedHandler" />
|
||||
|
||||
<!-- Putting this in a grid w/ the SwapChainPanel
|
||||
ensures that it's always aligned w/ the scrollbar -->
|
||||
<local:SearchBoxControl x:Name="SearchBox"
|
||||
x:Load="False"
|
||||
Visibility="Collapsed"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Search="_Search"
|
||||
Closed="_CloseSearchBoxControl" />
|
||||
</Grid>
|
||||
|
||||
<ScrollBar Grid.Column="1"
|
||||
x:Name="ScrollBar"
|
||||
Orientation="Vertical"
|
||||
IndicatorMode="MouseIndicator"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Stretch"
|
||||
Maximum="1"
|
||||
ViewportSize="10"
|
||||
IsTabStop="False"
|
||||
SmallChange="1"
|
||||
LargeChange="4"
|
||||
ValueChanged="_ScrollbarChangeHandler"
|
||||
PointerPressed="_CapturePointer"
|
||||
PointerReleased="_ReleasePointerCapture" />
|
||||
</Grid>
|
||||
|
||||
<local:TSFInputControl x:Name="TSFInputControl"
|
||||
CompositionCompleted="_CompositionCompleted"
|
||||
CurrentCursorPosition="_CurrentCursorPositionHandler"
|
||||
CurrentFontInfo="_FontInfoHandler" />
|
||||
|
||||
<Grid x:Name="RendererFailedNotice"
|
||||
x:Load="False"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Border Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
|
||||
BorderBrush="{ThemeResource SystemAccentColor}"
|
||||
Margin="8,8,8,8"
|
||||
Padding="8,8,8,8"
|
||||
BorderThickness="2,2,2,2"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||
<StackPanel>
|
||||
<TextBlock HorizontalAlignment="Center" x:Uid="TermControl_RendererFailedTextBlock" TextWrapping="WrapWholeWords"/>
|
||||
<Button Click="_RenderRetryButton_Click" x:Uid="TermControl_RendererRetryButton" HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -3,14 +3,17 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include <UIAutomationCore.h>
|
||||
#include <LibraryResources.h>
|
||||
#include "TermControlAutomationPeer.h"
|
||||
#include "TermControl.h"
|
||||
#include "TermControlAutomationPeer.g.cpp"
|
||||
|
||||
#include "XamlUiaTextRange.h"
|
||||
#include "..\types\UiaTracing.h"
|
||||
|
||||
using namespace Microsoft::Console::Types;
|
||||
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
|
||||
using namespace winrt::Windows::Graphics::Display;
|
||||
|
||||
namespace UIA
|
||||
{
|
||||
@@ -28,9 +31,10 @@ namespace XamlAutomation
|
||||
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
TermControlAutomationPeer::TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl* owner) :
|
||||
TermControlAutomationPeerT<TermControlAutomationPeer>(*owner) // pass owner to FrameworkElementAutomationPeer
|
||||
TermControlAutomationPeerT<TermControlAutomationPeer>(*owner), // pass owner to FrameworkElementAutomationPeer
|
||||
_termControl{ owner }
|
||||
{
|
||||
THROW_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, owner, std::bind(&TermControlAutomationPeer::GetBoundingRectWrapped, this)));
|
||||
THROW_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, _termControl->GetUiaData(), this));
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
@@ -41,6 +45,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControlAutomationPeer::SignalSelectionChanged()
|
||||
{
|
||||
UiaTracing::Signal::SelectionChanged();
|
||||
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
|
||||
// The event that is raised when the text selection is modified.
|
||||
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextSelectionChanged);
|
||||
@@ -55,6 +60,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControlAutomationPeer::SignalTextChanged()
|
||||
{
|
||||
UiaTracing::Signal::TextChanged();
|
||||
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
|
||||
// The event that is raised when textual content is modified.
|
||||
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextChanged);
|
||||
@@ -69,13 +75,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControlAutomationPeer::SignalCursorChanged()
|
||||
{
|
||||
UiaTracing::Signal::CursorChanged();
|
||||
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
|
||||
// The event that is raised when the text was changed in an edit control.
|
||||
RaiseAutomationEvent(AutomationEvents::TextEditTextChanged);
|
||||
// Do NOT fire a TextEditTextChanged. Generally, an app on the other side
|
||||
// will expect more information. Though you can dispatch that event
|
||||
// on its own, it may result in a nullptr exception on the other side
|
||||
// because no additional information was provided. Crashing the screen
|
||||
// reader.
|
||||
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextSelectionChanged);
|
||||
});
|
||||
}
|
||||
|
||||
winrt::hstring TermControlAutomationPeer::GetClassNameCore() const
|
||||
hstring TermControlAutomationPeer::GetClassNameCore() const
|
||||
{
|
||||
return L"TermControl";
|
||||
}
|
||||
@@ -85,13 +97,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return AutomationControlType::Text;
|
||||
}
|
||||
|
||||
winrt::hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
|
||||
hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
|
||||
{
|
||||
// TODO GitHub #2142: Localize string
|
||||
return L"TerminalControl";
|
||||
return RS_(L"TerminalControl_ControlType");
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const
|
||||
Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const
|
||||
{
|
||||
switch (patternInterface)
|
||||
{
|
||||
@@ -103,15 +114,41 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
}
|
||||
|
||||
AutomationOrientation TermControlAutomationPeer::GetOrientationCore() const
|
||||
{
|
||||
return AutomationOrientation::Vertical;
|
||||
}
|
||||
|
||||
hstring TermControlAutomationPeer::GetNameCore() const
|
||||
{
|
||||
// fallback to title if profile name is empty
|
||||
auto profileName = _termControl->GetProfileName();
|
||||
if (profileName.empty())
|
||||
{
|
||||
return _termControl->Title();
|
||||
}
|
||||
return profileName;
|
||||
}
|
||||
|
||||
hstring TermControlAutomationPeer::GetHelpTextCore() const
|
||||
{
|
||||
return _termControl->Title();
|
||||
}
|
||||
|
||||
AutomationLiveSetting TermControlAutomationPeer::GetLiveSettingCore() const
|
||||
{
|
||||
return AutomationLiveSetting::Polite;
|
||||
}
|
||||
|
||||
#pragma region ITextProvider
|
||||
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetSelection()
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetSelection()
|
||||
{
|
||||
SAFEARRAY* pReturnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->GetSelection(&pReturnVal));
|
||||
return WrapArrayOfTextRangeProviders(pReturnVal);
|
||||
}
|
||||
|
||||
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetVisibleRanges()
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetVisibleRanges()
|
||||
{
|
||||
SAFEARRAY* pReturnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->GetVisibleRanges(&pReturnVal));
|
||||
@@ -150,7 +187,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Automation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection()
|
||||
XamlAutomation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection()
|
||||
{
|
||||
UIA::SupportedTextSelection returnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->get_SupportedTextSelection(&returnVal));
|
||||
@@ -159,27 +196,63 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
#pragma endregion
|
||||
|
||||
RECT TermControlAutomationPeer::GetBoundingRectWrapped()
|
||||
#pragma region IControlAccessibilityInfo
|
||||
COORD TermControlAutomationPeer::GetFontSize() const
|
||||
{
|
||||
return _termControl->GetActualFont().GetSize();
|
||||
}
|
||||
|
||||
RECT TermControlAutomationPeer::GetBounds() const
|
||||
{
|
||||
auto rect = GetBoundingRectangle();
|
||||
return {
|
||||
gsl::narrow<LONG>(rect.X),
|
||||
gsl::narrow<LONG>(rect.Y),
|
||||
gsl::narrow<LONG>(rect.X + rect.Width),
|
||||
gsl::narrow<LONG>(rect.Y + rect.Height)
|
||||
gsl::narrow_cast<LONG>(rect.X),
|
||||
gsl::narrow_cast<LONG>(rect.Y),
|
||||
gsl::narrow_cast<LONG>(rect.X + rect.Width),
|
||||
gsl::narrow_cast<LONG>(rect.Y + rect.Height)
|
||||
};
|
||||
}
|
||||
|
||||
HRESULT TermControlAutomationPeer::GetHostUiaProvider(IRawElementProviderSimple** provider)
|
||||
{
|
||||
RETURN_HR_IF(E_INVALIDARG, provider == nullptr);
|
||||
*provider = nullptr;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
RECT TermControlAutomationPeer::GetPadding() const
|
||||
{
|
||||
auto padding = _termControl->GetPadding();
|
||||
return {
|
||||
gsl::narrow_cast<LONG>(padding.Left),
|
||||
gsl::narrow_cast<LONG>(padding.Top),
|
||||
gsl::narrow_cast<LONG>(padding.Right),
|
||||
gsl::narrow_cast<LONG>(padding.Bottom)
|
||||
};
|
||||
}
|
||||
|
||||
double TermControlAutomationPeer::GetScaleFactor() const
|
||||
{
|
||||
return DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
|
||||
}
|
||||
|
||||
void TermControlAutomationPeer::ChangeViewport(const SMALL_RECT NewWindow)
|
||||
{
|
||||
_termControl->ScrollViewport(NewWindow.Top);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
// Method Description:
|
||||
// - extracts the UiaTextRanges from the SAFEARRAY and converts them to Xaml ITextRangeProviders
|
||||
// Arguments:
|
||||
// - SAFEARRAY of UIA::UiaTextRange (ITextRangeProviders)
|
||||
// Return Value:
|
||||
// - com_array of Xaml Wrapped UiaTextRange (ITextRangeProviders)
|
||||
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges)
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges)
|
||||
{
|
||||
// transfer ownership of UiaTextRanges to this new vector
|
||||
auto providers = SafeArrayToOwningVector<::Microsoft::Terminal::UiaTextRange>(textRanges);
|
||||
auto providers = SafeArrayToOwningVector<::Microsoft::Terminal::TermControlUiaTextRange>(textRanges);
|
||||
int count = gsl::narrow<int>(providers.size());
|
||||
|
||||
std::vector<XamlAutomation::ITextRangeProvider> vec;
|
||||
@@ -187,11 +260,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto parentProvider = this->ProviderFromPeer(*this);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
auto xutr = winrt::make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);
|
||||
auto xutr = make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);
|
||||
vec.emplace_back(xutr.as<XamlAutomation::ITextRangeProvider>());
|
||||
}
|
||||
|
||||
winrt::com_array<XamlAutomation::ITextRangeProvider> result{ vec };
|
||||
com_array<XamlAutomation::ITextRangeProvider> result{ vec };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -27,22 +27,30 @@ Author(s):
|
||||
#include "TermControl.h"
|
||||
#include "TermControlAutomationPeer.g.h"
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
#include "TermControlUiaProvider.hpp"
|
||||
#include "../types/TermControlUiaProvider.hpp"
|
||||
#include "../types/IUiaEventDispatcher.h"
|
||||
#include "../types/IControlAccessibilityInfo.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
struct TermControlAutomationPeer :
|
||||
public TermControlAutomationPeerT<TermControlAutomationPeer>,
|
||||
::Microsoft::Console::Types::IUiaEventDispatcher
|
||||
::Microsoft::Console::Types::IUiaEventDispatcher,
|
||||
::Microsoft::Console::Types::IControlAccessibilityInfo
|
||||
{
|
||||
public:
|
||||
TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl* owner);
|
||||
TermControlAutomationPeer(Microsoft::Terminal::TerminalControl::implementation::TermControl* owner);
|
||||
|
||||
winrt::hstring GetClassNameCore() const;
|
||||
winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
|
||||
winrt::hstring GetLocalizedControlTypeCore() const;
|
||||
winrt::Windows::Foundation::IInspectable GetPatternCore(winrt::Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const;
|
||||
#pragma region FrameworkElementAutomationPeer
|
||||
hstring GetClassNameCore() const;
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
|
||||
hstring GetLocalizedControlTypeCore() const;
|
||||
Windows::Foundation::IInspectable GetPatternCore(Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const;
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationOrientation GetOrientationCore() const;
|
||||
hstring GetNameCore() const;
|
||||
hstring GetHelpTextCore() const;
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationLiveSetting GetLiveSettingCore() const;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IUiaEventDispatcher
|
||||
void SignalSelectionChanged() override;
|
||||
@@ -53,17 +61,27 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
#pragma region ITextProvider Pattern
|
||||
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromPoint(Windows::Foundation::Point screenLocation);
|
||||
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromChild(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple childElement);
|
||||
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetVisibleRanges();
|
||||
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetSelection();
|
||||
com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetVisibleRanges();
|
||||
com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetSelection();
|
||||
Windows::UI::Xaml::Automation::SupportedTextSelection SupportedTextSelection();
|
||||
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider DocumentRange();
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IControlAccessibilityInfo Pattern
|
||||
// Inherited via IControlAccessibilityInfo
|
||||
virtual COORD GetFontSize() const override;
|
||||
virtual RECT GetBounds() const override;
|
||||
virtual RECT GetPadding() const override;
|
||||
virtual double GetScaleFactor() const override;
|
||||
virtual void ChangeViewport(SMALL_RECT NewWindow) override;
|
||||
virtual HRESULT GetHostUiaProvider(IRawElementProviderSimple** provider) override;
|
||||
#pragma endregion
|
||||
|
||||
RECT GetBoundingRectWrapped();
|
||||
|
||||
private:
|
||||
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl* _termControl;
|
||||
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,17 +36,15 @@
|
||||
<ClInclude Include="SearchBoxControl.h">
|
||||
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TermControlUiaProvider.hpp" />
|
||||
<ClInclude Include="TermControl.h">
|
||||
<DependentUpon>TermControl.idl</DependentUpon>
|
||||
<DependentUpon>TermControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TermControlAutomationPeer.h">
|
||||
<DependentUpon>TermControlAutomationPeer.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TSFInputControl.h">
|
||||
<DependentUpon>TSFInputControl.idl</DependentUpon>
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="UiaTextRange.hpp" />
|
||||
<ClInclude Include="XamlUiaTextRange.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -57,34 +55,36 @@
|
||||
<ClCompile Include="SearchBoxControl.cpp">
|
||||
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TermControlUiaProvider.cpp" />
|
||||
<ClCompile Include="TermControl.cpp">
|
||||
<DependentUpon>TermControl.idl</DependentUpon>
|
||||
<DependentUpon>TermControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TSFInputControl.cpp">
|
||||
<DependentUpon>TSFInputControl.idl</DependentUpon>
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="TermControlAutomationPeer.cpp">
|
||||
<DependentUpon>TermControlAutomationPeer.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UiaTextRange.cpp" />
|
||||
<ClCompile Include="XamlUiaTextRange.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="SearchBoxControl.idl">
|
||||
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
|
||||
</Midl>
|
||||
<Midl Include="TermControl.idl" />
|
||||
<Midl Include="TermControl.idl">
|
||||
<DependentUpon>TermControl.xaml</DependentUpon>
|
||||
</Midl>
|
||||
<Midl Include="TermControlAutomationPeer.idl" />
|
||||
<Midl Include="TSFInputControl.idl" />
|
||||
<Midl Include="TSFInputControl.idl">
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="TerminalControl.def" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources/en-US/Resources.resw" />
|
||||
<PRIResource Include="Resources/Resources.language-en.resw" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
@@ -113,6 +113,12 @@
|
||||
<Page Include="SearchBoxControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="TermControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="TSFInputControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<ClCompile Include="TermControlUiaProvider.cpp" />
|
||||
<ClCompile Include="UiaTextRange.cpp" />
|
||||
<ClCompile Include="SearchBoxControl.cpp" />
|
||||
<ClCompile Include="init.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
@@ -26,12 +27,12 @@
|
||||
<ClInclude Include="XamlUiaTextRange.h" />
|
||||
<ClInclude Include="TermControlUiaProvider.hpp" />
|
||||
<ClInclude Include="UiaTextRange.hpp" />
|
||||
<ClCompile Include="SearchBoxControl.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="TermControl.idl" />
|
||||
<Midl Include="TermControlAutomationPeer.idl" />
|
||||
<Midl Include="SearchBoxControl.idl" />
|
||||
<Midl Include="TSFInputControl.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="TerminalControl.def" />
|
||||
@@ -43,4 +44,9 @@
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources/Resources.language-en.resw">
|
||||
<Filter>Resources</Filter>
|
||||
</PRIResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "UiaTextRange.hpp"
|
||||
#include "TermControlUiaProvider.hpp"
|
||||
|
||||
using namespace Microsoft::Terminal;
|
||||
using namespace Microsoft::Console::Types;
|
||||
using namespace Microsoft::WRL;
|
||||
using namespace winrt::Windows::Graphics::Display;
|
||||
|
||||
HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
|
||||
_In_ IRawElementProviderSimple* pProvider,
|
||||
_In_ const std::wstring_view wordDelimiters,
|
||||
_Out_ std::deque<ComPtr<UiaTextRange>>& ranges)
|
||||
{
|
||||
try
|
||||
{
|
||||
typename std::remove_reference<decltype(ranges)>::type temporaryResult;
|
||||
|
||||
// get the selection rects
|
||||
const auto rectangles = pData->GetSelectionRects();
|
||||
|
||||
// create a range for each row
|
||||
for (const auto& rect : rectangles)
|
||||
{
|
||||
const auto start = rect.Origin();
|
||||
const auto end = rect.EndExclusive();
|
||||
|
||||
ComPtr<UiaTextRange> range;
|
||||
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, wordDelimiters));
|
||||
temporaryResult.emplace_back(std::move(range));
|
||||
}
|
||||
std::swap(temporaryResult, ranges);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
// degenerate range constructor.
|
||||
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters)
|
||||
{
|
||||
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters);
|
||||
}
|
||||
|
||||
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
|
||||
_In_ IRawElementProviderSimple* const pProvider,
|
||||
const Cursor& cursor,
|
||||
const std::wstring_view wordDelimiters)
|
||||
{
|
||||
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, cursor, wordDelimiters);
|
||||
}
|
||||
|
||||
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
|
||||
_In_ IRawElementProviderSimple* const pProvider,
|
||||
const COORD start,
|
||||
const COORD end,
|
||||
const std::wstring_view wordDelimiters)
|
||||
{
|
||||
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, wordDelimiters);
|
||||
}
|
||||
|
||||
// returns a degenerate text range of the start of the row closest to the y value of point
|
||||
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
|
||||
_In_ IRawElementProviderSimple* const pProvider,
|
||||
const UiaPoint point,
|
||||
const std::wstring_view wordDelimiters)
|
||||
{
|
||||
RETURN_IF_FAILED(UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters));
|
||||
Initialize(point);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT UiaTextRange::RuntimeClassInitialize(const UiaTextRange& a)
|
||||
{
|
||||
return UiaTextRangeBase::RuntimeClassInitialize(a);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal)
|
||||
{
|
||||
RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr);
|
||||
*ppRetVal = nullptr;
|
||||
auto hr = MakeAndInitialize<UiaTextRange>(ppRetVal, *this);
|
||||
|
||||
if (hr != S_OK)
|
||||
{
|
||||
*ppRetVal = nullptr;
|
||||
return hr;
|
||||
}
|
||||
|
||||
#if defined(_DEBUG) && defined(UiaTextRangeBase_DEBUG_MSGS)
|
||||
OutputDebugString(L"Clone\n");
|
||||
std::wstringstream ss;
|
||||
ss << _id << L" cloned to " << (static_cast<UiaTextRangeBase*>(*ppRetVal))->_id;
|
||||
std::wstring str = ss.str();
|
||||
OutputDebugString(str.c_str());
|
||||
OutputDebugString(L"\n");
|
||||
#endif
|
||||
// TODO GitHub #1914: Re-attach Tracing to UIA Tree
|
||||
// tracing
|
||||
/*ApiMsgClone apiMsg;
|
||||
apiMsg.CloneId = static_cast<UiaTextRangeBase*>(*ppRetVal)->GetId();
|
||||
Tracing::s_TraceUia(this, ApiCall::Clone, &apiMsg);*/
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void UiaTextRange::_ChangeViewport(const SMALL_RECT NewWindow)
|
||||
{
|
||||
auto provider = static_cast<TermControlUiaProvider*>(_pProvider);
|
||||
provider->ChangeViewport(NewWindow);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Transform coordinates relative to the client to relative to the screen
|
||||
// Arguments:
|
||||
// - clientPoint: coordinates relative to the client where
|
||||
// (0,0) is the top-left of the app window
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void UiaTextRange::_TranslatePointToScreen(LPPOINT clientPoint) const
|
||||
{
|
||||
auto provider = static_cast<TermControlUiaProvider*>(_pProvider);
|
||||
|
||||
auto includeOffsets = [](long clientPos, double termControlPos, double padding, double scaleFactor) {
|
||||
auto result = base::ClampedNumeric<double>(clientPos);
|
||||
result += padding;
|
||||
result *= scaleFactor;
|
||||
result += termControlPos;
|
||||
return result;
|
||||
};
|
||||
|
||||
// update based on TermControl location (important for Panes)
|
||||
UiaRect boundingRect;
|
||||
THROW_IF_FAILED(provider->get_BoundingRectangle(&boundingRect));
|
||||
|
||||
// update based on TermControl padding
|
||||
const auto padding = provider->GetPadding();
|
||||
|
||||
// Get scale factor for display
|
||||
const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
|
||||
|
||||
clientPoint->x = includeOffsets(clientPoint->x, boundingRect.left, padding.Left, scaleFactor);
|
||||
clientPoint->y = includeOffsets(clientPoint->y, boundingRect.top, padding.Top, scaleFactor);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Transform coordinates relative to the screen to relative to the client
|
||||
// Arguments:
|
||||
// - screenPoint: coordinates relative to the screen where
|
||||
// (0,0) is the top-left of the screen
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void UiaTextRange::_TranslatePointFromScreen(LPPOINT screenPoint) const
|
||||
{
|
||||
auto provider = static_cast<TermControlUiaProvider*>(_pProvider);
|
||||
|
||||
auto includeOffsets = [](long screenPos, double termControlPos, double padding, double scaleFactor) {
|
||||
auto result = base::ClampedNumeric<double>(screenPos);
|
||||
result -= termControlPos;
|
||||
result /= scaleFactor;
|
||||
result -= padding;
|
||||
return result;
|
||||
};
|
||||
|
||||
// update based on TermControl location (important for Panes)
|
||||
UiaRect boundingRect;
|
||||
THROW_IF_FAILED(provider->get_BoundingRectangle(&boundingRect));
|
||||
|
||||
// update based on TermControl padding
|
||||
const auto padding = provider->GetPadding();
|
||||
|
||||
// Get scale factor for display
|
||||
const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
|
||||
|
||||
screenPoint->x = includeOffsets(screenPoint->x, boundingRect.left, padding.Left, scaleFactor);
|
||||
screenPoint->y = includeOffsets(screenPoint->y, boundingRect.top, padding.Top, scaleFactor);
|
||||
}
|
||||
|
||||
const COORD UiaTextRange::_getScreenFontSize() const
|
||||
{
|
||||
// Do NOT get the font info from IRenderData. It is a dummy font info.
|
||||
// Instead, the font info is saved in the TermControl. So we have to
|
||||
// ask our parent to get it for us.
|
||||
auto provider = static_cast<TermControlUiaProvider*>(_pProvider);
|
||||
return provider->GetFontSize();
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "XamlUiaTextRange.h"
|
||||
#include "UiaTextRange.hpp"
|
||||
#include "../types/TermControlUiaTextRange.hpp"
|
||||
#include <UIAutomationClient.h>
|
||||
|
||||
// the same as COR_E_NOTSUPPORTED
|
||||
|
||||
@@ -22,7 +22,7 @@ Author(s):
|
||||
|
||||
#include "TermControlAutomationPeer.h"
|
||||
#include <UIAutomationCore.h>
|
||||
#include "UiaTextRange.hpp"
|
||||
#include "../types/TermControlUiaTextRange.hpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <winrt/Windows.Graphics.Display.h>
|
||||
#include <winrt/windows.ui.core.h>
|
||||
#include <winrt/Windows.ui.input.h>
|
||||
#include <winrt/Windows.UI.ViewManagement.h>
|
||||
#include <winrt/Windows.UI.Xaml.h>
|
||||
#include <winrt/Windows.UI.Xaml.Automation.Peers.h>
|
||||
#include <winrt/Windows.UI.Text.Core.h>
|
||||
|
||||
@@ -28,7 +28,9 @@ namespace Microsoft::Terminal::Core
|
||||
|
||||
virtual bool SetCursorPosition(short x, short y) noexcept = 0;
|
||||
virtual COORD GetCursorPosition() noexcept = 0;
|
||||
virtual bool SetCursorVisibility(const bool visible) noexcept = 0;
|
||||
virtual bool CursorLineFeed(const bool withReturn) noexcept = 0;
|
||||
virtual bool EnableCursorBlinking(const bool enable) noexcept = 0;
|
||||
|
||||
virtual bool DeleteCharacter(const size_t count) noexcept = 0;
|
||||
virtual bool InsertCharacter(const size_t count) noexcept = 0;
|
||||
@@ -45,6 +47,17 @@ namespace Microsoft::Terminal::Core
|
||||
virtual bool SetDefaultForeground(const DWORD color) noexcept = 0;
|
||||
virtual bool SetDefaultBackground(const DWORD color) noexcept = 0;
|
||||
|
||||
virtual bool SetCursorKeysMode(const bool applicationMode) noexcept = 0;
|
||||
virtual bool SetKeypadMode(const bool applicationMode) noexcept = 0;
|
||||
virtual bool EnableVT200MouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableSGRExtendedMouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableButtonEventMouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableAnyEventMouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableAlternateScrollMode(const bool enabled) noexcept = 0;
|
||||
|
||||
virtual bool IsVtInputEnabled() const = 0;
|
||||
|
||||
protected:
|
||||
ITerminalApi() = default;
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Microsoft::Terminal::Core
|
||||
ITerminalInput& operator=(ITerminalInput&&) = default;
|
||||
|
||||
virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) = 0;
|
||||
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) = 0;
|
||||
virtual bool SendCharEvent(const wchar_t ch) = 0;
|
||||
|
||||
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
|
||||
|
||||
@@ -44,12 +44,10 @@ Terminal::Terminal() :
|
||||
_pfnWriteInput{ nullptr },
|
||||
_scrollOffset{ 0 },
|
||||
_snapOnInput{ true },
|
||||
_boxSelection{ false },
|
||||
_selectionActive{ false },
|
||||
_blockSelection{ false },
|
||||
_selection{ std::nullopt },
|
||||
_allowSingleCharSelection{ true },
|
||||
_copyOnSelect{ false },
|
||||
_selectionAnchor{ 0, 0 },
|
||||
_endSelectionPosition{ 0, 0 }
|
||||
_copyOnSelect{ false }
|
||||
{
|
||||
auto dispatch = std::make_unique<TerminalDispatch>(*this);
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
||||
@@ -176,24 +174,174 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
const auto dx = ::base::ClampSub(viewportSize.X, oldDimensions.X);
|
||||
|
||||
const auto oldTop = _mutableViewport.Top();
|
||||
|
||||
const short newBufferHeight = viewportSize.Y + _scrollbackLines;
|
||||
COORD bufferSize{ viewportSize.X, newBufferHeight };
|
||||
RETURN_IF_FAILED(_buffer->ResizeTraditional(bufferSize));
|
||||
const short newBufferHeight = ::base::ClampAdd(viewportSize.Y, _scrollbackLines);
|
||||
|
||||
COORD bufferSize{ viewportSize.X, newBufferHeight };
|
||||
|
||||
// Save cursor's relative height versus the viewport
|
||||
const short sCursorHeightInViewportBefore = ::base::ClampSub(_buffer->GetCursor().GetPosition().Y, _mutableViewport.Top());
|
||||
|
||||
// This will be used to determine where the viewport should be in the new buffer.
|
||||
const short oldViewportTop = _mutableViewport.Top();
|
||||
short newViewportTop = oldViewportTop;
|
||||
short newVisibleTop = ::base::saturated_cast<short>(_VisibleStartIndex());
|
||||
|
||||
// If the original buffer had _no_ scroll offset, then we should be at the
|
||||
// bottom in the new buffer as well. Track that case now.
|
||||
const bool originalOffsetWasZero = _scrollOffset == 0;
|
||||
|
||||
// First allocate a new text buffer to take the place of the current one.
|
||||
std::unique_ptr<TextBuffer> newTextBuffer;
|
||||
try
|
||||
{
|
||||
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
|
||||
_buffer->GetCurrentAttributes(),
|
||||
0, // temporarily set size to 0 so it won't render.
|
||||
_buffer->GetRenderTarget());
|
||||
|
||||
// Build a PositionInformation to track the position of both the top of
|
||||
// the mutable viewport and the top of the visible viewport in the new
|
||||
// buffer.
|
||||
// * the new value of mutableViewportTop will be used to figure out
|
||||
// where we should place the mutable viewport in the new buffer. This
|
||||
// requires a bit of trickiness to remain consistent with conpty's
|
||||
// buffer (as seen below).
|
||||
// * the new value of visibleViewportTop will be used to calculate the
|
||||
// new scrollOffsett in the new buffer, so that the visible lines on
|
||||
// the screen remain roughly the same.
|
||||
TextBuffer::PositionInformation oldRows{ 0 };
|
||||
oldRows.mutableViewportTop = oldViewportTop;
|
||||
oldRows.visibleViewportTop = newVisibleTop;
|
||||
|
||||
const std::optional<short> oldViewStart{ oldViewportTop };
|
||||
RETURN_IF_FAILED(TextBuffer::Reflow(*_buffer.get(),
|
||||
*newTextBuffer.get(),
|
||||
_mutableViewport,
|
||||
{ oldRows }));
|
||||
|
||||
newViewportTop = oldRows.mutableViewportTop;
|
||||
newVisibleTop = oldRows.visibleViewportTop;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Conpty resizes a little oddly - if the height decreased, and there were
|
||||
// blank lines at the bottom, those lines will get trimmed. If there's not
|
||||
// blank lines, then the top will get "shifted down", moving the top line
|
||||
// into scrollback. See GH#3490 for more details.
|
||||
//
|
||||
// If the final position in the buffer is on the bottom row of the new
|
||||
// viewport, then we're going to need to move the top down. Otherwise, move
|
||||
// the bottom up.
|
||||
//
|
||||
// There are also important things to consider with line wrapping.
|
||||
// * If a line in scrollback wrapped that didn't previously, we'll need to
|
||||
// make sure to have the new viewport down another line. This will cause
|
||||
// our top to move down.
|
||||
// * If a line _in the viewport_ wrapped that didn't previously, then the
|
||||
// conpty buffer will also have that wrapped line, and will move the
|
||||
// cursor & text down a line in response. This causes our bottom to move
|
||||
// down.
|
||||
//
|
||||
// We're going to use a combo of both these things to calculate where the
|
||||
// new viewport should be. To keep in sync with conpty, we'll need to make
|
||||
// sure that any lines that entered the scrollback _stay in scrollback_. We
|
||||
// do that by taking the max of
|
||||
// * Where the old top line in the viewport exists in the new buffer (as
|
||||
// calculated by TextBuffer::Reflow)
|
||||
// * Where the bottom of the text in the new buffer is (and using that to
|
||||
// calculate another proposed top location).
|
||||
|
||||
const COORD newCursorPos = newTextBuffer->GetCursor().GetPosition();
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26496) // cpp core checks wants this const, but it's assigned immediately below...
|
||||
COORD newLastChar = newCursorPos;
|
||||
try
|
||||
{
|
||||
newLastChar = newTextBuffer->GetLastNonSpaceCharacter();
|
||||
}
|
||||
CATCH_LOG();
|
||||
#pragma warning(pop)
|
||||
|
||||
const auto maxRow = std::max(newLastChar.Y, newCursorPos.Y);
|
||||
|
||||
const short proposedTopFromLastLine = ::base::ClampAdd(::base::ClampSub(maxRow, viewportSize.Y), 1);
|
||||
const short proposedTopFromScrollback = newViewportTop;
|
||||
|
||||
short proposedTop = std::max(proposedTopFromLastLine,
|
||||
proposedTopFromScrollback);
|
||||
|
||||
// If we're using the new location of the old top line to place the
|
||||
// viewport, we might need to make an adjustment to it.
|
||||
//
|
||||
// We're using the last cell of the line to calculate where the top line is
|
||||
// in the new buffer. If that line wrapped, then all the lines below it
|
||||
// shifted down in the buffer. If there's space for all those lines in the
|
||||
// conpty buffer, then the originally unwrapped top line will _still_ be in
|
||||
// the buffer. In that case, don't stick to the _end_ of the old top line,
|
||||
// instead stick to the _start_, which is one line up.
|
||||
//
|
||||
// We can know if there's space in the conpty buffer by checking if the
|
||||
// maxRow (the highest row we've written text to) is above the viewport from
|
||||
// this proposed top position.
|
||||
if (proposedTop == proposedTopFromScrollback)
|
||||
{
|
||||
const auto proposedViewFromTop = Viewport::FromDimensions({ 0, proposedTopFromScrollback }, viewportSize);
|
||||
if (maxRow < proposedViewFromTop.BottomInclusive())
|
||||
{
|
||||
if (dx < 0 && proposedTop > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto row = newTextBuffer->GetRowByOffset(::base::ClampSub(proposedTop, 1));
|
||||
if (row.GetCharRow().WasWrapForced())
|
||||
{
|
||||
proposedTop--;
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the new bottom would be higher than the last row of text, then we
|
||||
// definitely want to use the last row of text to determine where the
|
||||
// viewport should be.
|
||||
const auto proposedViewFromTop = Viewport::FromDimensions({ 0, proposedTopFromScrollback }, viewportSize);
|
||||
if (maxRow > proposedViewFromTop.BottomInclusive())
|
||||
{
|
||||
proposedTop = proposedTopFromLastLine;
|
||||
}
|
||||
|
||||
// Make sure the proposed viewport is within the bounds of the buffer.
|
||||
// First make sure the top is >=0
|
||||
proposedTop = std::max(static_cast<short>(0), proposedTop);
|
||||
|
||||
auto proposedTop = oldTop;
|
||||
const auto newView = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
|
||||
const auto proposedBottom = newView.BottomExclusive();
|
||||
// If the new bottom would be below the bottom of the buffer, then slide the
|
||||
// top up so that we'll still fit within the buffer.
|
||||
const auto newView = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
|
||||
const auto proposedBottom = newView.BottomExclusive();
|
||||
if (proposedBottom > bufferSize.Y)
|
||||
{
|
||||
proposedTop -= (proposedBottom - bufferSize.Y);
|
||||
proposedTop = ::base::ClampSub(proposedTop, ::base::ClampSub(proposedBottom, bufferSize.Y));
|
||||
}
|
||||
|
||||
_mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
|
||||
_scrollOffset = 0;
|
||||
|
||||
_buffer.swap(newTextBuffer);
|
||||
|
||||
// GH#3494: Maintain scrollbar position during resize
|
||||
// Make sure that we don't scroll past the mutableViewport at the bottom of the buffer
|
||||
newVisibleTop = std::min(newVisibleTop, _mutableViewport.Top());
|
||||
// Make sure we don't scroll past the top of the scrollback
|
||||
newVisibleTop = std::max<short>(newVisibleTop, 0);
|
||||
|
||||
// If the old scrolloffset was 0, then we weren't scrolled back at all
|
||||
// before, and shouldn't be now either.
|
||||
_scrollOffset = originalOffsetWasZero ? 0 : ::base::ClampSub(_mutableViewport.Top(), newVisibleTop);
|
||||
_NotifyScrollEvent();
|
||||
|
||||
return S_OK;
|
||||
@@ -224,6 +372,17 @@ void Terminal::TrySnapOnInput()
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Relays if we are tracking mouse input
|
||||
// Parameters:
|
||||
// - <none>
|
||||
// Return value:
|
||||
// - true, if we are tracking mouse input. False, otherwise
|
||||
bool Terminal::IsTrackingMouseInput() const noexcept
|
||||
{
|
||||
return _terminalInput->IsTrackingMouseInput();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send this particular key event to the terminal. The terminal will translate
|
||||
// the key and the modifiers pressed into the appropriate VT sequence for that
|
||||
@@ -286,6 +445,32 @@ bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlK
|
||||
return translated && manuallyHandled;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send this particular mouse event to the terminal. The terminal will translate
|
||||
// the button and the modifiers pressed into the appropriate VT sequence for that
|
||||
// mouse event. If we do translate the key, we'll return true. In that case, the
|
||||
// event should NOT be processed any further. If we return false, the event
|
||||
// was NOT translated, and we should instead use the event normally
|
||||
// Arguments:
|
||||
// - viewportPos: the position of the mouse event relative to the viewport origin.
|
||||
// - uiButton: the WM mouse button event code
|
||||
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
|
||||
// - wheelDelta: the amount that the scroll wheel changed (should be 0 unless button is a WM_MOUSE*WHEEL)
|
||||
// Return Value:
|
||||
// - true if we translated the key event, and it should not be processed any further.
|
||||
// - false if we did not translate the key, and it should be processed into a character.
|
||||
bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta)
|
||||
{
|
||||
// viewportPos must be within the dimensions of the viewport
|
||||
const auto viewportDimensions = _mutableViewport.Dimensions();
|
||||
if (viewportPos.X < 0 || viewportPos.X >= viewportDimensions.X || viewportPos.Y < 0 || viewportPos.Y >= viewportDimensions.Y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _terminalInput->HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta);
|
||||
}
|
||||
|
||||
bool Terminal::SendCharEvent(const wchar_t ch)
|
||||
{
|
||||
return _terminalInput->HandleChar(ch);
|
||||
@@ -454,6 +639,19 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
|
||||
// With well behaving shells during normal operation this safeguard should normally not be encountered.
|
||||
proposedCursorPosition.X = 0;
|
||||
proposedCursorPosition.Y++;
|
||||
|
||||
// Try the character again.
|
||||
i--;
|
||||
|
||||
// If we write the last cell of the row here, TextBuffer::Write will
|
||||
// mark this line as wrapped for us. If the next character we
|
||||
// process is a newline, the Terminal::CursorLineFeed will unmark
|
||||
// this line as wrapped.
|
||||
|
||||
// TODO: GH#780 - This should really be a _deferred_ newline. If
|
||||
// the next character to come in is a newline or a cursor
|
||||
// movement or anything, then we should _not_ wrap this line
|
||||
// here.
|
||||
}
|
||||
|
||||
_AdjustCursorPosition(proposedCursorPosition);
|
||||
@@ -573,13 +771,16 @@ try
|
||||
CATCH_LOG()
|
||||
|
||||
// Method Description:
|
||||
// - Sets the visibility of the text cursor.
|
||||
// - Sets the cursor to be currently on. On/Off is tracked independently of
|
||||
// cursor visibility (hidden/visible). On/off is controlled by the cursor
|
||||
// blinker. Visibility is usually controlled by the client application. If the
|
||||
// cursor is hidden, then the cursor will remain hidden. If the cursor is
|
||||
// Visible, then it will immediately become visible.
|
||||
// Arguments:
|
||||
// - isVisible: whether the cursor should be visible
|
||||
void Terminal::SetCursorVisible(const bool isVisible) noexcept
|
||||
void Terminal::SetCursorOn(const bool isOn) noexcept
|
||||
{
|
||||
auto& cursor = _buffer->GetCursor();
|
||||
cursor.SetIsVisible(isVisible);
|
||||
_buffer->GetCursor().SetIsOn(isOn);
|
||||
}
|
||||
|
||||
bool Terminal::IsCursorBlinkingAllowed() const noexcept
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Microsoft::Terminal::Core
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class TerminalBufferTests;
|
||||
class TerminalApiTest;
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
#endif
|
||||
@@ -84,6 +85,8 @@ public:
|
||||
bool ReverseText(bool reversed) noexcept override;
|
||||
bool SetCursorPosition(short x, short y) noexcept override;
|
||||
COORD GetCursorPosition() noexcept override;
|
||||
bool SetCursorVisibility(const bool visible) noexcept override;
|
||||
bool EnableCursorBlinking(const bool enable) noexcept override;
|
||||
bool CursorLineFeed(const bool withReturn) noexcept override;
|
||||
bool DeleteCharacter(const size_t count) noexcept override;
|
||||
bool InsertCharacter(const size_t count) noexcept override;
|
||||
@@ -95,11 +98,23 @@ public:
|
||||
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept override;
|
||||
bool SetDefaultForeground(const COLORREF color) noexcept override;
|
||||
bool SetDefaultBackground(const COLORREF color) noexcept override;
|
||||
|
||||
bool SetCursorKeysMode(const bool applicationMode) noexcept override;
|
||||
bool SetKeypadMode(const bool applicationMode) noexcept override;
|
||||
bool EnableVT200MouseMode(const bool enabled) noexcept override;
|
||||
bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept override;
|
||||
bool EnableSGRExtendedMouseMode(const bool enabled) noexcept override;
|
||||
bool EnableButtonEventMouseMode(const bool enabled) noexcept override;
|
||||
bool EnableAnyEventMouseMode(const bool enabled) noexcept override;
|
||||
bool EnableAlternateScrollMode(const bool enabled) noexcept override;
|
||||
|
||||
bool IsVtInputEnabled() const noexcept override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ITerminalInput
|
||||
// These methods are defined in Terminal.cpp
|
||||
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states) override;
|
||||
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) override;
|
||||
bool SendCharEvent(const wchar_t ch) override;
|
||||
|
||||
[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;
|
||||
@@ -107,6 +122,7 @@ public:
|
||||
int GetScrollOffset() noexcept override;
|
||||
|
||||
void TrySnapOnInput() override;
|
||||
bool IsTrackingMouseInput() const noexcept;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IBaseData(base to IRenderData and IUiaData)
|
||||
@@ -132,6 +148,7 @@ public:
|
||||
CursorType GetCursorStyle() const noexcept override;
|
||||
COLORREF GetCursorColor() const noexcept override;
|
||||
bool IsCursorDoubleWidth() const noexcept override;
|
||||
bool IsScreenReversed() const noexcept override;
|
||||
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
||||
const bool IsGridLineDrawingAllowed() noexcept override;
|
||||
#pragma endregion
|
||||
@@ -139,9 +156,11 @@ public:
|
||||
#pragma region IUiaData
|
||||
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override;
|
||||
const bool IsSelectionActive() const noexcept override;
|
||||
const bool IsBlockSelection() const noexcept override;
|
||||
void ClearSelection() override;
|
||||
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
|
||||
const COORD GetSelectionAnchor() const override;
|
||||
const COORD GetSelectionAnchor() const noexcept override;
|
||||
const COORD GetSelectionEnd() const noexcept override;
|
||||
const std::wstring GetConsoleTitle() const noexcept override;
|
||||
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
|
||||
#pragma endregion
|
||||
@@ -151,17 +170,22 @@ public:
|
||||
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
|
||||
void SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept;
|
||||
|
||||
void SetCursorVisible(const bool isVisible) noexcept;
|
||||
void SetCursorOn(const bool isOn) noexcept;
|
||||
bool IsCursorBlinkingAllowed() const noexcept;
|
||||
|
||||
#pragma region TextSelection
|
||||
// These methods are defined in TerminalSelection.cpp
|
||||
enum class SelectionExpansionMode
|
||||
{
|
||||
Cell,
|
||||
Word,
|
||||
Line
|
||||
};
|
||||
const bool IsCopyOnSelectActive() const noexcept;
|
||||
void DoubleClickSelection(const COORD position);
|
||||
void TripleClickSelection(const COORD position);
|
||||
void MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode);
|
||||
void SetSelectionAnchor(const COORD position);
|
||||
void SetEndSelectionPosition(const COORD position);
|
||||
void SetBoxSelection(const bool isEnabled) noexcept;
|
||||
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansionMode> newExpansionMode = std::nullopt);
|
||||
void SetBlockSelection(const bool isEnabled) noexcept;
|
||||
|
||||
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const;
|
||||
#pragma endregion
|
||||
@@ -186,19 +210,20 @@ private:
|
||||
bool _suppressApplicationTitle;
|
||||
|
||||
#pragma region Text Selection
|
||||
enum class SelectionExpansionMode
|
||||
// a selection is represented as a range between two COORDs (start and end)
|
||||
// the pivot is the COORD that remains selected when you extend a selection in any direction
|
||||
// this is particularly useful when a word selection is extended over its starting point
|
||||
// see TerminalSelection.cpp for more information
|
||||
struct SelectionAnchors
|
||||
{
|
||||
Cell,
|
||||
Word,
|
||||
Line
|
||||
COORD start;
|
||||
COORD end;
|
||||
COORD pivot;
|
||||
};
|
||||
COORD _selectionAnchor;
|
||||
COORD _endSelectionPosition;
|
||||
bool _boxSelection;
|
||||
bool _selectionActive;
|
||||
std::optional<SelectionAnchors> _selection;
|
||||
bool _blockSelection;
|
||||
bool _allowSingleCharSelection;
|
||||
bool _copyOnSelect;
|
||||
SHORT _selectionVerticalOffset;
|
||||
std::wstring _wordDelimiters;
|
||||
SelectionExpansionMode _multiClickSelectionMode;
|
||||
#pragma endregion
|
||||
@@ -247,19 +272,15 @@ private:
|
||||
#pragma region TextSelection
|
||||
// These methods are defined in TerminalSelection.cpp
|
||||
std::vector<SMALL_RECT> _GetSelectionRects() const noexcept;
|
||||
SHORT _ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const;
|
||||
SHORT _ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const;
|
||||
COORD _ExpandDoubleClickSelectionLeft(const COORD position) const;
|
||||
COORD _ExpandDoubleClickSelectionRight(const COORD position) const;
|
||||
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos) const;
|
||||
std::pair<COORD, COORD> _ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const;
|
||||
COORD _ConvertToBufferCell(const COORD viewportPos) const;
|
||||
const bool _IsSingleCellSelection() const noexcept;
|
||||
std::tuple<COORD, COORD> _PreprocessSelectionCoords() const;
|
||||
SMALL_RECT _GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const;
|
||||
void _ExpandSelectionRow(SMALL_RECT& selectionRow) const;
|
||||
#pragma endregion
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TerminalCoreUnitTests::TerminalBufferTests;
|
||||
friend class TerminalCoreUnitTests::TerminalApiTest;
|
||||
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -142,6 +142,11 @@ bool Terminal::CursorLineFeed(const bool withReturn) noexcept
|
||||
try
|
||||
{
|
||||
auto cursorPos = _buffer->GetCursor().GetPosition();
|
||||
|
||||
// since we explicitly just moved down a row, clear the wrap status on the
|
||||
// row we just came from
|
||||
_buffer->GetRowByOffset(cursorPos.Y).GetCharRow().SetWrapForced(false);
|
||||
|
||||
cursorPos.Y++;
|
||||
if (withReturn)
|
||||
{
|
||||
@@ -514,3 +519,77 @@ try
|
||||
return true;
|
||||
}
|
||||
CATCH_LOG_RETURN_FALSE()
|
||||
|
||||
bool Terminal::SetCursorKeysMode(const bool applicationMode) noexcept
|
||||
{
|
||||
_terminalInput->ChangeCursorKeysMode(applicationMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::SetKeypadMode(const bool applicationMode) noexcept
|
||||
{
|
||||
_terminalInput->ChangeKeypadMode(applicationMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableVT200MouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->EnableDefaultTracking(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableUTF8ExtendedMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->SetUtf8ExtendedMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableSGRExtendedMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->SetSGRExtendedMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableButtonEventMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->EnableButtonEventTracking(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableAnyEventMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->EnableAnyEventTracking(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableAlternateScrollMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->EnableAlternateScroll(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::IsVtInputEnabled() const noexcept
|
||||
{
|
||||
// We should never be getting this call in Terminal.
|
||||
FAIL_FAST();
|
||||
}
|
||||
|
||||
bool Terminal::SetCursorVisibility(const bool visible) noexcept
|
||||
{
|
||||
_buffer->GetCursor().SetIsVisible(visible);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableCursorBlinking(const bool enable) noexcept
|
||||
{
|
||||
_buffer->GetCursor().SetBlinkingAllowed(enable);
|
||||
|
||||
// GH#2642 - From what we've gathered from other terminals, when blinking is
|
||||
// disabled, the cursor should remain On always, and have the visibility
|
||||
// controlled by the IsVisible property. So when you do a printf "\e[?12l"
|
||||
// to disable blinking, the cursor stays stuck On. At this point, only the
|
||||
// cursor visibility property controls whether the user can see it or not.
|
||||
// (Yes, the cursor can be On and NOT Visible)
|
||||
_buffer->GetCursor().SetIsOn(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,16 @@ try
|
||||
}
|
||||
CATCH_LOG_RETURN_FALSE()
|
||||
|
||||
bool TerminalDispatch::CursorVisibility(const bool isVisible) noexcept
|
||||
{
|
||||
return _terminalApi.SetCursorVisibility(isVisible);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::EnableCursorBlinking(const bool enable) noexcept
|
||||
{
|
||||
return _terminalApi.EnableCursorBlinking(enable);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::CursorForward(const size_t distance) noexcept
|
||||
try
|
||||
{
|
||||
@@ -213,3 +223,279 @@ try
|
||||
return _terminalApi.EraseInDisplay(eraseType);
|
||||
}
|
||||
CATCH_LOG_RETURN_FALSE()
|
||||
|
||||
// - DECKPAM, DECKPNM - Sets the keypad input mode to either Application mode or Numeric mode (true, false respectively)
|
||||
// Arguments:
|
||||
// - applicationMode - set to true to enable Application Mode Input, false for Numeric Mode Input.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
|
||||
{
|
||||
_terminalApi.SetKeypadMode(fApplicationMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
// - DECCKM - Sets the cursor keys input mode to either Application mode or Normal mode (true, false respectively)
|
||||
// Arguments:
|
||||
// - applicationMode - set to true to enable Application Mode Input, false for Normal Mode Input.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::SetCursorKeysMode(const bool applicationMode) noexcept
|
||||
{
|
||||
_terminalApi.SetCursorKeysMode(applicationMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable VT200 Mouse Mode - Enables/disables the mouse input handler in default tracking mode.
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableVT200MouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableVT200MouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable UTF-8 Extended Encoding - this changes the encoding scheme for sequences
|
||||
// emitted by the mouse input handler. Does not enable/disable mouse mode on its own.
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableUTF8ExtendedMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableUTF8ExtendedMouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable SGR Extended Encoding - this changes the encoding scheme for sequences
|
||||
// emitted by the mouse input handler. Does not enable/disable mouse mode on its own.
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableSGRExtendedMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableSGRExtendedMouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable Button Event mode - send mouse move events WITH A BUTTON PRESSED to the input.
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableButtonEventMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableButtonEventMouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable Any Event mode - send all mouse events to the input.
|
||||
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableAnyEventMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableAnyEventMouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable Alternate Scroll Mode - When in the Alt Buffer, send CUP and CUD on
|
||||
// scroll up/down events instead of the usual sequences
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableAlternateScroll(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableAlternateScrollMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TerminalDispatch::SetPrivateModes(const std::basic_string_view<DispatchTypes::PrivateModeParams> params) noexcept
|
||||
{
|
||||
return _SetResetPrivateModes(params, true);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::ResetPrivateModes(const std::basic_string_view<DispatchTypes::PrivateModeParams> params) noexcept
|
||||
{
|
||||
return _SetResetPrivateModes(params, false);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Generalized handler for the setting/resetting of DECSET/DECRST parameters.
|
||||
// All params in the rgParams will attempt to be executed, even if one
|
||||
// fails, to allow us to successfully re/set params that are chained with
|
||||
// params we don't yet support.
|
||||
// Arguments:
|
||||
// - params - array of params to set/reset
|
||||
// - enable - True for set, false for unset.
|
||||
// Return Value:
|
||||
// - True if ALL params were handled successfully. False otherwise.
|
||||
bool TerminalDispatch::_SetResetPrivateModes(const std::basic_string_view<DispatchTypes::PrivateModeParams> params, const bool enable) noexcept
|
||||
{
|
||||
// because the user might chain together params we don't support with params we DO support, execute all
|
||||
// params in the sequence, and only return failure if we failed at least one of them
|
||||
size_t failures = 0;
|
||||
for (const auto& p : params)
|
||||
{
|
||||
failures += _PrivateModeParamsHelper(p, enable) ? 0 : 1; // increment the number of failures if we fail.
|
||||
}
|
||||
return failures == 0;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Support routine for routing private mode parameters to be set/reset as flags
|
||||
// Arguments:
|
||||
// - params - array of params to set/reset
|
||||
// - enable - True for set, false for unset.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateModeParams param, const bool enable) noexcept
|
||||
{
|
||||
bool success = false;
|
||||
switch (param)
|
||||
{
|
||||
case DispatchTypes::PrivateModeParams::DECCKM_CursorKeysMode:
|
||||
// set - Enable Application Mode, reset - Normal mode
|
||||
success = SetCursorKeysMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::VT200_MOUSE_MODE:
|
||||
success = EnableVT200MouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::BUTTON_EVENT_MOUSE_MODE:
|
||||
success = EnableButtonEventMouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::ANY_EVENT_MOUSE_MODE:
|
||||
success = EnableAnyEventMouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::UTF8_EXTENDED_MODE:
|
||||
success = EnableUTF8ExtendedMouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::SGR_EXTENDED_MODE:
|
||||
success = EnableSGRExtendedMouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::ALTERNATE_SCROLL:
|
||||
success = EnableAlternateScroll(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::DECTCEM_TextCursorEnableMode:
|
||||
success = CursorVisibility(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::ATT610_StartCursorBlink:
|
||||
success = EnableCursorBlinking(enable);
|
||||
break;
|
||||
default:
|
||||
// If no functions to call, overall dispatch was a failure.
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TerminalDispatch::SoftReset() noexcept
|
||||
{
|
||||
// TODO:GH#1883 much of this method is not yet implemented in the Terminal,
|
||||
// because the Terminal _doesn't need to_ yet. The terminal is only ever
|
||||
// connected to conpty, so it doesn't implement most of these things that
|
||||
// Hard/Soft Reset would reset. As those things are implemented, they should
|
||||
|
||||
// also get cleared here.
|
||||
//
|
||||
// This code is left here (from its original form in conhost) as a reminder
|
||||
// of what needs to be done.
|
||||
|
||||
bool success = CursorVisibility(true); // Cursor enabled.
|
||||
// if (success)
|
||||
// {
|
||||
// success = SetOriginMode(false); // Absolute cursor addressing.
|
||||
// }
|
||||
// if (success)
|
||||
// {
|
||||
// success = SetAutoWrapMode(true); // Wrap at end of line.
|
||||
// }
|
||||
if (success)
|
||||
{
|
||||
success = SetCursorKeysMode(false); // Normal characters.
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
success = SetKeypadMode(false); // Numeric characters.
|
||||
}
|
||||
// if (success)
|
||||
// {
|
||||
// // Top margin = 1; bottom margin = page length.
|
||||
// success = _DoSetTopBottomScrollingMargins(0, 0);
|
||||
// }
|
||||
// if (success)
|
||||
// {
|
||||
// success = DesignateCharset(DispatchTypes::VTCharacterSets::USASCII); // Default Charset
|
||||
// }
|
||||
if (success)
|
||||
{
|
||||
const auto opt = DispatchTypes::GraphicsOptions::Off;
|
||||
success = SetGraphicsRendition({ &opt, 1 }); // Normal rendition.
|
||||
}
|
||||
// if (success)
|
||||
// {
|
||||
// // Reset the saved cursor state.
|
||||
// // Note that XTerm only resets the main buffer state, but that
|
||||
// // seems likely to be a bug. Most other terminals reset both.
|
||||
// _savedCursorState.at(0) = {}; // Main buffer
|
||||
// _savedCursorState.at(1) = {}; // Alt buffer
|
||||
// }
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TerminalDispatch::HardReset() noexcept
|
||||
{
|
||||
// TODO:GH#1883 much of this method is not yet implemented in the Terminal,
|
||||
// because the Terminal _doesn't need to_ yet. The terminal is only ever
|
||||
// connected to conpty, so it doesn't implement most of these things that
|
||||
// Hard/Soft Reset would reset. As those things ar implemented, they should
|
||||
// also get cleared here.
|
||||
//
|
||||
// This code is left here (from its original form in conhost) as a reminder
|
||||
// of what needs to be done.
|
||||
|
||||
// Sets the SGR state to normal - this must be done before EraseInDisplay
|
||||
// to ensure that it clears with the default background color.
|
||||
bool success = SoftReset();
|
||||
|
||||
// Clears the screen - Needs to be done in two operations.
|
||||
if (success)
|
||||
{
|
||||
success = EraseInDisplay(DispatchTypes::EraseType::All);
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
success = EraseInDisplay(DispatchTypes::EraseType::Scrollback);
|
||||
}
|
||||
|
||||
// // Set the DECSCNM screen mode back to normal.
|
||||
// if (success)
|
||||
// {
|
||||
// success = SetScreenMode(false);
|
||||
// }
|
||||
|
||||
// Cursor to 1,1 - the Soft Reset guarantees this is absolute
|
||||
if (success)
|
||||
{
|
||||
success = CursorPosition(1, 1);
|
||||
}
|
||||
|
||||
// // delete all current tab stops and reapply
|
||||
// _pConApi->PrivateSetDefaultTabStops();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ public:
|
||||
bool CursorPosition(const size_t line,
|
||||
const size_t column) noexcept override; // CUP
|
||||
|
||||
bool CursorVisibility(const bool isVisible) noexcept override; // DECTCEM
|
||||
bool EnableCursorBlinking(const bool enable) noexcept override; // ATT610
|
||||
|
||||
bool CursorForward(const size_t distance) noexcept override;
|
||||
bool CursorBackward(const size_t distance) noexcept override;
|
||||
bool CursorUp(const size_t distance) noexcept override;
|
||||
@@ -38,6 +41,22 @@ public:
|
||||
bool InsertCharacter(const size_t count) noexcept override;
|
||||
bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) noexcept override;
|
||||
|
||||
bool SetCursorKeysMode(const bool applicationMode) noexcept override; // DECCKM
|
||||
bool SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM
|
||||
|
||||
bool SoftReset() noexcept override; // DECSTR
|
||||
bool HardReset() noexcept override; // RIS
|
||||
|
||||
bool EnableVT200MouseMode(const bool enabled) noexcept override; // ?1000
|
||||
bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept override; // ?1005
|
||||
bool EnableSGRExtendedMouseMode(const bool enabled) noexcept override; // ?1006
|
||||
bool EnableButtonEventMouseMode(const bool enabled) noexcept override; // ?1002
|
||||
bool EnableAnyEventMouseMode(const bool enabled) noexcept override; // ?1003
|
||||
bool EnableAlternateScroll(const bool enabled) noexcept override; // ?1007
|
||||
|
||||
bool SetPrivateModes(const std::basic_string_view<::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECSET
|
||||
bool ResetPrivateModes(const std::basic_string_view<::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECRST
|
||||
|
||||
private:
|
||||
::Microsoft::Terminal::Core::ITerminalApi& _terminalApi;
|
||||
|
||||
@@ -46,4 +65,7 @@ private:
|
||||
bool _SetBoldColorHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions opt) noexcept;
|
||||
bool _SetDefaultColorHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions opt) noexcept;
|
||||
void _SetGraphicsOptionHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions opt) noexcept;
|
||||
|
||||
bool _SetResetPrivateModes(const std::basic_string_view<::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> params, const bool enable) noexcept;
|
||||
bool _PrivateModeParamsHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams param, const bool enable) noexcept;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,38 @@
|
||||
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
|
||||
/* Selection Pivot Description:
|
||||
* The pivot helps properly update the selection when a user moves a selection over itself
|
||||
* See SelectionTest::DoubleClickDrag_Left for an example of the functionality mentioned here
|
||||
* As an example, consider the following scenario...
|
||||
* 1. Perform a word selection (double-click) on a word
|
||||
*
|
||||
* |-position where we double-clicked
|
||||
* _|_
|
||||
* |word|
|
||||
* |--|
|
||||
* start & pivot-| |-end
|
||||
*
|
||||
* 2. Drag your mouse down a line
|
||||
*
|
||||
*
|
||||
* start & pivot-|__________
|
||||
* __|word_______|
|
||||
* |______|
|
||||
* |
|
||||
* |-end & mouse position
|
||||
*
|
||||
* 3. Drag your mouse up two lines
|
||||
*
|
||||
* |-start & mouse position
|
||||
* |________
|
||||
* ____| ______|
|
||||
* |___w|ord
|
||||
* |-end & pivot
|
||||
*
|
||||
* The pivot never moves until a new selection is created. It ensures that that cell will always be selected.
|
||||
*/
|
||||
|
||||
// Method Description:
|
||||
// - Helper to determine the selected region of the buffer. Used for rendering.
|
||||
// Return Value:
|
||||
@@ -22,183 +54,32 @@ std::vector<SMALL_RECT> Terminal::_GetSelectionRects() const noexcept
|
||||
|
||||
try
|
||||
{
|
||||
// NOTE: (0,0) is the top-left of the screen
|
||||
// the physically "higher" coordinate is closer to the top-left
|
||||
// the physically "lower" coordinate is closer to the bottom-right
|
||||
const auto [higherCoord, lowerCoord] = _PreprocessSelectionCoords();
|
||||
|
||||
SHORT selectionRectSize;
|
||||
THROW_IF_FAILED(ShortSub(lowerCoord.Y, higherCoord.Y, &selectionRectSize));
|
||||
THROW_IF_FAILED(ShortAdd(selectionRectSize, 1, &selectionRectSize));
|
||||
|
||||
std::vector<SMALL_RECT> selectionArea;
|
||||
selectionArea.reserve(selectionRectSize);
|
||||
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
|
||||
{
|
||||
SMALL_RECT selectionRow = _GetSelectionRow(row, higherCoord, lowerCoord);
|
||||
_ExpandSelectionRow(selectionRow);
|
||||
selectionArea.emplace_back(selectionRow);
|
||||
}
|
||||
result.swap(selectionArea);
|
||||
return _buffer->GetTextRects(_selection->start, _selection->end, _blockSelection);
|
||||
}
|
||||
CATCH_LOG();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - convert selection anchors to proper coordinates for rendering
|
||||
// NOTE: (0,0) is top-left so vertical comparison is inverted
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - tuple.first: the physically "higher" coordinate (closer to the top-left)
|
||||
// - tuple.second: the physically "lower" coordinate (closer to the bottom-right)
|
||||
std::tuple<COORD, COORD> Terminal::_PreprocessSelectionCoords() const
|
||||
{
|
||||
// create these new anchors for comparison and rendering
|
||||
COORD selectionAnchorWithOffset{ _selectionAnchor };
|
||||
COORD endSelectionPositionWithOffset{ _endSelectionPosition };
|
||||
|
||||
// Add anchor offset here to update properly on new buffer output
|
||||
THROW_IF_FAILED(ShortAdd(selectionAnchorWithOffset.Y, _selectionVerticalOffset, &selectionAnchorWithOffset.Y));
|
||||
THROW_IF_FAILED(ShortAdd(endSelectionPositionWithOffset.Y, _selectionVerticalOffset, &endSelectionPositionWithOffset.Y));
|
||||
|
||||
// clamp anchors to be within buffer bounds
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
bufferSize.Clamp(selectionAnchorWithOffset);
|
||||
bufferSize.Clamp(endSelectionPositionWithOffset);
|
||||
|
||||
// NOTE: (0,0) is top-left so vertical comparison is inverted
|
||||
// CompareInBounds returns whether A is to the left of (rv<0), equal to (rv==0), or to the right of (rv>0) B.
|
||||
// Here, we want the "left"most coordinate to be the one "higher" on the screen. The other gets the dubious honor of
|
||||
// being the "lower."
|
||||
return bufferSize.CompareInBounds(selectionAnchorWithOffset, endSelectionPositionWithOffset) <= 0 ?
|
||||
std::make_tuple(selectionAnchorWithOffset, endSelectionPositionWithOffset) :
|
||||
std::make_tuple(endSelectionPositionWithOffset, selectionAnchorWithOffset);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - constructs the selection row at the given row
|
||||
// NOTE: (0,0) is top-left so vertical comparison is inverted
|
||||
// Arguments:
|
||||
// - row: the buffer y-value under observation
|
||||
// - higherCoord: the physically "higher" coordinate (closer to the top-left)
|
||||
// - lowerCoord: the physically "lower" coordinate (closer to the bottom-right)
|
||||
// Return Value:
|
||||
// - the selection row needed for rendering
|
||||
SMALL_RECT Terminal::_GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const
|
||||
{
|
||||
SMALL_RECT selectionRow;
|
||||
|
||||
selectionRow.Top = row;
|
||||
selectionRow.Bottom = row;
|
||||
|
||||
if (_boxSelection || higherCoord.Y == lowerCoord.Y)
|
||||
{
|
||||
selectionRow.Left = std::min(higherCoord.X, lowerCoord.X);
|
||||
selectionRow.Right = std::max(higherCoord.X, lowerCoord.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectionRow.Left = (row == higherCoord.Y) ? higherCoord.X : _buffer->GetSize().Left();
|
||||
selectionRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : _buffer->GetSize().RightInclusive();
|
||||
}
|
||||
|
||||
return selectionRow;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the current anchor position relative to the whole text buffer
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - None
|
||||
const COORD Terminal::GetSelectionAnchor() const
|
||||
const COORD Terminal::GetSelectionAnchor() const noexcept
|
||||
{
|
||||
COORD selectionAnchorPos{ _selectionAnchor };
|
||||
THROW_IF_FAILED(ShortAdd(selectionAnchorPos.Y, _selectionVerticalOffset, &selectionAnchorPos.Y));
|
||||
|
||||
return selectionAnchorPos;
|
||||
return _selection->start;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Expand the selection row according to selection mode and wide glyphs
|
||||
// - this is particularly useful for box selections (ALT + selection)
|
||||
// - Get the current end anchor position relative to the whole text buffer
|
||||
// Arguments:
|
||||
// - selectionRow: the selection row to be expanded
|
||||
// - None
|
||||
// Return Value:
|
||||
// - modifies selectionRow's Left and Right values to expand properly
|
||||
void Terminal::_ExpandSelectionRow(SMALL_RECT& selectionRow) const
|
||||
// - None
|
||||
const COORD Terminal::GetSelectionEnd() const noexcept
|
||||
{
|
||||
const auto row = selectionRow.Top;
|
||||
|
||||
// expand selection for Double/Triple Click
|
||||
if (_multiClickSelectionMode == SelectionExpansionMode::Word)
|
||||
{
|
||||
selectionRow.Left = _ExpandDoubleClickSelectionLeft({ selectionRow.Left, row }).X;
|
||||
selectionRow.Right = _ExpandDoubleClickSelectionRight({ selectionRow.Right, row }).X;
|
||||
}
|
||||
else if (_multiClickSelectionMode == SelectionExpansionMode::Line)
|
||||
{
|
||||
selectionRow.Left = _buffer->GetSize().Left();
|
||||
selectionRow.Right = _buffer->GetSize().RightInclusive();
|
||||
}
|
||||
|
||||
// expand selection for Wide Glyphs
|
||||
selectionRow.Left = _ExpandWideGlyphSelectionLeft(selectionRow.Left, row);
|
||||
selectionRow.Right = _ExpandWideGlyphSelectionRight(selectionRow.Right, row);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Expands the selection left-wards to cover a wide glyph, if necessary
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
// Return Value:
|
||||
// - updated x position to encapsulate the wide glyph
|
||||
SHORT Terminal::_ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const
|
||||
{
|
||||
// don't change the value if at/outside the boundary
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
if (xPos <= bufferSize.Left() || xPos > bufferSize.RightInclusive())
|
||||
{
|
||||
return xPos;
|
||||
}
|
||||
|
||||
COORD position{ xPos, yPos };
|
||||
const auto attr = _buffer->GetCellDataAt(position)->DbcsAttr();
|
||||
if (attr.IsTrailing())
|
||||
{
|
||||
// move off by highlighting the lead half too.
|
||||
// alters position.X
|
||||
bufferSize.DecrementInBounds(position);
|
||||
}
|
||||
return position.X;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Expands the selection right-wards to cover a wide glyph, if necessary
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
// Return Value:
|
||||
// - updated x position to encapsulate the wide glyph
|
||||
SHORT Terminal::_ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const
|
||||
{
|
||||
// don't change the value if at/outside the boundary
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
if (xPos < bufferSize.Left() || xPos >= bufferSize.RightInclusive())
|
||||
{
|
||||
return xPos;
|
||||
}
|
||||
|
||||
COORD position{ xPos, yPos };
|
||||
const auto attr = _buffer->GetCellDataAt(position)->DbcsAttr();
|
||||
if (attr.IsLeading())
|
||||
{
|
||||
// move off by highlighting the trailing half too.
|
||||
// alters position.X
|
||||
bufferSize.IncrementInBounds(position);
|
||||
}
|
||||
return position.X;
|
||||
return _selection->end;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -207,7 +88,7 @@ SHORT Terminal::_ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPo
|
||||
// - bool representing if selection is only a single cell. Used for copyOnSelect
|
||||
const bool Terminal::_IsSingleCellSelection() const noexcept
|
||||
{
|
||||
return (_selectionAnchor == _endSelectionPosition);
|
||||
return (_selection->start == _selection->end);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -222,7 +103,12 @@ const bool Terminal::IsSelectionActive() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return _selectionActive;
|
||||
return _selection.has_value();
|
||||
}
|
||||
|
||||
const bool Terminal::IsBlockSelection() const noexcept
|
||||
{
|
||||
return _blockSelection;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -235,79 +121,60 @@ const bool Terminal::IsCopyOnSelectActive() const noexcept
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Select the sequence between delimiters defined in Settings
|
||||
// - Perform a multi-click selection at viewportPos expanding according to the expansionMode
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
void Terminal::DoubleClickSelection(const COORD position)
|
||||
// - viewportPos: the (x,y) coordinate on the visible viewport
|
||||
// - expansionMode: the SelectionExpansionMode to dictate the boundaries of the selection anchors
|
||||
void Terminal::MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode)
|
||||
{
|
||||
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
|
||||
COORD positionWithOffsets = _ConvertToBufferCell(position);
|
||||
// set the selection pivot to expand the selection using SetSelectionEnd()
|
||||
_selection = SelectionAnchors{};
|
||||
_selection->pivot = _ConvertToBufferCell(viewportPos);
|
||||
|
||||
// scan leftwards until delimiter is found and
|
||||
// set selection anchor to one right of that spot
|
||||
_selectionAnchor = _ExpandDoubleClickSelectionLeft(positionWithOffsets);
|
||||
THROW_IF_FAILED(ShortSub(_selectionAnchor.Y, gsl::narrow<SHORT>(ViewStartIndex()), &_selectionAnchor.Y));
|
||||
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
|
||||
_multiClickSelectionMode = expansionMode;
|
||||
SetSelectionEnd(viewportPos);
|
||||
|
||||
// scan rightwards until delimiter is found and
|
||||
// set endSelectionPosition to one left of that spot
|
||||
_endSelectionPosition = _ExpandDoubleClickSelectionRight(positionWithOffsets);
|
||||
THROW_IF_FAILED(ShortSub(_endSelectionPosition.Y, gsl::narrow<SHORT>(ViewStartIndex()), &_endSelectionPosition.Y));
|
||||
|
||||
_selectionActive = true;
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Word;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Select the entire row of the position clicked
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
void Terminal::TripleClickSelection(const COORD position)
|
||||
{
|
||||
SetSelectionAnchor({ 0, position.Y });
|
||||
SetEndSelectionPosition({ _buffer->GetSize().RightInclusive(), position.Y });
|
||||
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Line;
|
||||
// we need to set the _selectionPivot again
|
||||
// for future shift+clicks
|
||||
_selection->pivot = _selection->start;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Record the position of the beginning of a selection
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
void Terminal::SetSelectionAnchor(const COORD position)
|
||||
void Terminal::SetSelectionAnchor(const COORD viewportPos)
|
||||
{
|
||||
_selectionAnchor = position;
|
||||
_selection = SelectionAnchors{};
|
||||
_selection->pivot = _ConvertToBufferCell(viewportPos);
|
||||
|
||||
// include _scrollOffset here to ensure this maps to the right spot of the original viewport
|
||||
THROW_IF_FAILED(ShortSub(_selectionAnchor.Y, gsl::narrow<SHORT>(_scrollOffset), &_selectionAnchor.Y));
|
||||
|
||||
// copy value of ViewStartIndex to support scrolling
|
||||
// and update on new buffer output (used in _GetSelectionRects())
|
||||
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
|
||||
|
||||
_selectionActive = true;
|
||||
_allowSingleCharSelection = (_copyOnSelect) ? false : true;
|
||||
|
||||
SetEndSelectionPosition(position);
|
||||
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Cell;
|
||||
SetSelectionEnd(viewportPos);
|
||||
|
||||
_selection->start = _selection->pivot;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Record the position of the end of a selection
|
||||
// - Update selection anchors when dragging to a position
|
||||
// - based on the selection expansion mode
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
void Terminal::SetEndSelectionPosition(const COORD position)
|
||||
// - viewportPos: the (x,y) coordinate on the visible viewport
|
||||
// - newExpansionMode: overwrites the _multiClickSelectionMode for this function call. Used for ShiftClick
|
||||
void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional<SelectionExpansionMode> newExpansionMode)
|
||||
{
|
||||
_endSelectionPosition = position;
|
||||
const auto textBufferPos = _ConvertToBufferCell(viewportPos);
|
||||
|
||||
// include _scrollOffset here to ensure this maps to the right spot of the original viewport
|
||||
THROW_IF_FAILED(ShortSub(_endSelectionPosition.Y, gsl::narrow<SHORT>(_scrollOffset), &_endSelectionPosition.Y));
|
||||
// if this is a shiftClick action, we need to overwrite the _multiClickSelectionMode value (even if it's the same)
|
||||
// Otherwise, we may accidentally expand during other selection-based actions
|
||||
_multiClickSelectionMode = newExpansionMode.has_value() ? *newExpansionMode : _multiClickSelectionMode;
|
||||
|
||||
// copy value of ViewStartIndex to support scrolling
|
||||
// and update on new buffer output (used in _GetSelectionRects())
|
||||
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
|
||||
const auto anchors = _PivotSelection(textBufferPos);
|
||||
std::tie(_selection->start, _selection->end) = _ExpandSelectionAnchors(anchors);
|
||||
|
||||
// moving the endpoint of what used to be a single cell selection
|
||||
// allows the user to drag back and select just one cell
|
||||
if (_copyOnSelect && !_IsSingleCellSelection())
|
||||
{
|
||||
_allowSingleCharSelection = true;
|
||||
@@ -315,78 +182,94 @@ void Terminal::SetEndSelectionPosition(const COORD position)
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - enable/disable box selection (ALT + selection)
|
||||
// - returns a new pair of selection anchors for selecting around the pivot
|
||||
// - This ensures start < end when compared
|
||||
// Arguments:
|
||||
// - isEnabled: new value for _boxSelection
|
||||
void Terminal::SetBoxSelection(const bool isEnabled) noexcept
|
||||
// - targetPos: the (x,y) coordinate we are moving to on the text buffer
|
||||
// Return Value:
|
||||
// - the new start/end for a selection
|
||||
std::pair<COORD, COORD> Terminal::_PivotSelection(const COORD targetPos) const
|
||||
{
|
||||
_boxSelection = isEnabled;
|
||||
if (_buffer->GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0)
|
||||
{
|
||||
// target is before pivot
|
||||
// treat target as start
|
||||
return std::make_pair(targetPos, _selection->pivot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// target is after pivot
|
||||
// treat pivot as start
|
||||
return std::make_pair(_selection->pivot, targetPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update the selection anchors to expand according to the expansion mode
|
||||
// Arguments:
|
||||
// - anchors: a pair of selection anchors representing a desired selection
|
||||
// Return Value:
|
||||
// - the new start/end for a selection
|
||||
std::pair<COORD, COORD> Terminal::_ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const
|
||||
{
|
||||
COORD start = anchors.first;
|
||||
COORD end = anchors.second;
|
||||
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
switch (_multiClickSelectionMode)
|
||||
{
|
||||
case SelectionExpansionMode::Line:
|
||||
start = { bufferSize.Left(), start.Y };
|
||||
end = { bufferSize.RightInclusive(), end.Y };
|
||||
break;
|
||||
case SelectionExpansionMode::Word:
|
||||
start = _buffer->GetWordStart(start, _wordDelimiters);
|
||||
end = _buffer->GetWordEnd(end, _wordDelimiters);
|
||||
break;
|
||||
case SelectionExpansionMode::Cell:
|
||||
default:
|
||||
// no expansion is necessary
|
||||
break;
|
||||
}
|
||||
return std::make_pair(start, end);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - enable/disable block selection (ALT + selection)
|
||||
// Arguments:
|
||||
// - isEnabled: new value for _blockSelection
|
||||
void Terminal::SetBlockSelection(const bool isEnabled) noexcept
|
||||
{
|
||||
_blockSelection = isEnabled;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - clear selection data and disable rendering it
|
||||
#pragma warning(disable : 26440) // changing this to noexcept would require a change to ConHost's selection model
|
||||
void Terminal::ClearSelection()
|
||||
{
|
||||
_selectionActive = false;
|
||||
_allowSingleCharSelection = false;
|
||||
_selectionAnchor = { 0, 0 };
|
||||
_endSelectionPosition = { 0, 0 };
|
||||
_selectionVerticalOffset = 0;
|
||||
|
||||
_buffer->GetRenderTarget().TriggerSelection();
|
||||
_selection = std::nullopt;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get wstring text from highlighted portion of text buffer
|
||||
// Arguments:
|
||||
// - trimTrailingWhitespace: enable removing any whitespace from copied selection
|
||||
// and get text to appear on separate lines.
|
||||
// - collapseText: collapse all of the text to one line
|
||||
// Return Value:
|
||||
// - wstring text from buffer. If extended to multiple lines, each line is separated by \r\n
|
||||
const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const
|
||||
const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool collapseText) const
|
||||
{
|
||||
const auto selectionRects = _GetSelectionRects();
|
||||
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor = std::bind(&Terminal::GetForegroundColor, this, std::placeholders::_1);
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor = std::bind(&Terminal::GetBackgroundColor, this, std::placeholders::_1);
|
||||
|
||||
return _buffer->GetTextForClipboard(!_boxSelection,
|
||||
trimTrailingWhitespace,
|
||||
_GetSelectionRects(),
|
||||
GetForegroundColor,
|
||||
GetBackgroundColor);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - expand the double click selection to the left
|
||||
// - stopped by delimiter if started on delimiter
|
||||
// Arguments:
|
||||
// - position: buffer coordinate for selection
|
||||
// Return Value:
|
||||
// - updated copy of "position" to new expanded location (with vertical offset)
|
||||
COORD Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) const
|
||||
{
|
||||
// force position to be within bounds
|
||||
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
|
||||
COORD positionWithOffsets = position;
|
||||
_buffer->GetSize().Clamp(positionWithOffsets);
|
||||
|
||||
return _buffer->GetWordStart(positionWithOffsets, _wordDelimiters);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - expand the double click selection to the right
|
||||
// - stopped by delimiter if started on delimiter
|
||||
// Arguments:
|
||||
// - position: buffer coordinate for selection
|
||||
// Return Value:
|
||||
// - updated copy of "position" to new expanded location (with vertical offset)
|
||||
COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const
|
||||
{
|
||||
// force position to be within bounds
|
||||
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
|
||||
COORD positionWithOffsets = position;
|
||||
_buffer->GetSize().Clamp(positionWithOffsets);
|
||||
|
||||
return _buffer->GetWordEnd(positionWithOffsets, _wordDelimiters);
|
||||
return _buffer->GetText(!collapseText,
|
||||
!collapseText,
|
||||
selectionRects,
|
||||
GetForegroundColor,
|
||||
GetBackgroundColor);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -397,13 +280,10 @@ COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const
|
||||
// - the corresponding location on the buffer
|
||||
COORD Terminal::_ConvertToBufferCell(const COORD viewportPos) const
|
||||
{
|
||||
// Force position to be valid
|
||||
COORD positionWithOffsets = viewportPos;
|
||||
_buffer->GetSize().Clamp(positionWithOffsets);
|
||||
|
||||
THROW_IF_FAILED(ShortSub(viewportPos.Y, gsl::narrow<SHORT>(_scrollOffset), &positionWithOffsets.Y));
|
||||
THROW_IF_FAILED(ShortAdd(positionWithOffsets.Y, gsl::narrow<SHORT>(ViewStartIndex()), &positionWithOffsets.Y));
|
||||
return positionWithOffsets;
|
||||
const auto yPos = base::ClampedNumeric<short>(_VisibleStartIndex()) + viewportPos.Y;
|
||||
COORD bufferPos = { viewportPos.X, yPos };
|
||||
_buffer->GetSize().Clamp(bufferPos);
|
||||
return bufferPos;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -173,8 +173,7 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
|
||||
realCoordEnd.Y -= gsl::narrow<short>(_VisibleStartIndex());
|
||||
|
||||
SetSelectionAnchor(realCoordStart);
|
||||
SetEndSelectionPosition(realCoordEnd);
|
||||
_buffer->GetRenderTarget().TriggerSelection();
|
||||
SetSelectionEnd(realCoordEnd, SelectionExpansionMode::Cell);
|
||||
}
|
||||
|
||||
const std::wstring Terminal::GetConsoleTitle() const noexcept
|
||||
@@ -205,3 +204,13 @@ void Terminal::UnlockConsole() noexcept
|
||||
{
|
||||
_readWriteLock.unlock_shared();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns whether the screen is inverted;
|
||||
// This state is not currently known to Terminal.
|
||||
// Return Value:
|
||||
// - false.
|
||||
bool Terminal::IsScreenReversed() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,13 @@ namespace Microsoft.Terminal.Settings
|
||||
Hidden
|
||||
};
|
||||
|
||||
enum TextAntialiasingMode
|
||||
{
|
||||
Grayscale = 0,
|
||||
Cleartype,
|
||||
Aliased
|
||||
};
|
||||
|
||||
// Class Description:
|
||||
// TerminalSettings encapsulates all settings that control the
|
||||
// TermControl's behavior. In these settings there is both the entirety
|
||||
@@ -19,6 +26,8 @@ namespace Microsoft.Terminal.Settings
|
||||
// for specifically the control.
|
||||
interface IControlSettings requires Microsoft.Terminal.Settings.ICoreSettings
|
||||
{
|
||||
String ProfileName;
|
||||
|
||||
Boolean UseAcrylic;
|
||||
Double TintOpacity;
|
||||
ScrollbarState ScrollState;
|
||||
@@ -41,5 +50,7 @@ namespace Microsoft.Terminal.Settings
|
||||
|
||||
UInt32 SelectionBackground;
|
||||
Boolean RetroTerminalEffect;
|
||||
|
||||
TextAntialiasingMode AntialiasingMode;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },
|
||||
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||
_copyOnSelect{ false },
|
||||
_profileName{},
|
||||
_useAcrylic{ false },
|
||||
_tintOpacity{ 0.5 },
|
||||
_padding{ DEFAULT_PADDING },
|
||||
@@ -39,7 +40,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_backgroundImageHorizontalAlignment{ winrt::Windows::UI::Xaml::HorizontalAlignment::Center },
|
||||
_backgroundImageVerticalAlignment{ winrt::Windows::UI::Xaml::VerticalAlignment::Center },
|
||||
_keyBindings{ nullptr },
|
||||
_scrollbarState{ ScrollbarState::Visible }
|
||||
_scrollbarState{ ScrollbarState::Visible },
|
||||
_antialiasingMode{ TextAntialiasingMode::Grayscale }
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
@@ -194,6 +197,16 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_copyOnSelect = value;
|
||||
}
|
||||
|
||||
void TerminalSettings::ProfileName(hstring const& value)
|
||||
{
|
||||
_profileName = value;
|
||||
}
|
||||
|
||||
hstring TerminalSettings::ProfileName()
|
||||
{
|
||||
return _profileName;
|
||||
}
|
||||
|
||||
bool TerminalSettings::UseAcrylic() noexcept
|
||||
{
|
||||
return _useAcrylic;
|
||||
@@ -374,4 +387,14 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_retroTerminalEffect = value;
|
||||
}
|
||||
|
||||
Settings::TextAntialiasingMode TerminalSettings::AntialiasingMode() const noexcept
|
||||
{
|
||||
return _antialiasingMode;
|
||||
}
|
||||
|
||||
void TerminalSettings::AntialiasingMode(const Settings::TextAntialiasingMode& value) noexcept
|
||||
{
|
||||
_antialiasingMode = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
void CopyOnSelect(bool value) noexcept;
|
||||
// ------------------------ End of Core Settings -----------------------
|
||||
|
||||
hstring ProfileName();
|
||||
void ProfileName(hstring const& value);
|
||||
bool UseAcrylic() noexcept;
|
||||
void UseAcrylic(bool value) noexcept;
|
||||
double TintOpacity() noexcept;
|
||||
@@ -102,6 +104,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
bool RetroTerminalEffect() noexcept;
|
||||
void RetroTerminalEffect(bool value) noexcept;
|
||||
|
||||
TextAntialiasingMode AntialiasingMode() const noexcept;
|
||||
void AntialiasingMode(winrt::Microsoft::Terminal::Settings::TextAntialiasingMode const& value) noexcept;
|
||||
|
||||
private:
|
||||
uint32_t _defaultForeground;
|
||||
uint32_t _defaultBackground;
|
||||
@@ -117,6 +122,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
uint32_t _cursorHeight;
|
||||
hstring _wordDelimiters;
|
||||
|
||||
hstring _profileName;
|
||||
bool _useAcrylic;
|
||||
double _tintOpacity;
|
||||
hstring _fontFace;
|
||||
@@ -137,6 +143,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
Settings::ScrollbarState _scrollbarState;
|
||||
|
||||
bool _retroTerminalEffect;
|
||||
|
||||
Settings::TextAntialiasingMode _antialiasingMode;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ using namespace Microsoft::Terminal::Core;
|
||||
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class TerminalBufferTests;
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
using namespace TerminalCoreUnitTests;
|
||||
|
||||
@@ -115,6 +115,9 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
auto pfn = std::bind(&ConptyRoundtripTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
_pVtRenderEngine->SetTestCallback(pfn);
|
||||
|
||||
// Enable the resize quirk, as the Terminal is going to be reacting as if it's enabled.
|
||||
_pVtRenderEngine->SetResizeQuirk(true);
|
||||
|
||||
// Configure the OutputStateMachine's _pfnFlushToTerminal
|
||||
// Use OutputStateMachineEngine::SetTerminalConnection
|
||||
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
|
||||
@@ -152,10 +155,22 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
TEST_METHOD(SimpleWriteOutputTest);
|
||||
TEST_METHOD(WriteTwoLinesUsesNewline);
|
||||
TEST_METHOD(WriteAFewSimpleLines);
|
||||
|
||||
TEST_METHOD(PassthroughClearScrollback);
|
||||
|
||||
TEST_METHOD(PassthroughHardReset);
|
||||
|
||||
TEST_METHOD(PassthroughCursorShapeImmediately);
|
||||
|
||||
TEST_METHOD(TestWrappingALongString);
|
||||
TEST_METHOD(TestAdvancedWrapping);
|
||||
TEST_METHOD(TestExactWrappingWithoutSpaces);
|
||||
TEST_METHOD(TestExactWrappingWithSpaces);
|
||||
|
||||
TEST_METHOD(MoveCursorAtEOL);
|
||||
|
||||
TEST_METHOD(TestResizeHeight);
|
||||
|
||||
private:
|
||||
bool _writeCallback(const char* const pch, size_t const cch);
|
||||
void _flushFirstFrame();
|
||||
@@ -348,6 +363,251 @@ void ConptyRoundtripTests::WriteAFewSimpleLines()
|
||||
verifyData(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestWrappingALongString()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
_checkConptyOutput = false;
|
||||
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
|
||||
VERIFY_ARE_EQUAL(100, charsToWrite);
|
||||
|
||||
VERIFY_ARE_EQUAL(0, initialTermView.Top());
|
||||
VERIFY_ARE_EQUAL(32, initialTermView.BottomExclusive());
|
||||
|
||||
hostSm.ProcessString(TestUtils::Test100CharsString);
|
||||
|
||||
const auto secondView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(0, secondView.Top());
|
||||
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(charsToWrite % initialTermView.Width(), cursor.GetPosition().X);
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
|
||||
// Verify that we marked the 0th row as _wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb);
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestAdvancedWrapping()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
|
||||
VERIFY_ARE_EQUAL(100, charsToWrite);
|
||||
|
||||
hostSm.ProcessString(TestUtils::Test100CharsString);
|
||||
hostSm.ProcessString(L"\n");
|
||||
hostSm.ProcessString(L" ");
|
||||
hostSm.ProcessString(L"1234567890");
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(2, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);
|
||||
|
||||
// Verify that we marked the 0th row as _wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 });
|
||||
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 2 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb);
|
||||
|
||||
// First write the first 80 characters from the string
|
||||
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
|
||||
// Without line breaking, write the remaining 20 chars
|
||||
expectedOutput.push_back(R"(qrstuvwxyz{|}~!"#$%&)");
|
||||
// Clear the rest of row 1
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
// This is the hard line break
|
||||
expectedOutput.push_back("\r\n");
|
||||
// Now write row 2 of the buffer
|
||||
expectedOutput.push_back(" 1234567890");
|
||||
// and clear everything after the text, because the buffer is empty.
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
|
||||
{
|
||||
// This test (and TestExactWrappingWitSpaces) reveals a bug in the old
|
||||
// implementation.
|
||||
//
|
||||
// If a line _exactly_ wraps to the next line, we can't tell if the line
|
||||
// should really wrap, or manually break. The client app is writing a line
|
||||
// that's exactly the width of the buffer that manually linebreaked at the
|
||||
// end of the line, followed by another line.
|
||||
//
|
||||
// With the old PaintBufferLine interface, there's no way to know if this
|
||||
// case is because the line wrapped or not. Hence, the addition of the
|
||||
// `lineWrapped` parameter
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
const auto charsToWrite = initialTermView.Width();
|
||||
VERIFY_ARE_EQUAL(80, charsToWrite);
|
||||
|
||||
for (auto i = 0; i < charsToWrite; i++)
|
||||
{
|
||||
// This is a handy way of just printing the printable characters that
|
||||
// _aren't_ the space character.
|
||||
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
|
||||
hostSm.ProcessCharacter(wch);
|
||||
}
|
||||
|
||||
hostSm.ProcessString(L"\n");
|
||||
hostSm.ProcessString(L"1234567890");
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(10, cursor.GetPosition().X);
|
||||
|
||||
// Verify that we marked the 0th row as _not wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_IS_FALSE(row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 });
|
||||
TestUtils::VerifyExpectedString(tb, L"1234567890", { 0, 1 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb);
|
||||
|
||||
// First write the first 80 characters from the string
|
||||
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
|
||||
|
||||
// This is the hard line break
|
||||
expectedOutput.push_back("\r\n");
|
||||
// Now write row 2 of the buffer
|
||||
expectedOutput.push_back("1234567890");
|
||||
// and clear everything after the text, because the buffer is empty.
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestExactWrappingWithSpaces()
|
||||
{
|
||||
// This test is also explained by the comment at the top of TestExactWrappingWithoutSpaces
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
const auto charsToWrite = initialTermView.Width();
|
||||
VERIFY_ARE_EQUAL(80, charsToWrite);
|
||||
|
||||
for (auto i = 0; i < charsToWrite; i++)
|
||||
{
|
||||
// This is a handy way of just printing the printable characters that
|
||||
// _aren't_ the space character.
|
||||
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
|
||||
hostSm.ProcessCharacter(wch);
|
||||
}
|
||||
|
||||
hostSm.ProcessString(L"\n");
|
||||
hostSm.ProcessString(L" ");
|
||||
hostSm.ProcessString(L"1234567890");
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);
|
||||
|
||||
// Verify that we marked the 0th row as _not wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_IS_FALSE(row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 });
|
||||
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 1 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb);
|
||||
|
||||
// First write the first 80 characters from the string
|
||||
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
|
||||
|
||||
// This is the hard line break
|
||||
expectedOutput.push_back("\r\n");
|
||||
// Now write row 2 of the buffer
|
||||
expectedOutput.push_back(" 1234567890");
|
||||
// and clear everything after the text, because the buffer is empty.
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::MoveCursorAtEOL()
|
||||
{
|
||||
// This is a test for GH#1245
|
||||
@@ -358,9 +618,9 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
@@ -412,10 +672,263 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
|
||||
verifyData1(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestResizeHeight()
|
||||
{
|
||||
// This test class is _60_ tests to ensure that resizing the terminal works
|
||||
// with conpty correctly. There's a lot of min/maxing in expressions here,
|
||||
// to account for the sheer number of cases here, and that we have to handle
|
||||
// both resizing larger and smaller all in one test.
|
||||
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
TEST_METHOD_PROPERTY(L"Data:dx", L"{-1, 0, 1}")
|
||||
TEST_METHOD_PROPERTY(L"Data:dy", L"{-10, -1, 0, 1, 10}")
|
||||
TEST_METHOD_PROPERTY(L"Data:printedRows", L"{1, 10, 50, 200}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
int dx, dy;
|
||||
int printedRows;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dx", dx), L"change in width of buffer");
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dy", dy), L"change in height of buffer");
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"printedRows", printedRows), L"Number of rows of text to print");
|
||||
|
||||
_checkConptyOutput = false;
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto* hostTb = &si.GetTextBuffer();
|
||||
auto* termTb = term->_buffer.get();
|
||||
const auto initialHostView = si.GetViewport();
|
||||
const auto initialTermView = term->GetViewport();
|
||||
const auto initialTerminalBufferHeight = term->GetTextBuffer().GetSize().Height();
|
||||
|
||||
VERIFY_ARE_EQUAL(0, initialHostView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, initialHostView.BottomExclusive());
|
||||
VERIFY_ARE_EQUAL(0, initialTermView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, initialTermView.BottomExclusive());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Print %d lines of output, which will scroll the viewport", printedRows));
|
||||
|
||||
for (auto i = 0; i < printedRows; i++)
|
||||
{
|
||||
// This looks insane, but this expression is carefully crafted to give
|
||||
// us only printable characters, starting with `!` (0n33).
|
||||
// Similar statements are used elsewhere throughout this test.
|
||||
auto wstr = std::wstring(1, static_cast<wchar_t>((i) % 93) + 33);
|
||||
hostSm.ProcessString(wstr);
|
||||
hostSm.ProcessString(L"\r\n");
|
||||
}
|
||||
|
||||
// Conpty doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto secondHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, secondHostView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, secondHostView.BottomExclusive());
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
const auto secondTermView = term->GetViewport();
|
||||
// If we've printed more lines than the height of the buffer, then we're
|
||||
// expecting the viewport to have moved down. Otherwise, the terminal's
|
||||
// viewport will stay at 0,0.
|
||||
const auto expectedTerminalViewBottom = std::max(std::min(::base::saturated_cast<short>(printedRows + 1),
|
||||
term->GetBufferHeight()),
|
||||
term->GetViewport().Height());
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, secondTermView.BottomExclusive());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom - initialTermView.Height(), secondTermView.Top());
|
||||
|
||||
auto verifyTermData = [&expectedTerminalViewBottom, &printedRows, this, &initialTerminalBufferHeight](TextBuffer& termTb, const int resizeDy = 0) {
|
||||
// Some number of lines of text were lost from the scrollback. The
|
||||
// number of lines lost will be determined by whichever of the initial
|
||||
// or current buffer is smaller.
|
||||
const auto numLostRows = std::max(0,
|
||||
printedRows - std::min(term->GetTextBuffer().GetSize().Height(), initialTerminalBufferHeight) + 1);
|
||||
|
||||
const auto rowsWithText = std::min(::base::saturated_cast<short>(printedRows),
|
||||
expectedTerminalViewBottom) -
|
||||
1 + std::min(resizeDy, 0);
|
||||
|
||||
for (short row = 0; row < rowsWithText; row++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
auto iter = termTb.GetCellDataAt({ 0, row });
|
||||
const wchar_t expectedChar = static_cast<wchar_t>((row + numLostRows) % 93) + 33;
|
||||
|
||||
auto expectedString = std::wstring(1, expectedChar);
|
||||
|
||||
if (iter->Chars() != expectedString)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
};
|
||||
auto verifyHostData = [&si, &initialHostView, &printedRows](TextBuffer& hostTb, const int resizeDy = 0) {
|
||||
const auto hostView = si.GetViewport();
|
||||
|
||||
// In the host, there are two regions we're interested in:
|
||||
|
||||
// 1. the first section of the buffer with the output in it. Before
|
||||
// we're resized, this will be filled with one character on each row.
|
||||
// 2. The second area below the first that's empty (filled with spaces).
|
||||
// Initially, this is only one row.
|
||||
// After we resize, different things will happen.
|
||||
// * If we decrease the height of the buffer, the characters in the
|
||||
// buffer will all move _up_ the same number of rows. We'll want to
|
||||
// only check the first initialView+dy rows for characters.
|
||||
// * If we increase the height, rows will be added at the bottom. We'll
|
||||
// want to check the initial viewport height for the original
|
||||
// characters, but then we'll want to look for more blank rows at the
|
||||
// bottom. The characters in the initial viewport won't have moved.
|
||||
|
||||
const short originalViewHeight = ::base::saturated_cast<short>(resizeDy < 0 ?
|
||||
initialHostView.Height() + resizeDy :
|
||||
initialHostView.Height());
|
||||
const auto rowsWithText = std::min(originalViewHeight - 1, printedRows);
|
||||
const bool scrolled = printedRows > initialHostView.Height();
|
||||
// The last row of the viewport should be empty
|
||||
// The second last row will have '0'+50
|
||||
// The third last row will have '0'+49
|
||||
// ...
|
||||
// The <height> last row will have '0'+(50-height+1)
|
||||
const auto firstChar = static_cast<wchar_t>(scrolled ?
|
||||
(printedRows - originalViewHeight + 1) :
|
||||
0);
|
||||
|
||||
short row = 0;
|
||||
// Don't include the last row of the viewport in this check, since it'll
|
||||
// be blank. We'll check it in the below loop.
|
||||
for (; row < rowsWithText; row++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
auto iter = hostTb.GetCellDataAt({ 0, row });
|
||||
|
||||
const auto expectedChar = static_cast<wchar_t>(((firstChar + row) % 93) + 33);
|
||||
auto expectedString = std::wstring(1, static_cast<wchar_t>(expectedChar));
|
||||
|
||||
if (iter->Chars() != expectedString)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars(), NoThrowString().Format(L"%s", expectedString.data()));
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
|
||||
// Check that the remaining rows in the viewport are empty.
|
||||
for (; row < hostView.Height(); row++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
auto iter = hostTb.GetCellDataAt({ 0, row });
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
};
|
||||
|
||||
verifyHostData(*hostTb);
|
||||
verifyTermData(*termTb);
|
||||
|
||||
const COORD newViewportSize{
|
||||
::base::saturated_cast<short>(TerminalViewWidth + dx),
|
||||
::base::saturated_cast<short>(TerminalViewHeight + dy)
|
||||
};
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Resize the Terminal and conpty here"));
|
||||
auto resizeResult = term->UserResize(newViewportSize);
|
||||
VERIFY_SUCCEEDED(resizeResult);
|
||||
_resizeConpty(newViewportSize.X, newViewportSize.Y);
|
||||
|
||||
// After we resize, make sure to get the new textBuffers
|
||||
hostTb = &si.GetTextBuffer();
|
||||
termTb = term->_buffer.get();
|
||||
|
||||
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto thirdHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, thirdHostView.Top());
|
||||
VERIFY_ARE_EQUAL(newViewportSize.Y, thirdHostView.BottomExclusive());
|
||||
|
||||
// The Terminal should be stuck to the top of the viewport, unless dy<0,
|
||||
// rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to
|
||||
// the old top, we actually shifted it down (because the output was at the
|
||||
// bottom of the window, not empty lines).
|
||||
const auto thirdTermView = term->GetViewport();
|
||||
if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight))
|
||||
{
|
||||
VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive());
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive());
|
||||
}
|
||||
|
||||
verifyHostData(*hostTb, dy);
|
||||
// Note that at this point, nothing should have changed with the Terminal.
|
||||
verifyTermData(*termTb, dy);
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Paint a frame to update the Terminal"));
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto fourthHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, fourthHostView.Top());
|
||||
VERIFY_ARE_EQUAL(newViewportSize.Y, fourthHostView.BottomExclusive());
|
||||
|
||||
// The Terminal should be stuck to the top of the viewport, unless dy<0,
|
||||
// rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to
|
||||
// the old top, we actually shifted it down (because the output was at the
|
||||
// bottom of the window, not empty lines).
|
||||
const auto fourthTermView = term->GetViewport();
|
||||
if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight))
|
||||
{
|
||||
VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive());
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive());
|
||||
}
|
||||
verifyHostData(*hostTb, dy);
|
||||
verifyTermData(*termTb, dy);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::PassthroughCursorShapeImmediately()
|
||||
{
|
||||
// This is a test for GH#4106, and more indirectly, GH #2011.
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Change the cursor shape with VT. This should immediately be flushed to the Terminal."));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
_logConpty = true;
|
||||
|
||||
VERIFY_ARE_NOT_EQUAL(CursorType::VerticalBar, hostTb.GetCursor().GetType());
|
||||
VERIFY_ARE_NOT_EQUAL(CursorType::VerticalBar, termTb.GetCursor().GetType());
|
||||
|
||||
expectedOutput.push_back("\x1b[5 q");
|
||||
hostSm.ProcessString(L"\x1b[5 q");
|
||||
|
||||
VERIFY_ARE_EQUAL(CursorType::VerticalBar, hostTb.GetCursor().GetType());
|
||||
VERIFY_ARE_EQUAL(CursorType::VerticalBar, termTb.GetCursor().GetType());
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::PassthroughClearScrollback()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write more lines of outout. We should use \r\n to move the cursor"));
|
||||
L"Write more lines of output than there are lines in the viewport. Clear the scrollback with ^[[3J"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
@@ -423,6 +936,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
@@ -473,7 +987,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
|
||||
const auto termSecondView = term->GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, termSecondView.Top());
|
||||
|
||||
// Verify the top of the Terminal veiwoprt contains the contents of the old viewport
|
||||
// Verify the top of the Terminal viewport contains the contents of the old viewport
|
||||
for (short y = 0; y < termSecondView.BottomInclusive(); y++)
|
||||
{
|
||||
TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y });
|
||||
@@ -485,3 +999,70 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
|
||||
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
|
||||
}
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::PassthroughHardReset()
|
||||
{
|
||||
// This test is highly similar to PassthroughClearScrollback.
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write more lines of output than there are lines in the viewport. Clear everything with ^[c"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
_logConpty = true;
|
||||
|
||||
const auto hostView = si.GetViewport();
|
||||
const auto end = 2 * hostView.Height();
|
||||
for (auto i = 0; i < end; i++)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end));
|
||||
expectedOutput.push_back("X");
|
||||
if (i < hostView.BottomInclusive())
|
||||
{
|
||||
expectedOutput.push_back("\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// After we hit the bottom of the viewport, the newlines come in
|
||||
// separated for whatever reason.
|
||||
|
||||
expectedOutput.push_back("\r");
|
||||
expectedOutput.push_back("\n");
|
||||
expectedOutput.push_back("");
|
||||
}
|
||||
|
||||
hostSm.ProcessString(L"X\n");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
// Verify that we've printed height*2 lines of X's to the Terminal
|
||||
const auto termFirstView = term->GetViewport();
|
||||
for (short y = 0; y < 2 * termFirstView.Height(); y++)
|
||||
{
|
||||
TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y });
|
||||
}
|
||||
|
||||
// Write a Hard Reset VT sequence to the host, it should come through to the Terminal
|
||||
expectedOutput.push_back("\033c");
|
||||
hostSm.ProcessString(L"\033c");
|
||||
|
||||
const auto termSecondView = term->GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, termSecondView.Top());
|
||||
|
||||
// Verify everything has been cleared out
|
||||
for (short y = 0; y < termFirstView.BottomInclusive(); y++)
|
||||
{
|
||||
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
@@ -21,66 +24,109 @@ namespace TerminalCoreUnitTests
|
||||
{
|
||||
TEST_CLASS(ScreenSizeLimitsTest);
|
||||
|
||||
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds)
|
||||
{
|
||||
DummyRenderTarget emptyRenderTarget;
|
||||
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds);
|
||||
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds);
|
||||
|
||||
// Negative values for initial visible row count or column count
|
||||
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
|
||||
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
|
||||
Terminal negativeColumnsTerminal;
|
||||
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
|
||||
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
|
||||
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
|
||||
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
|
||||
|
||||
// Zero values are clamped to 1 as well.
|
||||
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
|
||||
Terminal zeroRowsTerminal;
|
||||
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
|
||||
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
|
||||
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
|
||||
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
|
||||
}
|
||||
|
||||
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds)
|
||||
{
|
||||
// What is actually clamped is the number of rows in the internal history buffer,
|
||||
// which is the *sum* of the history size plus the number of rows
|
||||
// actually visible on screen at the moment.
|
||||
|
||||
const unsigned int visibleRowCount = 100;
|
||||
DummyRenderTarget emptyRenderTarget;
|
||||
|
||||
// Zero history size is acceptable.
|
||||
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
|
||||
Terminal noHistoryTerminal;
|
||||
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted");
|
||||
|
||||
// Negative history sizes are clamped to zero.
|
||||
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
|
||||
Terminal negativeHistorySizeTerminal;
|
||||
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0");
|
||||
|
||||
// History size + initial visible rows == SHRT_MAX is acceptable.
|
||||
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
|
||||
Terminal maxHistorySizeTerminal;
|
||||
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == SHRT_MAX - initial row count is accepted");
|
||||
|
||||
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
|
||||
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
|
||||
Terminal justTooBigHistorySizeTerminal;
|
||||
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
|
||||
|
||||
// Ridiculously large history sizes are also clamped.
|
||||
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
|
||||
Terminal farTooBigHistorySizeTerminal;
|
||||
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size that is far too large is clamped to SHRT_MAX - initial row count");
|
||||
}
|
||||
TEST_METHOD(ResizeIsClampedToBounds);
|
||||
};
|
||||
}
|
||||
|
||||
using namespace TerminalCoreUnitTests;
|
||||
|
||||
void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds()
|
||||
{
|
||||
DummyRenderTarget emptyRenderTarget;
|
||||
|
||||
// Negative values for initial visible row count or column count
|
||||
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
|
||||
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
|
||||
Terminal negativeColumnsTerminal;
|
||||
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
|
||||
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
|
||||
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
|
||||
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
|
||||
|
||||
// Zero values are clamped to 1 as well.
|
||||
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
|
||||
Terminal zeroRowsTerminal;
|
||||
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
|
||||
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
|
||||
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
|
||||
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
|
||||
}
|
||||
|
||||
void ScreenSizeLimitsTest::ScrollbackHistorySizeIsClampedToBounds()
|
||||
{
|
||||
// What is actually clamped is the number of rows in the internal history buffer,
|
||||
// which is the *sum* of the history size plus the number of rows
|
||||
// actually visible on screen at the moment.
|
||||
|
||||
const unsigned int visibleRowCount = 100;
|
||||
DummyRenderTarget emptyRenderTarget;
|
||||
|
||||
// Zero history size is acceptable.
|
||||
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
|
||||
Terminal noHistoryTerminal;
|
||||
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted");
|
||||
|
||||
// Negative history sizes are clamped to zero.
|
||||
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
|
||||
Terminal negativeHistorySizeTerminal;
|
||||
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0");
|
||||
|
||||
// History size + initial visible rows == SHRT_MAX is acceptable.
|
||||
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
|
||||
Terminal maxHistorySizeTerminal;
|
||||
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == SHRT_MAX - initial row count is accepted");
|
||||
|
||||
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
|
||||
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
|
||||
Terminal justTooBigHistorySizeTerminal;
|
||||
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
|
||||
|
||||
// Ridiculously large history sizes are also clamped.
|
||||
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
|
||||
Terminal farTooBigHistorySizeTerminal;
|
||||
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size that is far too large is clamped to SHRT_MAX - initial row count");
|
||||
}
|
||||
|
||||
void ScreenSizeLimitsTest::ResizeIsClampedToBounds()
|
||||
{
|
||||
// What is actually clamped is the number of rows in the internal history buffer,
|
||||
// which is the *sum* of the history size plus the number of rows
|
||||
// actually visible on screen at the moment.
|
||||
//
|
||||
// This is a test for GH#2630, GH#2815.
|
||||
|
||||
const unsigned int initialVisibleColCount = 50;
|
||||
const unsigned int initialVisibleRowCount = 50;
|
||||
const auto historySize = SHRT_MAX - (initialVisibleRowCount * 2);
|
||||
DummyRenderTarget emptyRenderTarget;
|
||||
|
||||
Log::Comment(L"Watch out - this test takes a while on debug, because "
|
||||
L"ResizeWithReflow takes a while on debug. This is expected.");
|
||||
|
||||
auto settings = winrt::make<MockTermSettings>(historySize, initialVisibleRowCount, initialVisibleColCount);
|
||||
Log::Comment(L"First create a terminal with fewer than SHRT_MAX lines");
|
||||
Terminal terminal;
|
||||
terminal.CreateFromSettings(settings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(historySize + initialVisibleRowCount));
|
||||
|
||||
Log::Comment(L"Resize the terminal to have exactly SHRT_MAX lines");
|
||||
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount * 2 }));
|
||||
|
||||
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX));
|
||||
|
||||
Log::Comment(L"Resize the terminal to have MORE than SHRT_MAX lines - we should clamp to SHRT_MAX");
|
||||
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount * 3 }));
|
||||
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX));
|
||||
|
||||
Log::Comment(L"Resize back down to the original size");
|
||||
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount }));
|
||||
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(historySize + initialVisibleRowCount));
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionAnchor({ 5, rowValue });
|
||||
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetEndSelectionPosition({ 15, 20 });
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -110,19 +110,19 @@ namespace TerminalCoreUnitTests
|
||||
{
|
||||
const COORD maxCoord = { SHRT_MAX, SHRT_MAX };
|
||||
|
||||
// Test SetSelectionAnchor(COORD) and SetEndSelectionPosition(COORD)
|
||||
// Test SetSelectionAnchor(COORD) and SetSelectionEnd(COORD)
|
||||
// Behavior: clamp coord to viewport.
|
||||
auto ValidateSingleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
|
||||
Terminal term;
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 10, 10 }, scrollback, emptyRT);
|
||||
|
||||
// NOTE: SetEndSelectionPosition(COORD) is called within SetSelectionAnchor(COORD)
|
||||
// NOTE: SetSelectionEnd(COORD) is called within SetSelectionAnchor(COORD)
|
||||
term.SetSelectionAnchor(maxCoord);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
};
|
||||
|
||||
// Test DoubleClickSelection(COORD)
|
||||
// Test a Double Click Selection
|
||||
// Behavior: clamp coord to viewport.
|
||||
// Then, do double click selection.
|
||||
auto ValidateDoubleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
|
||||
@@ -130,11 +130,11 @@ namespace TerminalCoreUnitTests
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 10, 10 }, scrollback, emptyRT);
|
||||
|
||||
term.DoubleClickSelection(maxCoord);
|
||||
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansionMode::Word);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
};
|
||||
|
||||
// Test TripleClickSelection(COORD)
|
||||
// Test a Triple Click Selection
|
||||
// Behavior: clamp coord to viewport.
|
||||
// Then, do triple click selection.
|
||||
auto ValidateTripleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
|
||||
@@ -142,7 +142,7 @@ namespace TerminalCoreUnitTests
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 10, 10 }, scrollback, emptyRT);
|
||||
|
||||
term.TripleClickSelection(maxCoord);
|
||||
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansionMode::Line);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
};
|
||||
|
||||
@@ -226,17 +226,17 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Case 1: Move out of right boundary
|
||||
Log::Comment(L"Out of bounds: X-value too large");
|
||||
term.SetEndSelectionPosition({ 20, 5 });
|
||||
term.SetSelectionEnd({ 20, 5 });
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 5, 5, rightBoundary, 5 }));
|
||||
|
||||
// Case 2: Move out of left boundary
|
||||
Log::Comment(L"Out of bounds: X-value negative");
|
||||
term.SetEndSelectionPosition({ -20, 5 });
|
||||
term.SetSelectionEnd({ -20, 5 });
|
||||
ValidateSingleRowSelection(term, { leftBoundary, 5, 5, 5 });
|
||||
|
||||
// Case 3: Move out of top boundary
|
||||
Log::Comment(L"Out of bounds: Y-value negative");
|
||||
term.SetEndSelectionPosition({ 5, -20 });
|
||||
term.SetSelectionEnd({ 5, -20 });
|
||||
{
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Case 4: Move out of bottom boundary
|
||||
Log::Comment(L"Out of bounds: Y-value too large");
|
||||
term.SetEndSelectionPosition({ 5, 20 });
|
||||
term.SetSelectionEnd({ 5, 20 });
|
||||
{
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
@@ -310,10 +310,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate ALT + click at (x,y) = (5,10)
|
||||
term.SetSelectionAnchor({ 5, rowValue });
|
||||
term.SetBoxSelection(true);
|
||||
term.SetBlockSelection(true);
|
||||
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetEndSelectionPosition({ 15, 20 });
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -349,7 +349,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionAnchor({ 5, rowValue });
|
||||
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetEndSelectionPosition({ 15, 20 });
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -449,10 +449,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate ALT + click at (x,y) = (5,8)
|
||||
term.SetSelectionAnchor({ 5, 8 });
|
||||
term.SetBoxSelection(true);
|
||||
term.SetBlockSelection(true);
|
||||
|
||||
// Simulate move to (x,y) = (7,12)
|
||||
term.SetEndSelectionPosition({ 7, 12 });
|
||||
term.SetSelectionEnd({ 7, 12 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -501,7 +501,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate double click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.DoubleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, (4 + gsl::narrow<SHORT>(text.size()) - 1), 10 }));
|
||||
@@ -519,7 +519,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.DoubleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -546,7 +546,7 @@ namespace TerminalCoreUnitTests
|
||||
// Simulate click at (x,y) = (15,10)
|
||||
// this is over the '>' char
|
||||
auto clickPos = COORD{ 15, 10 };
|
||||
term.DoubleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// ---Validate selection area---
|
||||
// "Terminal" is in class 2
|
||||
@@ -572,14 +572,14 @@ namespace TerminalCoreUnitTests
|
||||
term.Write(text);
|
||||
|
||||
// Simulate double click at (x,y) = (5,10)
|
||||
term.DoubleClickSelection({ 5, 10 });
|
||||
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// Simulate move to (x,y) = (21,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^
|
||||
// start finish
|
||||
term.SetEndSelectionPosition({ 21, 10 });
|
||||
term.SetSelectionEnd({ 21, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
|
||||
@@ -601,14 +601,14 @@ namespace TerminalCoreUnitTests
|
||||
term.Write(text);
|
||||
|
||||
// Simulate double click at (x,y) = (21,10)
|
||||
term.DoubleClickSelection({ 21, 10 });
|
||||
term.MultiClickSelection({ 21, 10 }, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// Simulate move to (x,y) = (5,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^
|
||||
// finish start
|
||||
term.SetEndSelectionPosition({ 5, 10 });
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
|
||||
@@ -622,7 +622,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.TripleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
|
||||
@@ -636,10 +636,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.TripleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
|
||||
|
||||
// Simulate move to (x,y) = (7,10)
|
||||
term.SetEndSelectionPosition({ 7, 10 });
|
||||
term.SetSelectionEnd({ 7, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
|
||||
@@ -653,10 +653,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.TripleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
|
||||
|
||||
// Simulate move to (x,y) = (5,11)
|
||||
term.SetEndSelectionPosition({ 5, 11 });
|
||||
term.SetSelectionEnd({ 5, 11 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -689,7 +689,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate move to (x,y) = (5,10)
|
||||
// (So, no movement)
|
||||
term.SetEndSelectionPosition({ 5, 10 });
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
|
||||
// Case 1: single cell selection not allowed
|
||||
{
|
||||
@@ -705,12 +705,12 @@ namespace TerminalCoreUnitTests
|
||||
}
|
||||
|
||||
// Case 2: move off of single cell
|
||||
term.SetEndSelectionPosition({ 6, 10 });
|
||||
term.SetSelectionEnd({ 6, 10 });
|
||||
ValidateSingleRowSelection(term, { 5, 10, 6, 10 });
|
||||
VERIFY_IS_TRUE(term.IsSelectionActive());
|
||||
|
||||
// Case 3: move back onto single cell (now allowed)
|
||||
term.SetEndSelectionPosition({ 5, 10 });
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
ValidateSingleRowSelection(term, { 5, 10, 5, 10 });
|
||||
|
||||
// single cell selection should now be allowed
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user